개발 회고

[PonsWarp] - 파일 깨짐 문제를 끝까지 붙잡은 기록

AC 2026. 6. 24. 00:23

PonsWarp에서 파일 깨짐 문제는 한 번 고치고 끝나는 종류의 버그가 아니었습니다.

저장 완료 핸드셰이크를 붙였고, 수신부 버퍼 오류를 고쳤고, ZIP 크기 의미론을 다시 봤고, reordering buffer와 checksum 검증을 더했습니다. 그래도 이 흐름은 “버그 몇 개를 잡았다”보다 “제품이 완료라고 말할 자격을 다시 배웠다”에 가깝습니다.

2025년 11월 21일의 21dc9ba는 파일 전송 완료 후 수신자 저장까지 대기하는 양방향 핸드셰이크를 붙였습니다. 그 다음 날 db175bf8aff234는 WebRTC 데이터 수신부 버퍼 처리 오류로 인한 파일 손상 문제를 고쳤습니다. 11월 29일 4650d02파일 깨짐 문제 완전 해결 및 대용량 전송 성능 대폭 개선이라는 더 강한 제목으로 ZIP 크기 불일치, 순차적 쓰기, ReorderingBuffer 통합, 부분 전송 방지를 함께 다뤘습니다.

PonsWarp corruption surface map
파일 깨짐은 sender, DataChannel, receiver, storage 사이의 여러 신뢰 경계가 함께 흔들릴 때 드러났습니다.

🧨 깨짐은 어떻게 드러났나

💡 중요한 것은 특정 커밋 하나의 승리 선언이 아닙니다. PonsWarp가 파일 깨짐을 화면 표시 오류가 아니라 신뢰 계약 위반으로 보았고, 그 계약을 지키기 위해 전송, 검증, 저장, UI 완료 상태를 모두 다시 연결했다는 점입니다.

파일 깨짐은 대개 화면에서 먼저 보이지 않습니다. 진행률은 100%처럼 보일 수 있고, sender는 모든 청크를 보냈다고 생각할 수 있습니다. 하지만 사용자가 받은 ZIP을 열거나, 이미지·문서 파일을 실제로 확인하는 순간 문제가 드러납니다. 버튼 색이 틀리거나 문구가 어색한 문제와 달리, 깨진 파일은 성공처럼 지나간 뒤 나중에 사용자의 작업물 안에서 터집니다.

PonsWarp의 기록에서 이 문제는 여러 형태로 남아 있습니다. db175bf8aff234는 수신부 버퍼 처리 오류를 직접 가리킵니다. 여기에는 Uint8Array view의 byteOffset, packet size 검증, header parsing 오류 차단 같은 경계가 얽혀 있었습니다. 데이터가 도착했다는 사실만으로는 그 데이터가 올바른 payload라고 말할 수 없었습니다.

4650d02 쪽에서는 더 제품적인 형태로 문제가 커집니다. 원본 파일 크기의 합계와 실제 ZIP byte length가 다르면, 진행률 계산과 저장 종료 판단이 틀어질 수 있습니다. 압축률이 좋으면 뒤가 null byte로 채워질 수 있고, 압축률이 나쁘면 central directory가 잘릴 수 있습니다. ZIP은 끝부분의 central directory가 살아 있어야 열리는 형식이기 때문에, 이것은 작은 표시 오차가 아니라 파일 형식 자체를 깨뜨리는 문제였습니다.

🔍 왜 신뢰 문제였나

파일 전송 제품에서 사용자가 믿는 문장은 단순합니다. “받았다”입니다. 하지만 내부에서 이 문장은 sender, receiver worker, 저장 계층, 완료 ACK가 모두 같은 결론에 도달해야만 성립합니다.

파일 깨짐은 cosmetic bug가 아닙니다. 사용자가 성공 화면을 보고 창을 닫았는데 실제 파일이 열리지 않는다면, 다음 전송에서 아무리 속도가 빨라도 제품은 이미 한 번 거짓말을 한 상태가 됩니다. PonsWarp가 저장 완료 handshake, checksum, reordering buffer, size-estimated manifest, partial recovery 같은 이름을 계속 붙인 이유는 이 거짓말을 줄이기 위해서였습니다.

전송 서비스의 완료 표시는 UI 장식이 아니라 계약입니다. 제품이 끝났다고 말한 순간, 사용자는 받은 파일을 업무나 기록의 일부로 사용하기 시작합니다.
PonsWarp verification path
완료 표시 전에 packet length, checksum, reordering, flush, save-complete ACK가 차례로 통과해야 했습니다.

🛡️ 증거와 검증 경로는 어디에 추가됐나

첫 번째 경로는 packet 자체입니다. 현재 constants.ts의 header 설명은 FileIndex, ChunkIndex, Offset, DataLen, Checksum을 합쳐 HEADER_SIZE = 22로 둡니다. pons-core-wasm/src/packet.rsPacketEncoder는 payload의 CRC32를 header에 기록하고, PacketDecoder::verify는 길이와 checksum을 다시 계산합니다. 브라우저 receiver worker도 packet length가 HEADER_SIZE + size와 맞지 않으면 corrupt packet으로 보고, WASM 검증 또는 CRC32 fallback을 사용합니다.

두 번째 경로는 순서입니다. src/services/reorderingBuffer.ts는 out-of-order로 도착한 chunk를 offset 기준으로 다시 내보내기 위한 장치입니다. StreamSaver처럼 순차 쓰기만 지원하는 writer에서 파일 손상을 막는다는 목적이 주석에 남아 있습니다. 이 장치는 멀티 채널이나 배치 전송처럼 도착 순서가 흔들릴 수 있는 설계에서 특히 중요합니다.

세 번째 경로는 저장 완료입니다. src/services/directFileWriter.ts는 단순 저장 헬퍼가 아니라 batch buffer, flush, pending bytes, writer mode, backpressure, reordering buffer를 함께 다루는 계층입니다. writeChunk() 호출이 Promise를 반환한다는 사실만으로는 충분하지 않았고, 순차적 쓰기와 flush 완료를 제품 상태에 반영해야 했습니다.

네 번째 경로는 완료 신호입니다. 21dc9ba의 save-complete handshake는 sender가 다 보냈다는 사건과 receiver가 저장을 끝냈다는 사건을 분리했습니다. 이 분리가 없으면 sender UI는 빠르게 성공을 말할 수 있지만, 제품 계약은 약해집니다.

⚙️ 수정은 제품 계약을 어떻게 바꿨나

처음의 계약은 쉽게 “파일을 보낸다”로 보입니다. 하지만 파일 깨짐을 겪은 뒤의 계약은 더 보수적입니다. 보냈다가 아니라 검증했고, 순서대로 썼고, 저장 완료가 확인됐다가 됩니다. 이 변화는 사용자를 더 기다리게 만들 수 있지만, 파일 전송 제품에서는 필요한 지연입니다.

4650d02의 의미도 여기서 읽어야 합니다. 커밋 제목의 “완전 해결”만 보면 끝난 문제처럼 보이지만, 실제로는 이후 backpressure, RTT 기반 제어, WASM reordering buffer, partial recovery로 이어지는 입구였습니다. 즉 한 번의 마침표라기보다, 제품이 앞으로 어떤 조건을 만족해야 완료라고 말할지 정한 기준선에 가깝습니다.

이 기준은 2026년 5월의 b185563 Make P2P transfers recover instead of saving partial files까지 이어집니다. 부분 파일을 성공처럼 남기는 대신, 중단된 전송을 recover 가능한 실패로 다루는 쪽입니다. 이건 사용자에게 더 정직합니다. 성공처럼 보이는 깨진 결과보다, 실패라고 말하고 복구 경로를 주는 쪽이 신뢰를 덜 망가뜨립니다.

PonsWarp integrity contract timeline
제품 계약은 빠른 완료 감각보다 검증된 저장 완료와 복구 가능한 실패를 우선하는 방향으로 바뀌었습니다.

📦 네 층이 같이 움직여야 했다

PonsWarp의 UI는 완료 상태와 오류 상태를 사용자에게 보여 줍니다. Browser transfer engine은 sender/receiver worker, packet decode, WebRTC DataChannel을 다룹니다. Storage/recovery 층은 DirectFileWriter, StreamSaver, File System Access API, reordering buffer, partial recovery를 맡습니다. Core 층은 packet, CRC32, Merkle Tree, file signature 같은 검증 언어를 담당합니다.

파일 깨짐 문제는 이 네 층 중 하나만 고쳐서는 끝나지 않았습니다. UI가 완료를 늦춰야 했고, worker가 packet을 의심해야 했고, writer가 순서와 flush를 보장해야 했고, core가 checksum과 proof 같은 검증 도구를 제공해야 했습니다. 깨짐은 한 계층의 실수가 아니라 계층 사이의 책임 누락으로 드러났기 때문입니다.

🧭 지금 돌아보는 판단

PonsWarp가 파일 깨짐 문제를 다룬 방식에는 과장도 있었습니다. “완전 해결”이라는 표현은 이후의 후속 작업을 생각하면 조심해서 읽어야 합니다. 하지만 방향은 맞았습니다. 빠른 전송, 큰 파일, 멀티 채널, 배치 쓰기 같은 성능 언어가 커질수록 무결성과 저장 완료의 언어도 같이 커져야 했습니다.

가장 좋은 수정은 사용자가 알아채지 못하는 수정입니다. 사용자는 CRC32나 reorderingBufferisSizeEstimated를 몰라도 됩니다. 대신 받은 파일이 열리고, 내용이 맞고, 앱이 끝났다고 말한 순간 정말 끝났다는 사실을 믿을 수 있어야 합니다. PonsWarp의 파일 깨짐 대응은 이 당연한 문장을 만들기 위해 내부 계약을 계속 보수적으로 바꾼 기록입니다.

📚 읽은 코드
PonsWarp commit 21dc9ba — 파일 전송 완료 후 수신자 저장까지 대기하는 양방향 핸드셰이크 구현
PonsWarp commit db175bf — 파일 전송 중 청크 순서 역전 및 버퍼 관리 문제 해결
PonsWarp commit 8aff234 — 파일 전송 중복 완료 및 수신 완료 체크 로직 개선
PonsWarp commit 4650d02 — 파일 깨짐 문제 완전 해결 및 ZIP 크기, 순차 쓰기, ReorderingBuffer 통합 흐름
PonsWarp commit b185563 — partial file을 성공처럼 저장하지 않고 recover 가능한 전송으로 다루는 후속 판단
PonsWarp/src/workers/file-receiver.worker.ts — packet length 검증, WASM verification, CRC32 fallback 처리
PonsWarp/src/services/directFileWriter.ts — batch write, flush, pending bytes, reordering buffer, size-estimated manifest 처리
PonsWarp/src/services/reorderingBuffer.ts — out-of-order chunk를 순서대로 내보내 파일 손상을 막는 저장 전 경계
pons-core-wasm/src/packet.rs — PacketEncoder CRC32 기록과 PacketDecoder 검증
pons-core-wasm/src/merkle_tree.rs — 대용량 파일 부분 무결성 검증으로 이어지는 후속 신뢰 경계
LIST