시리즈: ponslink-algorithm에 해당하는 글입니다.
![[PonsLink] PonsCast가 화면 공유 대신 DataChannel을 택한 이유](/tistory/body-images/2026-06-18-ponslink-algorithm-10-ponscast-tradeoff/cover.webp)
PonsCast를 만들며 화면 공유를 먼저 떠올린 것은 자연스러웠다. 화면 공유는 상대가 무엇을 보는지 바로 맞출 수 있다. 하지만 우리가 해결하려던 문제는 같은 화면을 복제하는 일이 아니었다. 각자가 가진 파일을 상대의 언어와 시간에 맞춰 함께 읽는 일이었다. 화면 공유는 쉬운 답처럼 보였지만 파일의 구조와 상태를 직접 다루기 어렵다. 보는 사람은 화면을 받지만 파일을 함께 읽고 조작하는 흐름과는 거리가 있다. 네트워크가 흔들리면 화면 전체가 영향을 받고 무엇을 기준으로 다시 맞출지도 분명하지 않다.
![[PonsLink] 마이크와 PonsCast 오디오를 함께 살리는 라우팅](/tistory/body-images/2026-06-18-ponslink-algorithm-09-ponscast-audio/cover.webp)
PonsCast 오디오를 방 안의 마이크와 함께 다루면서 소리를 한 줄의 미디어 스트림으로 볼 수 없다는 걸 알게 됐다. 사용자의 목소리와 파일에서 재생되는 소리는 출처와 역할이 다르다. 둘을 같은 방식으로 섞으면 말하는 사람의 목소리가 묻히거나 재생 소리가 예상하지 못한 방식으로 전달될 수 있다.
![[PonsLink] PonsCast 파일 감지와 캐시 전략의 기준](/tistory/body-images/2026-06-18-ponslink-algorithm-08-ponscast-cache/cover.webp)
화면 공유와 파일 동기화는 비슷해 보이지만 “같은 시간을 공유한다”는 요구는 완전히 달랐다는 문제가 있었다. PonsCast 캐시는 같은 파일을 다시 읽는 비용과 재생의 흐름을 함께 봐야 했다. 링크를 만들고, 상대에게 보내고, 같은 화면 안에서 말하고 파일을 주고받으면 된다고 봤다. PonsCast는 기능표에서 나온 아이디어가 아니었다. 언어가 잘 통하지 않는 사람과 같은 파일을 같은 시간에 보고 싶다는 꽤 개인적인 욕심에서 시작됐다.
![[PonsLink] DataChannel 재생을 버티게 하는 백프레셔와 지터 버퍼](/tistory/body-images/2026-06-18-ponslink-algorithm-07-ponscast-backpressure/cover.webp)
PonsCast는 파일을 보내는 기능보다 같은 시간을 공유하는 방식에 가까웠다. 화면 공유는 쉽게 떠올릴 수 있는 답이었지만, 내가 가진 파일을 상대의 언어와 속도에 맞춰 함께 보는 일은 화면을 복제하는 것과 달랐다. DataChannel 재생이 끊기면 이 차이가 바로 드러났다. 보내는 쪽이 계속 데이터를 밀어 넣으면 받는 쪽은 잠깐 뒤처진다. 그 차이가 쌓이면 재생은 끊기고, 같은 장면을 보고 있다는 약속도 무너진다. 내부에서는 버퍼가 조금 늘어난 것처럼 보여도 사용자에게는 불안정한 경험으로 남는다.
![[PonsLink] DataChannel 위에 PonsCast 프로토콜을 올린 이유](/tistory/body-images/2026-06-18-ponslink-algorithm-06-ponscast-protocol/cover.webp)
PonsCast는 방 안에 붙인 미디어 기능으로 시작하지 않았다. 언어가 잘 통하지 않는 사람과 같은 파일을 같은 시간에 보고 싶다는 개인적인 필요에서 출발했다. 화면 공유를 쓰면 화면은 같아진다. 하지만 상대가 파일의 어느 부분을 보고 있는지, 같은 속도로 따라오고 있는지, 한쪽이 바뀐 내용을 다른 쪽이 놓치지 않는지는 별개의 문제였다. 내가 해결하고 싶었던 것은 같은 화면보다 같은 시간을 공유하는 일이었다.
![[PonsLink] 여러 내부 상태를 하나의 사용자 문장으로 접기](/tistory/body-images/2026-06-18-ponslink-algorithm-05-request-state/cover.webp)
회의는 방에 들어온 뒤 시작되는 줄 알았다. 실제로는 그보다 앞에서 이미 시작된다. 누가, 왜, 언제 만나고 싶은지 모르면 좋은 방도 금방 비어 보인다. 그래서 요청 흐름을 먼저 받는 구조로 바꿨다. 요청을 받는 일은 사람을 거르는 장치가 아니라 서로의 시간을 덜 낭비하게 만드는 완충지대였다. 문제는 내부에 여러 상태가 생겼다는 점이다. 사용자에게 그 상태를 그대로 보여 주면 제품은 곧바로 내부 시스템처럼 보인다.
![[PonsLink] P2P 파일 전송에 작은 TCP가 필요했던 이유](/tistory/body-images/2026-06-18-ponslink-algorithm-03-file-transfer/cover.webp)
P2P만 있으면 나머지는 따라올 거라고 믿었다. 같은 단어를 쓰더라도 제품 문맥이 다르면 답이 갈렸다. 그 갈림길을 숨기지 않는 쪽으로 썼다. 파일 전송은 보내고 받는 속도가 다르다는 사실부터 인정해야 했다. 처음 기대와 실제 제약을 한 번 충돌시킨 뒤 선택 이유가 드러났다. 보내는 일, 받는 일, 저장하는 일은 같은 줄에 서 있지 않았다. 하나라도 밀리면 사용자는 전체 전송이 실패했다고 느꼈다.
![[PonsLink] 실시간 메시지는 모두 같은 줄에 서면 안 된다](/tistory/body-images/2026-06-18-ponslink-algorithm-02-realtime-queue/cover.webp)
실시간 메시지를 한 줄에 세우면 안 됐다. 채팅과 제어 메시지와 파일 조각은 모두 메시지이지만, 늦어졌을 때 사용자가 느끼는 피해가 같지 않다. 처음에는 단일 큐로 충분하다고 생각했다. 도착한 순서대로 처리하면 구현이 단순하고, 순서를 설명하기도 쉬워 보였다. 혼잡 구간에서 이 가정이 깨졌다. 파일 전송이 밀리면 제어 메시지까지 늦어지고, 사용자는 방 전체가 멈춘 것으로 느꼈다.