주제: dev-retrospective/ponswarp에 해당하는 글입니다.
![[PonsWarp 개념 교실 25] 멱등성 — 같은 결제 이벤트를 두 번 처리하지 않는 법](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-idempotent-webhook-cover-imagegen.webp)
쉽게 말하면, **멱등성은 같은 요청을 한 번 처리하든 열 번 처리하든 최종 결과가 한 번 처리한 것과 같게 만드는 성질**이다.  결제 Webhook은 네트워크 오류
![[PonsWarp 개념 교실 24] 토큰버킷 — API 요청을 어떻게 일정하게 제한하나](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-token-bucket-cover-imagegen.webp)
쉽게 말하면, **토큰버킷은 요청이 들어올 때마다 토큰을 하나씩 꺼내고, 토큰이 없으면 잠시 기다리거나 거부하는 요청 제한 장치**다.  무조건 초당 N개만 허용하는 고정 창보다 순간 bur
![[PonsWarp 개념 교실 26] 헥사고날 아키텍처 — 외부 기술을 갈아끼우는 구조](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-26-hexagonal-cover-imagegen.webp)
쉽게 말하면, **헥사고날 아키텍처는 핵심 업무 로직이 WebRTC·파일시스템·클라우드 같은 외부 기술을 직접 알지 않게 만드는 구조**다.  외부 기술을 바꾸더라도 안쪽
![[PonsWarp 개념 교실 23] presigned URL — 서버를 거치지 않고 R2에 올리는 법](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-23-presigned-url-cover-imagegen.webp)
Cloud Drop은 서버가 파일을 받아 저장하는 방식이 아니다. 서버가 잠깐 유효한 업로드 URL을 발급하고, 브라우저가 R2에 직접 PUT한다.
![[PonsWarp 개념 교실 22] TURN 자격증명 — coturn REST API와 HMAC-SHA1](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-22-turn-credential-cover-imagegen.webp)
쉽게 말하면, **TURN 자격증명은 "잠깐만 쓸 수 있는 릴레이 이용권"이고, username 안에 만료시각을 넣고 그 전체를 HMAC-SHA1으로 서명한다.**  직접 WebRTC 연결이
![[PonsWarp 개념 교실 21] 시그널링 — 방 모델과 메시지 라우팅](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-21-signaling-room-cover-imagegen.webp)
쉽게 말하면, **시그널링 서버는 파일 운반자가 아니라 두 브라우저를 소개하는 교환원**이다.  파일 바이트는 WebRTC DataChannel로 직접 흐르고, 서버는
![[PonsWarp 개념 교실 20] Resume 커서 — 끊긴 전송을 중간부터 이어가는 법](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-20-resume-cursor-cover-imagegen.webp)
전송이 끊겼을 때 처음부터 다시 보내는 것은 가장 비싼 복구다. PonsWarp는 global offset을 file offset·sequence·next partition으로 바꿔 정확한 위치부터 재개한다.
![[PonsWarp 개념 교실 19] 백프레셔 — high/low watermark로 전송을 보호하는 법](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-19-backpressure-cover-imagegen.webp)
받는 쪽 디스크가 느린데 보내는 쪽이 계속 달리면 버퍼가 쌓인다. PonsWarp는 48MiB에서 PAUSE하고 16MiB까지 내려오면 RESUME하는 high/low watermark를 사용한다.
![[PonsWarp 개념 교실 18] AIMD — 혼잡 제어의 고전](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-18-aimd-cover-imagegen.webp)
네트워크가 괜찮을 때는 조금씩 키우고, 혼잡해지면 한 번에 줄인다. AIMD는 이 단순한 규칙으로 전송량을 안정화한다. PonsWarp는 RTT와 bufferedAmount를 보고 cwnd를 조절한다.
![[PonsWarp 개념 교실 17] 파티션 배리어 — PARTITION_ACK와 연속 프론티어](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-17-partition-barrier-cover-imagegen.webp)
암호화된 파일 조각을 끝없이 보내면 수신측 writer가 따라가지 못한다. PonsWarp는 일정 구간마다 PARTITION 마커를 보내고, 수신측의 연속 오프셋이 그 지점에 도달했을 때 다음 구간을 보낸다.
![[PonsWarp 개념 교실 16] in-flight 윈도우 — BDP와 슬라이딩 윈도우](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-16-sliding-window-cover-imagegen.webp)
보내지 않은 데이터를 무한히 큐에 넣으면 브라우저 버퍼가 터진다. PonsWarp는 네트워크 경로와 RTT를 보고 동시에 날아갈 수 있는 바이트를 정하고, bufferedAmount를 뺀 만큼만 더 요청한다.
![[PonsWarp 개념 교실 15] prepare-ahead 크레딧 — 생산자·소비자 큐](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-15-prepare-ahead-cover-imagegen.webp)
암호화 워커가 파일을 너무 빨리 읽으면 준비된 암호문이 메모리에 쌓인다. PonsWarp는 12MiB 크레딧을 두고 소비된 만큼만 다시 만들게 하는 생산자·소비자 흐름을 구현했다.
![[PonsWarp 개념 교실 14] WebRTC DataChannel — SCTP 신뢰성과 bufferedAmount](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-14-datachannel-cover-imagegen.webp)
WebRTC 연결이 됐다고 파일이 자동으로 안전하게 흐르는 것은 아니다. DataChannel은 SCTP 위에서 신뢰성과 순서를 제공하지만, 애플리케이션은 control/bulk 분리와 bufferedAmount 흐름 제어를 직접 설계해야 한다.
![[PonsWarp 개념 교실 13] Reed-Solomon — GF(2^8) 이레이저 코딩](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-13-reed-solomon-cover-imagegen.webp)
패킷이 손실돼도 원본을 복구할 수 있다면 재전송을 기다릴 필요가 없다. Reed-Solomon은 데이터에 패리티를 더해 일부를 잃어도 나머지로 복원하는 FEC다.
![[PonsWarp 개념 교실 12] LZ4 블록 압축 — 속도를 위해 만든 비표준 포맷](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-12-lz4-block-cover-imagegen.webp)
DEFLATE보다 훨씬 빠른 압축이 필요한 순간이 있다. LZ4는 압축률을 희생하고 속도에 올인한 블록 압축이다. PonsWarp는 LZ4를 표준 프레임이 아닌 자체 블록 포맷으로 구현했는데, 그 선택의 이유와 대가를 파헤친다.
![[PonsWarp 개념 교실 11] DEFLATE — zip64 속의 압축 (miniz_oxide)](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-11-deflate-cover-imagegen.webp)
ZIP64 편에서 압축은 옵션이라고 했다. 그 옵션을 켰을 때 실제로 데이터를 줄이는 DEFLATE는 어떤 원리로 동작하고, 스트리밍에서 어떻게 조각 단위로 압축을 이어가는지 파헤친다.
![[PonsWarp 개념 교실 10] ZIP64 스트리밍 — LFH·CEN·EOCD64 구조](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-10-zip64-stream-cover-imagegen.webp)
여러 파일을 묶어 하나의 다운로드로 만들 때, 전체를 메모리에 올리지 않고 스트리밍으로 ZIP을 쓸 수 있다. PonsWarp의 수신측은 ZIP64 구조(LFH→데이터→CEN→EOCD64)를 조각 단위로 만들어 4GB 이상도 무손실로 표현한다.
![[PonsWarp 개념 교실 9] 재정렬 버퍼 — Arena·free-list·BTreeMap·LRU](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-09-reordering-buffer-cover-imagegen.webp)
조각이 순서대로 도착한다는 보장은 없다. 수신측은 뒤섞인 조각을 정렬해 파일 순서대로 디스크에 써야 한다. PonsWarp는 Arena+free-list로 메모리를 관리하고, BTreeMap으로 인덱스를 유지하며, 128MiB 한도를 넘으면 LRU로 밀어낸다.
![[PonsWarp 개념 교실 8] 청크 크기 16~192KiB — 어떻게 정해졌나](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-08-chunk-size-cover-imagegen.webp)
청크가 크면 패킷 수가 줄어 빨라질 것 같지만, SCTP 메시지 상한과 메모리·재전송 비용이 가로막는다. PonsWarp가 경로별로 16~192KiB를 고르는 기준과, 크면 무조건 빠르다가 아닌 이유를 파헤친다.
![[PonsWarp 개념 교실 7] 22바이트 패킷 헤더 — 한 조각이 자신을 설명하는 법](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-07-packet-header-cover-imagegen.webp)
파일을 조각내 보내면 수신측은 이 조각이 파일의 어디에 붙는지 알아야 한다. PonsWarp는 22바이트 고정 헤더에 그 정보를 담는다. 각 필드가 왜 그 크기인지, 왜 그 순서인지, 그리고 CRC 필드가 0으로 비워진 사연까지 파헤친다.
![[PonsWarp 개념 교실 6] 무결성 3총사 — CRC32 vs SHA-256 vs Merkle Tree](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-06-integrity-trio-cover-imagegen.webp)
데이터가 전송 중 변조되지 않았는지 확인하는 방법은 세 가지 층위가 있다. CRC32는 빠르지만 약하고, SHA-256은 강하지만 무겁고, Merkle 트리는 부분 검증이 가능하다. PonsWarp가 실제로 고른 조합과 그 이유를 파헤친다.
![[PonsWarp 개념 교실 5] HMAC과 상수시간 비교 — 키 확인과 타이밍 공격](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-05-hmac-constant-time-cover-imagegen.webp)
두 브라우저가 같은 키를 가졌는지 확인하려면 문자열 비교로 충분할 것 같지만, 그 비교 방식이 보안 구멍이 될 수 있다. HMAC과 상수시간 비교가 왜 함께 필요한지 파헤친다.
![[PonsWarp 구현 분석 2] 복사하지 않는 게 제일 빠르다 — WASM 메모리와 제로카피](/tistory/ponswarp/implementation/2026-08-21-ponswarp-impl-02-wasm-memory-zero-copy-cover-imagegen.webp)
JS↔WASM 경계를 넘을 때 바이트를 몇 번 복사해야 할까? PonsWarp는 64KB 슬롯 풀을 WASM 메모리에 미리 할당해 쓰기 0회, 읽기 1회 복사를 실현했지만, 실제로는 절반만 제로카피인 지점도 있다.
![[PonsWarp 구현 분석 6] 브라우저끼리 키를 나누는 법 — E2E 암호화의 실제 적용](/tistory/ponswarp/implementation/2026-08-21-ponswarp-impl-06-e2e-crypto-nonce-cover-imagegen.webp)
개념 글에서 다룬 ECDH, HKDF, AES-256-GCM, nonce가 실제 PonsWarp 코드에서 하나의 파이프라인으로 어떻게 연결되는지, 전송 전부터 전송 중, 재개까지 추적한다.
![[PonsWarp 개념 교실 4] GCM nonce — 왜 4B 카운터 + 8B random prefix인가](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-04-gcm-nonce-cover-imagegen.webp)
GCM에서 nonce가 두 번 재사용되면 암호문 두 개를 XOR하는 것만으로 키를 역산할 수 있다. PonsWarp가 nonce를 4바이트 카운터와 8바이트 랜덤 프리픽스로 쪼갠 이유와, 그 설계에 남은 위험이 무엇인지 파헤친다.
![[PonsWarp 개념 교실 3] AES-256-GCM — 암호화의 구조 (CTR + GHASH + tag)](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-03-aes-256-gcm-cover-imagegen.webp)
AES-256-GCM은 암호화와 위변조 감지를 한 번에 해결한다. CTR 모드로 데이터를 섞고, GHASH로 인증 태그를 만들어 붙인다. 이 태그가 16바이트로 파일 무결성을 지키는 이유를 파헤친다.
![[PonsWarp 개념 교실 2] HKDF-SHA256 — 공동 비밀을 안전한 키로 가공하기](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-02-hkdf-sha256-cover-imagegen.webp)
ECDH가 만든 공동 비밀은 좋은 AES 키라고 바로 가정하면 안 된다. 그 사이에 키 제조 공장 하나를 두는데, 그게 HKDF-SHA256이다.
![[PonsWarp 개념 교실 1] ECDH P-256 — 둘만 아는 공동 비밀 만들기](/tistory/ponswarp/concept/2026-08-21-ponswarp-concept-01-ecdh-p256-cover-imagegen.webp)
서로 비밀키를 직접 보내지 않고, 두 기기만 똑같은 암호화 키를 만들어내는 방법.
![[WarpInfra] 남은 일 — mTLS·20GB 실측·Control MVP·그리고 컴퓨트 그리드](/tistory/body-images/2026-08-07-warpinfra-10-remaining-work/cover.webp)
시리즈의 마지막 편은 백로그다. 지금 남은 일의 순서는 우연이 아니라 원칙으로 고정되어 있다. 이 편은 RC1 게이트 뒤의 작업을 Now/Next/Later로 나눠 읽고, 시리즈를 닫는다.
![[WarpInfra] RC1 — 20GB·다중 머신·SHA-256 없으면 출시가 아니다](/tistory/body-images/2026-08-07-warpinfra-09-rc1-gate/cover.webp)
제품이 "완료됐다"고 말하는 순간은 기능이 끝났을 때가 아니라 증거가 갖춰졌을 때다. WarpInfra의 RC1 문서는 이 원칙을 게이트로 박아두었다. 이 편은 그 게이트와 현재 통과 상태를 대조한다.
![[WarpInfra] Phase A를 95%까지 올린 구현 묶음](/tistory/body-images/2026-08-07-warpinfra-08-phase-a-95/cover.webp)
Phase ALAN Grid MVP의 목표는 단순하다. 코디네이터 없이, 같은 LAN에서 앱들이 release를 공유하고 메시 수신한다. 2026-07-18 기준 이 단계는 약 95%다. 이 편은 그 95%를 만든 부품 묶음을 정리한다.
![[WarpInfra] 희소한 조각부터, 여러 피어에게 동시에](/tistory/body-images/2026-08-07-warpinfra-07-rarest-first-piece-race/cover.webp)
여러 Node가 한 파일을 나눠 받을 때, 스케줄러가 "어느 조각을 먼저 요청할지" 고르는 기준이 결과를 가른다. WarpInfra는 rarest-first를 택하고, Have fanout으로 조각 보유 정보를 퍼뜨리고, multi-peer piece race로 지연을 줄였다.
![[WarpInfra] 폴더가 단일 스트림으로 줄줄이 나가던 버그](/tistory/body-images/2026-08-07-warpinfra-06-zip-streaming-folder/cover.webp)
폴더 전송은 겉보기엔 단순하다. "파일을 묶어서 보내면 된다." 그런데 zip으로 묶는 순간 메모리와 디스크와 타이밍이 한꺼번에 어긋난다. 이 편은 zip 패키징 실패가 단일 스트림 연속 송신으로 바뀌기까지의 커밋을 읽는다.
![[WarpInfra] WASM을 버리고 네이티브 QUIC로 갈아탄 날](/tistory/body-images/2026-08-07-warpinfra-05-native-quic/cover.webp)
한때 전송 코어는 WASM에 얹혀 있었다. 웹과 데스크톱이 같은 코어를 쓰려는 의도였다. 그런데 데스크톱에선 그 선택이 오히려 발목을 잡았다. 이 편은 WASM 의존을 제거하고 네이티브 QUIC로 전환한 커밋들을 추적한다.
![[WarpInfra] CLI를 1등 시민으로 둔 이유 — CI와 AI 에이전트](/tistory/body-images/2026-08-07-warpinfra-04-cli-first-ai-agents/cover.webp)
GUI를 먼저 만들고 CLI를 "개발자용 부가기능"으로 두는 제품이 많다. WarpInfra는 정반대다. CLIwarp가 1등 시민이고, GUI와 agentd가 같은 코어를 공유한다. 이 선택의 근거는 단순하다. "운영자는 사람이 아니라 파이프라인이다."
![[WarpInfra] 컴퓨트 그리드를 일부러 뒤로 미룬 로드맵](/tistory/body-images/2026-08-07-warpinfra-03-roadmap-data-grid-first/cover.webp)
"그리드"라는 단어를 들으면 대부분 분산 연산을 떠올린다. WarpInfra는 반대로 갔다. 분산 전송데이터 그리드을 먼저 끝내고, 분산 연산컴퓨트 그리드은 데이터 그리드가 안정화된 뒤에만 설계하기로 했다. 이 순서 고정이 이 시리즈의 로드맵 편이다.
![[WarpInfra] 파일 바이트는 Control을 지나지 않는다](/tistory/body-images/2026-08-07-warpinfra-02-control-vs-data-plane/cover.webp)
그리드 아키텍처에서 가장 먼저 고정한 원칙이 하나 있다. "파일 payload는 Control이 아니라 Node 간 P2P로 이동한다." 이 한 줄이 coordinator 설계, 라우트 노출, 보안 경계를 모두 결정했다.
![[WarpInfra] 레지스트리 옆 CLI release get — 복제하지 않는 니치](/tistory/body-images/2026-08-07-warpinfra-01-niche-cli-release-get/cover.webp)
분산 파일 전송 제품은 이미 많다. Resilio, Aspera, Signiant. 그런데도 WarpInfra를 처음부터 만들기로 한 이유는 무엇일까. 한 줄로: 전체 미러링 제품을 복제하는 대신, "레지스트리 옆에서 release를 받는 CLI"라는 좁은 일감을 골랐기 때문이다.
![[WarpInfra] 데이터 그리드 시리즈를 시작하며 — desktop과 grid, 그리고 이름](/tistory/body-images/2026-08-07-warpinfra-00-series-map/cover.webp)
레포는 두 개다. 이름은 하나다. 이 시리즈는 DeclanJeon/ponswarp-desktop146커밋과 DeclanJeon/ponswarp-grid29커밋의 커밋 이력을 시간순이 아니라 설계 → 구현 → 남은 일 순서로 다시 읽는 기록이다. 이 글이 그 지도다.
![[PonsWarp Grid] 방을 먼저 열고, direct와 grid를 가르다](/tistory/body-images/2026-08-07-ponswarp-grid-03-room-first-direct-grid/cover.webp)
파일을 고르는 순간 방이 열린다. 전송은 direct직접와 grid그리드로 갈린다. 사용자는 경로를 고르지 않는다. 제품이 고른다. 이 편은 room-first 메시 전송과 direct/grid 분기, 그리고 브라우저 대용량 export가 Blob에서 터진 사건을 함께 읽는다.
![[PonsWarp Grid] coordinator를 "안전하게" 노출한다는 것](/tistory/body-images/2026-08-07-ponswarp-grid-02-coordinator-routes/cover.webp)
제어면이 바이트를 안 다루기로 했으니, coordinator가 노출하는 라우트는 메타만 다뤄야 한다. 이 편은 ponswarp-grid에서 coordinator 라우트를 공개하면서 무엇을 넣고 무엇을 금지했는지, 그리고 mesh 연산을 CLI로 묶은 커밋을 본다.
![[PonsWarp Grid] 브라우저 그리드 엔진을 처음부터 다시 짜다](/tistory/body-images/2026-08-07-ponswarp-grid-01-engine-from-zero/cover.webp)
desktop이 네이티브 그리드로 가는 동안, 브라우저 쪽에는 별도의 엔진이 필요했다. ponswarp-grid 레포다. 이 편은 그 엔진을 처음부터제로베이스 짠 29개 커밋의 시작점을 읽는다.
![[PonsWarp] 트래픽 숫자보다 먼저 고정한 측정 단위](/tistory/body-images/2026-07-28-ponswarp-what-we-measure-before-traffic/cover.webp)
사용자 수와 평균 파일 크기보다 먼저 정한 것은 크기 구간, 경로 라벨, 완료의 정의였다.
![[PonsWarp] 전송 성공률을 시작 횟수로 세면 안 되는 이유](/tistory/body-images/2026-07-28-ponswarp-transfer-success-is-not-start/cover.webp)
보내기를 누른 횟수는 출발이다. 성공은 파일이 닫히고 내용까지 맞을 때다. 성공률은 그 차이를 드러내는 렌즈여야 한다.
![[PonsWarp] 전송 중 암호화 키가 사라지자 재개가 막혔다](/tistory/body-images/2026-07-28-ponswarp-crypto-resume-incident/cover.webp)
이어받기 버튼은 있었는데 문을 열 열쇠(암호화 세션)가 먼저 사라진 장애였다. 속도 문제가 아니라 복구 조건 문제였다.
![[PonsWarp] LAN에서 WebRTC가 한계에 부딪혔을 때, stripe가 아니라 cloud로 갈라졌다](/tistory/body-images/2026-07-20-ponswarp-lan-webrtc-stripe-vs-cloud/cover.webp)
대용량 파일을 브라우저끼리 직접 보내는 작업을 하면서 속도 문제를 만났을 때 가장 처음 떠올린 것은 더 빠른 채널을 여는 일이었다. 하지만 LAN에서도 WebRTC DataChannel은 일정 수준 이상으로 올라가지 않았고, 멀티 PC에서 striping을 시도했을 때
![[PonsLink] 특정 Wi-Fi에서 파일 전송이 느려지는 문제를 추적하다](/tistory/body-images/2026-07-20-ponslink-wifi-nat-transfer-slowdown/cover.webp)
PonsWarp의 파일 전송 기능이 완성되어 가던 어느 날, 특정 Wi-Fi 환경에서 전송 속도가 현저히 떨어지는 보고가 나왔다. 같은 파일을 같은 브라우저에서 보내도, 어떤 네트워크에서는 정상이지만 다른 네트워크에서는 마지막 몇 퍼센트에서 멈추거나 수 초 동안 진행률
![[Backpressure 딥다이브] bufferedAmount와 backpressure가 파일 전송을 지킨다](/tistory/body-images/2026-07-04-realtime-network-22-bufferedamount-backpressure-file-transfer/cover.webp)
파일 전송을 만들 때 처음 떠오르는 질문은 속도다. 얼마나 빨리 보낼 수 있는지, chunk를 얼마나 크게 잡아야 하는지, send를 얼마나 자주 호출할 수 있는지부터 궁금해진다. 그런데 브라우저 DataChannel로 큰 파일을 보내려면 속도보다 먼저 봐야 할 값이 있다. 상대와 내 런타임이 지금 이 속도를 받을 수 있는가. 로그를 다시 펼쳤을 때 눈에 들어온 건 예쁜 성공 화면이 아니라, Backpressure 주변에서 반복되던 작은 끊김이었다.
![[DataChannel 딥다이브] WebRTC DataChannel은 파일 전송 API가 아니다](/tistory/body-images/2026-07-04-realtime-network-19-datachannel-is-not-file-api/cover.webp)
DataChannel로 파일을 보낼 수 있다는 말을 처음 들으면 브라우저가 파일 전송 API를 제공하는 것처럼 느껴진다. 실제로는 bytes를 주고받는 통로를 받은 것에 가깝다. 파일 이름, 크기, MIME 타입, chunk 순서, 무결성, 재시도, 저장 위치, 진행률은 DataChannel이 알아서 처리하지 않는다. 이 부분을 애플리케이션 프로토콜로 설계해야 파일 전송 기능이 된다.
![[PonsWarp] 대용량 전송에서는 속도보다 흐름 제어가 먼저였다](/tistory/body-images/2026-06-29-main-ponswarp-03-backpressure-before-speed/cover.webp)
대용량 파일 전송을 처음 볼 때는 속도가 가장 중요한 숫자처럼 보였다. 하지만 큰 파일을 실제 브라우저로 보내 보니 문제는 전송률이 아니었다. 보내는 쪽이 계속 밀어 넣는 동안 받는 쪽의 메모리와 저장 속도가 따라오지 못했고, 실패는 마지막 몇 퍼센트에서야 드러났다. PonsWarp에서 ACK는 “받았다”는 표시만을 위한 메시지가 아니었다. 수신자가 다음 데이터를 받을 준비가 됐다는 신호로 써야 했다. 보내는 쪽이 자기 속도만 믿고 앞서가면 전송은 잠깐 빨라 보이지만, 브라우저가 버티지 못하는 순간 전체 작업이 무너진다.
![[PonsWarp] Signaling 서버는 파일 운반자가 아니라 소개자였다](/tistory/body-images/2026-06-29-main-ponswarp-02-signaling-is-matchmaker/cover.webp)
처음에는 PonsLink 안에 있는 파일 전송 기능이었다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. 브라우저끼리 직접 붙이면 서버 부담이 줄고 구조도 단순해질 줄 알았다. 그런데 WebRTC는 연결의 문만 열어줬고, 질서는 제품이 직접 만들어야 했다.
![[PonsWarp] 서버가 파일을 갖지 않는 전송을 만들고 싶었다](/tistory/body-images/2026-06-29-main-ponswarp-01-server-does-not-own-file/cover.webp)
큰 파일을 보낼 때 사람들은 보통 클라우드를 떠올린다. 올리면 링크가 생기고, 상대가 받는다. 익숙하지만 대가는 분명하다. 서버가 파일을 잠시라도 갖게 되고, 권한·보관·삭제·비용 문제가 따라온다. PonsWarp에서 붙잡은 질문은 단순했다. **서버가 파일을 소유하지 않는 전송을 만들 수 있는가.**
![[PonsWarp] 결국 내가 만든 건 파일 전송 버튼이 아니라 실패를 견디는 흐름이었다](/tistory/body-images/2026-06-29-ponswarp-12b-flow-that-survives-failure/cover.webp)
결국 만든 것은 파일 전송 버튼이 아니라 실패를 견디는 흐름이었다. 처음에는 PonsLink 방 안에서 파일을 보내면 충분할 줄 알았다. 파일이 커지자 전송은 방의 부가기능이 아니라 별도 제품처럼 행동했다. 브라우저 메모리, 수신 속도, 저장 방식, ACK, 백프레셔가 한꺼번에 얽혔다. 보내는 일과 받는 일과 저장하는 일이 같은 속도로 진행되지 않으면 사용자는 전체 전송이 실패했다고 느꼈다.
![[PonsWarp] 결제 성공보다 권한 증명이 먼저였다](/tistory/body-images/2026-06-29-ponswarp-12-entitlement-before-payment/cover.webp)
PonsWarp에서 파일 전송을 별도 제품으로 분리한 뒤에도 결제와 입장 권한의 문제가 남았다. 결제 성공 화면을 보여 주는 것과 실제로 세션에 들어갈 권한을 증명하는 것은 같은 사건이 아니었다. 브라우저의 화면만 믿으면 권한이 확정되기 전에 입장을 열 수 있다. 결제 성공, 권한 부여, 세션 입장은 사용자에게는 한 번의 흐름처럼 보이지만 운영상 분리해야 했다. 결제 상태를 서버가 확인하고, 그 결과로 입장 권한을 발급해야 한다. 화면이 먼저 초록색으로 바뀌었다고 방을 열 수는 없다.
![[PonsWarp] Zero-Copy Pool은 빠른 전송보다 GC를 피하기 위한 선택이었다](/tistory/body-images/2026-06-29-ponswarp-11-zero-copy-pool/cover.webp)
Zero-Copy Pool을 고른 이유는 전송 속도를 더 내고 싶어서가 아니었다. 브라우저에서 큰 파일을 다룰 때 복사와 가비지 컬렉션이 전송의 마지막 구간을 망가뜨리는 일이 무서워졌기 때문이다. 처음에는 PonsLink 방 안에서 파일을 보내면 충분할 줄 알았다. 파일 크기가 커지면서 회의방의 리듬까지 흔들렸다. 파일을 보내는 일과 대화하는 일이 같은 메모리 압박을 공유하고 있었다.
![[PonsWarp] 속도를 올리기 전에 파이프라인의 한계를 먼저 정했다](/tistory/body-images/2026-06-29-ponswarp-10-pipeline-limits-before-speed/cover.webp)
아래에서는 PonsWarp을 중심으로, 깨진 가정과 다시 그은 경계를 순서대로 남긴다. 처음 기대와 실제 제약을 한 번 충돌시킨 뒤 선택 이유가 드러났다. 이 흐름은 브라우저끼리 파일을 직접 보내면서 부딪힌 전송 엔진의 벽을 따라간다. 속도를 올리기 전에 파이 처음에는 PonsLink 안에 있는 파일 전송 기능이었다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. 처음에는 쉽게 정리할 수 있는 문제처럼 보였다. 그런데 직접 만들고 운영해보니 문장 하나로 접히지 않는 갈등이 남았다.…
![[PonsWarp] 브라우저마다 파일 저장 방식이 다르게 깨졌다](/tistory/body-images/2026-06-29-ponswarp-09-browser-download-strategy/cover.webp)
처음에는 PonsLink 안에 있는 파일 전송 기능이었다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. 파일 전송은 처음엔 방 안의 작은 부가기능처럼 보였다. 그런데 파일이 커질수록 이 기능은 방 전체를 흔드는 별도 제품이 됐다. 다음 글에서는 OPFS, ZIP64, 메모리 경계 중 실제 저장 안정화 질문을 넘어간다. 특히 PonsWarp에서는 재현 조건을 같이 적어야 의미가 있었다. 브라우저마다 파일 저장 전송 프로토콜이 같아도 마지막 저장 UX는 브라우저마다 다르고, 그…
![[PonsWarp] 모바일 브라우저가 잠들면 전송은 어떻게 이어져야 하나](/tistory/body-images/2026-06-29-ponswarp-08-mobile-background-resume/cover.webp)
정성적으로는 사용자가 '거의 다 됐는데 왜 깨졌지'라는 불신을 덜 느끼게 하는 게 목표였다. 이때부터 PonsWarp의 기준은 최고 속도가 아니라 끝까지 살아남는 흐름이 됐다. 다음 글에서는 OPFS, ZIP64, 메모리 경계 중 실제 저장 안정화 질문을 넘어간다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. 처음에는 빨리 보내는 쪽이 이기는 줄 알았다. 그런데 속도를 올릴수록 받는 쪽 버퍼가 먼저 비명을 질렀다. 특히 PonsWarp에서는 재현 조건을 같이 적어야 의미가 있었다.
![[PonsWarp] 끊긴 전송에서 partial file을 남기지 않기로 했다](/tistory/body-images/2026-06-29-ponswarp-07-incomplete-transfer-recovery/cover.webp)
전송이 끊기면 처음부터 다시 보내는 게 정석처럼 느껴졌다. 상태도 적고 디버깅도 쉽고 실패하면 재시도라는 문장도 깔끔했다. 작은 파일에서는 그 전략이 실제로 잘 작동했다. 문제는 파일이 커지고 네트워크가 불안정해질 때 시작됐다. 대용량 앞에서 처음부터 다시는 단순한 재시도가 아니라 기다림의 몰수다. 사용자가 길게 쌓은 진행률이 0으로 돌아가면 제품은 기능 실패가 아니라 약속 파기로 읽힌다. 그래서 끊긴 전송에서 부분 파일을 어떻게 다룰지가 복구 품질의 중심이 됐다.
![[PonsWarp] Rust와 WASM은 속도 욕심보다 메모리 생존을 위한 선택이었다](/tistory/body-images/2026-06-29-ponswarp-06b-rust-wasm-memory-survival/cover.webp)
작은 파일에서는 멀쩡하던 전송이 큰 파일에서 브라우저 메모리에 그대로 쌓이던 발견이 먼저 떠오른다. 아래에서는 PonsWarp을 중심으로, 깨진 가정과 다시 그은 경계를 순서대로 남긴다. 그래서 PonsWarp는 별도 제품처럼 분리됐다. 서버가 파일을 들고 있지 않는다는 원칙을 유지하면서도 브라우저가 버틸 수 있는 흐름 제어와 저장 전략을 먼저 세워야 했다. 그래서 대용량 전송을 '더 빠르게'가 아니라 '덜 무너지게' 만드는 과정으로 다시 써야 한다. 특히 PonsWarp에서는 재현 조건을 같이 적어야 의미가 있었다. Rust와…
![[PonsWarp] Cloud Drop은 P2P의 대체재가 아니라 보완재다](/tistory/body-images/2026-06-29-ponswarp-06-cloud-drop-complement/cover.webp)
다음에 남는 질문은 한 문장으로 충분하다. 처음에는 PonsLink 안에 있는 파일 전송 기능이었다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. 처음에는 쉽게 정리할 수 있는 문제처럼 보였다. 그런데 직접 만들고 운영해보니 문장 하나로 접히지 않는 갈등이 남았다. 전송은 단순히 빨리 보내는 문제가 아니었다.
![[PonsWarp] OPFS는 만능키가 아니라 마지막 안전망이었다](/tistory/body-images/2026-06-29-ponswarp-05c-opfs-safety-net/cover.webp)
IndexedDB를 떠올렸다가 브라우저가 제공하는 파일 시스템 경계를 다시 살펴보게 된 흐름같은 단어를 쓰더라도 제품 문맥이 다르면 답이 갈렸다. 그 갈림길을 숨기지 않는 쪽으로 썼다. 정성적으로는 사용자가 '거의 다 됐는데 왜 깨졌지'라는 불신을 덜 느끼게 하는 게 목표였다. 처음에는 PonsLink 안에 있는 파일 전송 기능이었다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. 큰 파일이 브라우저 메모리에 그대로 쌓이는 걸 보고 나서 저장소를 다시 봤다. 처음에는…
![[PonsWarp] 2GB를 넘기자 브라우저 메모리가 먼저 무너졌다](/tistory/body-images/2026-06-29-ponswarp-05b-browser-memory-2gb/cover.webp)
작은 파일에서는 멀쩡하던 전송이 큰 파일에서 브라우저 메모리에 그대로 쌓이던 발견이 먼저 떠오른다. 그래서 대용량 전송을 '더 빠르게'가 아니라 '덜 무너지게' 만드는 과정으로 다시 써야 한다. 전송은 단순히 빨리 보내는 문제가 아니었다. 브라우저 메모리, 수신 속도, 저장 방식, ACK, 백프레셔가 한꺼번에 걸렸다. 특히 큰 파일에서는 마지막 몇 퍼센트에서 실패하는 순간이 제일 잔인했다. 전송 성공과 저장 성공은 같은 말이 아니었다. 2GB 근처에서 깨지는 문제는 속도 문제가 아니라 제품이 파일을 어디에 들고 있느냐의 문제였다.
![[PonsWarp] ZIP64가 필요해진 순간](/tistory/body-images/2026-06-29-ponswarp-05-zip64-streaming/cover.webp)
속도를 더 내고 싶어서가 아니라 브라우저 메모리와 큰 파일 경계가 무서워진 순간이었다. ZIP64만 있으면 나머지는 따라올 거라고 믿었다. 이 흐름은 큰 파일이 브라우저 메모리에서 무너지지 않게 저장 경계를 다시 잡는 과정이다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. Rust와 WASM은 멋있어서 붙인 장식이 아니었다. 브라우저 안에서 큰 파일을 다루다 보니 복사와 메모리 경계가 계속 무서워졌다.
![[PonsWarp] ACK 하나 때문에 전송이 멈추고 살아났다](/tistory/body-images/2026-06-29-ponswarp-04b-ack-backpressure-battle/cover.webp)
전송이 빠른데도 수신 쪽이 밀리면서 멈추는 장면이 있었다. 처음에는 더 빠르게 보내면 해결될 것 같았다. 실제로는 속도를 올릴수록 브라우저 버퍼가 먼저 한계에 닿았다. 큰 파일에서 마지막 몇 퍼센트가 깨지는 이유도 그곳에 있었다. ACK는 받았다는 표시만이 아니었다. 수신자가 다음 조각을 받을 준비가 됐다는 신호로 사용해야 했다. 보내는 쪽이 자기 속도만 믿고 계속 밀어 넣으면 전송은 빨라 보이지만 실제로는 더 빨리 깨진다.
![[PonsWarp] 백프레셔는 기다림이 아니라 보호 장치였다](/tistory/body-images/2026-06-29-ponswarp-04-backpressure-protects-transfer/cover.webp)
빠르게 보내는 것보다 채널이 받아줄 수 있는 만큼만 보내는 일이 안정적인 전송을 만들었다. 백프레셔를 제품 기준으로 삼은 이유를 적었다.
![[PonsWarp] WebRTC는 파일을 보내주지 않는다, 길만 열어준다](/tistory/body-images/2026-06-29-ponswarp-03-webrtc-opens-the-road/cover.webp)
WebRTC를 처음 붙잡을 때는 약간의 낙관브라우저끼리 직접 연결되고, data channel이 열리고, 그 위로 바이트를 보내면 파일 전송의 큰 문제가 해결될 것 같았다. 서버에 파일을 올리지 않아도 되고, 두 사람이 바 는 점이 매력적이었다. 그런데 PonsWarp를 정리하면서 더 분명해진 건 WebRTC가 파일을 보내주는 기술이 아니라 길을 열어주는 기술이라는 점이었다. 아래에서는 PonsWarp을 중심으로, 깨진 가정과 다시 그은 경계를 순서대로 남긴다.
![[PonsWarp] 데스크탑 앱까지 갔지만, 테스트할 기기가 없었다](/tistory/body-images/2026-06-29-ponswarp-02b-desktop-testing-fatigue/cover.webp)
처음 기대와 실제 제약을 한 번 충돌시킨 뒤 선택 이유가 드러났다. 데스크탑 앱까지 갔지만, 그래서 요청 흐름은 PonsLink가 회의 도구보다 연결 설계에 가까워진 이유를 보여준다. 정성적으로는 사용자가 '거의 다 됐는데 왜 깨졌지'라는 불신을 덜 느끼게 하는 게 목표였다. 전송은 단순히 빨리 보내는 문제가 아니었다. 브라우저 메모리, 수신 속도, 저장 방식, ACK, 백프레셔가 한꺼번에 걸렸다. 특히 큰 파일에서는 마지막 몇 퍼센트에서 실패하는 순간이 제일 잔인했다. 요청을 먼저 받는 일은 사람을 거르는 장치가 아니었다.…
![[PonsWarp] Direct와 Cloud Drop을 나눈 이유](/tistory/body-images/2026-06-29-ponswarp-02-direct-cloud-drop-modes/cover.webp)
처음에는 클라우드로 보낸다면 동작이 하나일 거라고 생각했다. 파일을 집어서 올리면 끝. 직접 전송이 어려울 때 쓰는 보조 레일 정도로 보였다. 실제로는 폴더인지 파일인지, 구조를 보존하는지, 이름 충돌을 어떻게 처리하는지, 실패 시 부분 결과를 남기는지에 따라 완전히 다른 제품이 된다. 모드를 안 가르면 사용자는 끝까지 올라갔나를 확신하지 못한다.
![[PonsWarp] TB급 전송을 꿈꾸자 데이터 그리드가 필요해졌다](/tistory/body-images/2026-06-29-ponswarp-01b-data-grid-tb-experiment/cover.webp)
처음에는 PonsLink 안에 있는 파일 전송 기능이었다. 방 안에서 파일을 보내면 충분할 줄 알았는데, 파일 크기가 커질수록 회의방의 리듬까지 흔들렸다. 파일 전송은 처음엔 방 안의 작은 부가기능처럼 보였다. 그런데 파일이 커질수록 이 기능은 방 전체를 흔드는 별도 제품이 됐다. 전송은 단순히 빨리 보내는 문제가 아니었다. 브라우저 메모리, 수신 속도, 저장 방식, ACK, 백프레셔가 한꺼번에 걸렸다.
![[PonsWarp] 브라우저끼리 대용량 파일을 직접 보내고 싶었다](/tistory/body-images/2026-06-29-ponswarp-01-browser-direct-transfer/cover.webp)
정성적으로는 사용자가 '거의 다 됐는데 왜 깨졌지'라는 불신을 덜 느끼게 하는 게 목표였다. 다음 글에서는 OPFS, ZIP64, 메모리 경계 중 실제 저장 안정화 질문을 넘어간다. 특히 PonsWarp에서는 재현 조건을 같이 적어야 의미가 있었다. 전송 프로토콜이 같아도 마지막 저장 UX는 브라우저마다 다르고, 그 차이가 “전송 성공” 판단을 흔들었다는 문제가 있었다. 브라우저끼리 대용량 파일 특히 PonsWarp에서는 재현 조건을 같이 적어야 의미가 있었다.
![[PonsWarp] 파일 전송은 PonsLink 안에서 먼저 고장났다](/tistory/body-images/2026-06-29-ponswarp-00-file-transfer-broke-in-ponslink/cover.webp)
파일 전송을 처음부터 별도 제품으로 생각한 건 아니었다. PonsLink 방에 사람이 붙어 있으니, 파일 하나 보내는 버튼 정도는 자연스러워 보였다. 붙여 보니 먼저 무너진 것은 버튼 위치가 아니라 **약속의 크기**였다. 사용자는 “파일 전송” 한 줄로 이해하지만, 제품은 연결·진행률·메모리·저장 완료·실패 후 재시도를 모두 설명해야 했다.
![[PonsLink] 회의방보다 먼저 요청과 운영의 혼란을 정리해야 했다](/tistory/body-images/2026-06-16-ponslink-11-ponswarp-split/cover.webp)
처음에는 회의방 하나면 충분할 줄 알았다. 링크를 만들고, 상대를 불러 말하고, 같은 화면에서 파일도 주고받으면 된다고 봤다. 실제 운영에서는 방이 열리기 훨씬 앞에서 제품이 무너졌다. 누가, 왜, 언제 만나려는지 정리되지 않은 채 DM과 문의가 흩어졌고, 정작 방에 들어가면 맥락이 비어 있었다.
![[PonsLink] 파일 전송은 결국 방 밖으로 독립해야 했다](/tistory/body-images/2026-06-16-ponslink-09b-file-transfer-left-room/cover.webp)
파일 전송은 결국 방 밖으로 독립해야 했다. 처음에는 방 안에 두는 게 자연스러워 보였다. 사람이 붙어 있는데 파일 하나쯤 같이 보내면 될 줄 알았다. 방 UI 안의 파일 전송은 금방 이런 질문에 부딪혔다. 지금 업로드 중인가 peer 전송 중인가, 브라우저를 닫으면 이어받을 수 있는가, 상대가 새로고침하면 진행률은 어디로 가나.