PonsWarp는 겉으로 보면 브라우저 앱입니다.
사용자는 링크를 열고, 파일을 고르고, 상대가 들어오면 전송을 시작합니다. 화면은 React가 그리고, 연결은 WebRTC DataChannel이 담당하고, 저장은 브라우저의 File System Access API와 worker 흐름을 탑니다. 그러면 자연스럽게 질문이 생깁니다. “브라우저 앱인데 왜 코어가 Rust인가?”
짧은 답은 성능입니다. 하지만 그것만으로는 부족합니다. PonsWarp가 Rust/WASM 코어로 옮긴 것은 단순히 빠른 반복문이 아니라, 파일 전송 제품에서 거짓말하면 안 되는 바이트 단위의 계약이었습니다.
🧭 브라우저에 남아야 했던 것
PonsWarp가 Rust 코어를 도입했다고 해서 브라우저 앱의 책임이 사라진 것은 아닙니다. 오히려 사용자가 만지는 대부분의 흐름은 계속 브라우저 쪽에 남아야 했습니다.
PonsWarp/src/App.tsx, SenderView.tsx, ReceiverView.tsx, CloudSenderView.tsx 같은 프론트 파일은 제품의 상태와 선택지를 보여주는 경계입니다. 사용자는 Rust 코어를 직접 호출하지 않습니다. 파일을 고르고, 상대 연결을 기다리고, 전송률과 진행률을 보고, 저장 위치를 선택하고, 실패를 이해합니다.
전송 제어도 전부 코어로 내려갈 수 없습니다. webRTCService.ts의 receiver 흐름은 DataChannel 메시지를 받고, DirectFileWriter.writeChunk()가 끝난 뒤 ACK를 보내고, partition 단위에서는 writer.waitForIdle() 이후 PARTITION_ACK를 보냅니다. 마지막 완료도 receiver writer의 onComplete(actualSize)에서 DOWNLOAD_COMPLETE로 sender에게 전달됩니다.
또 transferFlowControl.ts와 constants.ts의 보수적 프로필도 브라우저 현실을 반영합니다. MAX_BUFFERED_AMOUNT는 4MB bounded queue로 제한되고, BATCH_SIZE_MAX는 1입니다. 기본 flow control profile은 16KB chunk, batch 1, prefetch 0을 택합니다.
🧱 코어로 내려간 것
반대로 어떤 책임은 JS 화면 상태보다 더 낮은 곳에 있어야 했습니다. PonsWarp의 핵심 질문은 결국 “보냈는가”보다 “받은 파일을 믿을 수 있는가”였습니다. 이 질문에 답하려면 패킷 구조와 파일 단위 검증이 흔들리면 안 됩니다.
현재 sender worker는 pons-core-wasm의 PacketEncoder, CryptoSession, Zip64Stream, ZeroCopyPacketPool을 사용합니다. receiver worker 역시 PacketDecoder, CryptoSession을 사용하고, CRC32 fallback 경계도 둡니다. wasmCore.ts는 CRC32, packet encode/decode, encrypted packet header, key confirmation, reordering buffer, zero-copy pool을 노출하는 경계로 설명됩니다.
여기서 중요한 점은 각 기능이 따로 멋있어서가 아닙니다. packet encoding은 sender와 receiver가 같은 구조를 이해해야 의미가 있습니다. encryption은 key confirmation과 header 처리가 엇갈리면 실패를 숨길 수 있습니다. ZIP64는 4GB 이상 파일을 다룰 때 크기와 스트림 경계가 틀어지지 않아야 합니다.
그래서 15aef19의 pons-core-wasm 초기 설정은 단순한 새 패키지 생성 이상의 의미가 있습니다. 이어지는 1b6fb15의 ZIP64 스트리밍 압축, 7b0301d의 sender ZIP64 WASM 마이그레이션, 58e6b89의 수신측 패킷 재정렬 버퍼 WASM 마이그레이션, e589cf2의 압축·Merkle Tree·파일 서명 기능 추가, 7fe0ca1의 암호화 지원 Zero-Copy Pool은 모두 같은 방향입니다.
⚖️ 성능 결정이면서 제품 결정이었다
Rust/WASM 경계를 성능 이야기로만 쓰면 절반만 맞습니다. CRC32, packet encode/decode, ZIP64, zero-copy pool은 분명 계산 비용과 메모리 이동 비용에 민감합니다. worker 안에서 반복되는 바이트 처리를 코어로 묶으면 성능과 예측 가능성에서 이점이 있습니다.
하지만 PonsWarp에서 더 중요한 효과는 책임의 위치가 선명해졌다는 점입니다. UI는 사용자가 이해할 수 있는 상태를 보여주고, WebRTC/worker는 브라우저 연결과 저장 흐름을 조율하고, Rust/WASM 코어는 바이트가 어떤 구조로 묶이고 어떤 조건에서 유효한지를 맡습니다.
🔄 JS와 Rust를 나누는 기준
좋은 경계는 “가능한 모든 것을 Rust로 옮기자”가 아닙니다. PonsWarp의 증거는 더 보수적인 기준을 보여줍니다.
첫째, 브라우저 권한과 사용자 피드백은 JS에 남습니다. 파일 선택, 저장 권한, QR/link 기반 진입, 진행률, 오류 메시지, WebRTC 연결 상태는 제품 표면입니다. 이 영역을 코어로 숨기면 사용자 경험을 설명하기 어려워집니다.
둘째, 바이트 불변식은 코어로 내려갑니다. packet header, encode/decode, CRC32, encryption session, ZIP64 stream, reordering buffer, zero-copy pool은 화면 상태와 분리되어야 합니다. 같은 입력에 같은 규칙을 적용해야 하고, sender와 receiver가 같은 계약을 공유해야 하기 때문입니다.
셋째, 경계의 양쪽을 잇는 worker가 중요합니다. sender worker와 receiver worker는 UI와 코어 사이의 번역 계층입니다. 이 계층이 있으므로 UI는 제품 상태를 말할 수 있고, 코어는 파일 구조와 검증 규칙에 집중할 수 있습니다.
🚫 Rust가 모든 문제를 해결한다는 말은 아니다
Rust/WASM이 들어갔다고 해서 네트워크가 안정되는 것은 아닙니다. DataChannel의 bufferedAmount는 여전히 sender 로컬 큐일 뿐 receiver 저장 완료가 아닙니다. 그래서 backpressure, ACK, writer idle, actualSize 검증은 계속 필요합니다.
또 WASM 코어가 있다고 해서 UI의 실패 설명이 자동으로 좋아지는 것도 아닙니다. 사용자는 PacketDecoder가 어떤 오류를 냈는지보다, 지금 다시 시도해야 하는지, partial file을 믿으면 안 되는지, 저장 권한을 다시 줘야 하는지를 알아야 합니다.
JS는 제품 표면을 맡고, Rust/WASM은 검증 가능한 바이트 규칙을 맡고, WebRTC/worker/DirectFileWriter는 그 사이의 실제 흐름을 닫습니다.
🧭 지금 돌아보는 판단
PonsWarp가 브라우저 앱인데 Rust 코어를 둔 이유는 결국 “보이는 앱”과 “믿을 수 있는 파일”이 같은 계층의 문제가 아니었기 때문입니다. 화면은 사용자의 행동을 따라가야 하고, 연결은 브라우저 네트워크 현실을 견뎌야 하고, 파일의 바이트 구조는 흔들리지 않는 계약으로 남아야 합니다.
따라서 이 결정은 성능 최적화이면서 동시에 제품 신뢰 설계였습니다. 빠른 JS를 더 짜는 것만으로는 패킷, 체크섬, 암호화, ZIP64, 재정렬, 서명 같은 조건을 일관된 제품 계약으로 묶기 어렵습니다. Rust/WASM 코어는 그 계약을 담는 장소였습니다.
브라우저 앱이라는 말은 모든 책임이 UI 코드에 있어야 한다는 뜻이 아닙니다. PonsWarp의 경우 브라우저는 사용자가 만나는 제품 표면이고, Rust/WASM은 그 표면이 거짓말하지 않도록 받치는 코어였습니다.
PonsWarp/src/App.tsx, SenderView.tsx, ReceiverView.tsx, CloudSenderView.tsx — 사용자 상태, 송수신 화면, 제품 표면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/utils/constants.ts:11-43 / transferFlowControl.ts:12-54 — bounded queue, single-flight batch, conservative profilePonsWarp/src/workers/file-sender.worker.ts:5-19 / file-receiver.worker.ts:6-12, 38-55 — WASM packet, crypto, ZIP64, CRC32 경계PonsWarp/src/wasmCore.ts — CRC32, packet encode/decode, encrypted packet header, key confirmation, reordering buffer, zero-copy pool 노출 경계pons-core-wasm commits 15aef19, 1b6fb15, e589cf2, 7fe0ca1 / PonsWarp commits 7b0301d, 58e6b89 — WASM 코어 분리와 ZIP64, 재정렬, 서명, zero-copy 흐름'개발 회고' 카테고리의 다른 글
| [PonsWarp] - `KronDelivery`와 `filetransfer`에서 이미 드러난 집착 (0) | 2026.06.24 |
|---|---|
| [PonsWarp] - 왜 나는 계속 파일 전송 프로젝트를 만들었나 (0) | 2026.06.24 |
| [PonsWarp] - 암호화와 성능 최적화가 충돌하던 지점 (0) | 2026.06.24 |
| [PonsWarp] - reordering buffer를 WASM으로 옮긴 이유 (2) | 2026.06.24 |
| [PonsWarp] - Merkle tree와 file signature를 왜 넣었나 (0) | 2026.06.24 |




