PonsWarp의 성능 개선 흐름을 따라가면 한동안 목표가 분명해 보입니다.
sender를 쉬지 않게 만들고, 파이프라인을 병렬화하고, 큐가 차면 멈추게 하고, RTT를 보고 전송량을 조절합니다. 표면만 보면 이 시기의 질문은 “어떻게 더 빠르게 보낼 것인가”였습니다. 하지만 실제 기록을 끝까지 읽으면 더 큰 질문은 “사용자가 받은 파일을 믿을 수 있는가”였습니다.
🧭 성능 개선은 왜 계속 필요했나
post-10의 6e635f3는 Push 방식과 AIMD 혼잡 제어를 붙였습니다. pull 요청을 기다리는 구조에서는 빠른 네트워크가 있어도 sender가 놀 수 있었기 때문입니다. post-14에서 다룬 949d921의 파이프라인 병렬화와 이중 버퍼링, 청크 풀링도 같은 방향이었습니다.
post-13의 541dd0c 멀티 채널 전략도 같은 압력에서 나왔습니다. 단일 DataChannel의 bufferedAmount에 모든 청크가 몰리면 한 통로의 막힘이 전체 리듬을 멈춥니다. 그래서 control 채널과 여러 data 채널로 압력을 나누고, getBestChannel()이 healthy 상태와 bufferedAmount를 보고 다음 통로를 고르게 했습니다.
이 변화들은 모두 합리적인 성능 실험이었습니다. PonsWarp는 대용량 파일을 브라우저에서 직접 보내려 했고, WebRTC DataChannel과 worker, WASM 코어를 함께 쓰는 구조였습니다. 이런 제품에서 “더 빠르게 밀어 넣기”를 시험하지 않는 쪽이 오히려 이상합니다.
⚠️ 속도 숫자가 신뢰를 대신하지 못한 지점
문제는 전송률 표시가 사용자 경험의 전부가 아니라는 점입니다. 파일 전송에서 사용자가 기억하는 실패는 “초당 몇 MB가 덜 나왔다”보다 “완료라고 떴는데 파일이 깨졌다”입니다.
증거도 이 방향으로 남아 있습니다. db175bf는 청크 순서 역전과 버퍼 관리 문제를 고쳤고, 8aff234는 중복 완료와 수신 완료 체크 로직을 고쳤습니다. 4650d02는 커밋 메시지 자체가 파일 깨짐 문제를 완전히 해결하고 대용량 전송 성능을 개선했다고 말합니다.
현재 코드에서도 이 판단은 더 분명합니다. src/utils/constants.ts는 MAX_BUFFERED_AMOUNT를 4MB bounded queue로 제한하고, BATCH_SIZE_MAX를 1로 묶습니다. 주석은 “DataChannel bufferedAmount는 sender 로컬 큐일 뿐 receiver 저장 완료를 의미하지 않는다”고 적습니다.
🧱 완료 판정은 어디에서 닫혀야 했나
초기에는 sender가 모든 청크를 내보내면 전송이 끝났다고 말하고 싶어집니다. 하지만 PonsWarp의 후속 구조는 그 판단을 receiver 쪽으로 계속 옮겼습니다.
webRTCService.ts의 receiver 흐름은 바이너리 데이터를 DirectFileWriter.writeChunk()에 넘긴 뒤, 쓰기 Promise가 끝나야 CONTROL ACK를 보냅니다. partition 단위에서는 PARTITION 메시지를 받으면 writer.waitForIdle()을 기다린 뒤 PARTITION_ACK를 보냅니다. 마지막 완료도 receiver writer의 onComplete(actualSize)에서 DOWNLOAD_COMPLETE 메시지로 sender에게 전달됩니다.
DirectFileWriter.finalize()는 더 엄격합니다. 잔여 버퍼를 flush하고, 재정렬 버퍼에 아직 기다리는 청크가 있으면 resume을 요청하거나 incomplete transfer로 중단합니다. strict size 모드에서 totalBytesWritten이 totalSize와 다르면 완료가 아니라 오류입니다.
더 많이 한꺼번에 보내고 싶어도, receiver writer가 idle되는 지점과 실제 저장 크기 검증을 기다려야 합니다. 이것이 없으면 완료 표시는 너무 쉽게 거짓말이 됩니다.
🔄 backpressure는 성능 기능이면서 신뢰 기능이었다
54cf5f4의 수신 측 backpressure와 현재 transferFlowControl.ts의 보수적 프로필은 단순히 속도를 낮추는 장치가 아닙니다. shouldRequestMoreChunks()는 전송 중이고 worker가 준비됐으며 active peer가 있고, paused peer와 pending ACK가 없고, 가장 높은 bufferedAmount가 high watermark 아래일 때만 더 요청합니다.
현재 기본 flow control profile은 더 노골적입니다. 주석은 “Conservative hotfix profile: one small chunk per pump”라고 설명합니다. chunkSize는 16KB, batchSize는 1, prefetchBufferSize는 0입니다. throughput을 희생하지만 실제 브라우저의 SCTP/DataChannel 큐를 과도하게 채우지 않기 위한 선택입니다.
📦 WASM 코어 분리도 같은 방향으로 이어졌다
post-22는 Season 2의 마지막 글이고, 다음 글부터는 Rust/WASM 코어 분리로 넘어갑니다. JS 레벨에서 Push, pipeline, backpressure를 아무리 다듬어도, 파일 무결성의 일부 책임은 더 낮은 계층으로 내려가야 했습니다.
현재 sender worker는 pons-core-wasm의 PacketEncoder, CryptoSession, Zip64Stream, ZeroCopyPacketPool을 사용합니다. receiver worker 역시 PacketDecoder, CryptoSession을 사용하고, fallback CRC32까지 둡니다.
이 흐름은 “JS가 느려서 Rust로 갔다” 정도로 쓰면 부족합니다. 더 정확히는 전송 제품의 핵심 계약이 속도 표시에서 패킷 구조, 체크섬, 암호화, 재정렬, ZIP64 같은 검증 가능한 단위로 내려간 것입니다.
🧭 지금 돌아보는 판단
PonsWarp가 성능을 포기한 것은 아닙니다. 오히려 성능을 끝까지 밀어붙였기 때문에, 성능만으로는 제품을 설명할 수 없다는 사실이 더 빨리 드러났습니다. Push와 AIMD는 필요했고, 파이프라인과 멀티 채널 실험도 자연스러웠고, RTT와 backpressure도 필요했습니다.
다만 마지막 기준은 달랐습니다. 전송률이 높아도 receiver가 저장을 끝내지 못하면 실패입니다. DataChannel 큐가 비어도 파일 크기가 맞지 않으면 실패입니다. 모든 청크를 보낸 것처럼 보여도 재정렬 버퍼에 구멍이 남아 있으면 실패입니다.
PonsWarp의 성능 최적화는 무결성을 이기기 위한 경쟁이 아니라, 무결성 검증을 통과한 상태에서만 의미가 있는 보조 조건이었습니다. 이 판단이 다음 시즌의 Rust/WASM 코어 분리, ZIP64, reordering buffer, Merkle tree, file signature 이야기로 넘어가는 다리입니다.
PonsWarp commit 6e635f3 — Push 방식과 AIMD 혼잡 제어를 도입해 sender 주도 전송으로 이동한 시작점PonsWarp commit 949d921 — Phase 2 파이프라인 병렬화, 이중 버퍼링, 청크 풀링 구현PonsWarp commit 541dd0c — Phase 3 멀티 채널 전략과 네트워크 적응형 제어 구현PonsWarp commit 54cf5f4 — 수신 측 backpressure 제어 구현PonsWarp commit 09161ce — RTT 기반 동적 혼잡 제어 알고리즘PonsWarp commit db175bf / 8aff234 / 4650d02 — 순서 역전, 완료 체크, 파일 깨짐 수정 맥락PonsWarp/src/utils/constants.ts:11-43 — 안정성 우선 청크 sizing, 4MB bounded queue, 단일-flight batchPonsWarp/src/utils/transferFlowControl.ts:12-54 — conservative hotfix profile과 추가 청크 요청 조건PonsWarp/src/services/webRTCService.ts:659-763 — write 완료 후 ACK, PARTITION_ACK, DOWNLOAD_COMPLETE 전송PonsWarp/src/services/directFileWriter.ts:1297-1490 — finalize 단계의 재정렬 버퍼, 크기 불일치, incomplete transfer 방어PonsWarp/src/workers/file-sender.worker.ts:5-19 / file-receiver.worker.ts:6-12, 38-55 — WASM packet verification, encryption, CRC32 경계'개발 회고' 카테고리의 다른 글
| [PonsWarp] - `pons-core-wasm`를 따로 만든 이유 (0) | 2026.06.24 |
|---|---|
| [PonsWarp] - JS로 버티다 Rust로 내려간 순간 (0) | 2026.06.24 |
| [PonsWarp] - partial file recovery를 왜 늦게 붙였나 (0) | 2026.06.24 |
| [PonsWarp] - 재정렬 버퍼를 둘러싼 고민 (0) | 2026.06.24 |
| [PonsWarp] - 파일 깨짐 문제를 끝까지 붙잡은 기록 (0) | 2026.06.24 |




