<PonsLink/>
작업
글
주제
시리즈
카테고리
연락
⌘K 검색
About
코드와 제품으로

코드와 제품으로

연결되는 세계

연결되는 세계

PonsLink는 만남 뒤 요청을 세션으로 잇고, PonsWarp는 서버 보관 없이 파일을 직접 전송합니다. 이 블로그는 기능 소개보다 설계 판단과 운영 증거를 남깁니다.

연락처

[email protected]github.com/DeclanJeon

운영

콘텐츠는 WordPress headless CMS, 공개 UI는 Next.js입니다.

초안 미리보기
© 2026 PonsLink Blog|Next.js · WordPress REST · Framer Motion
Local-first · Backup-before-deploy

[Blueprint W2] PonsWarp Bulk Plane — 헥사고날로 찢어 전송을 다시 설계한 이유

Declan·2026년 8월 28일

  • Blueprint
  • P2P/분산기술
  • 개발 회고

원본 ponswarp를 헥사고날로 찢었다. hybridBulkTransport 듀얼 플레인, SpeedSender 프레임, contracts 고정, crypto-plane 분리로 속도를 측정 가능하게 만든 리디자인.

# [Blueprint W2] PonsWarp Bulk Plane — 헥사고날로 찢어 전송을 다시 설계한 이유 > Series: `blueprint-ponswarp` (2/5) · Category: `P2P/분산기술` + `개발 회고` · Slug: `blueprint-ponswarp-02-bulk-plane-hexagonal` > Source: `blueprints/specs/P01_ponswarp_family.md` §02 + `blueprints/02-ponswarp-bulk-plane-vnext.png` + `ponswarp-bulk-plane-vnext/transfer/` — ## 0. TL;DR `ponswarp` 원본이 `SenderView → swarmManager → WASM` 일체형이었다면, `bulk-plane-vnext`는 `transfer/`를 헥사고날(Ports & Adapters)로 찢었다. `SpeedSender/SpeedReceiver` 프레임 프로토콜로 WebRTC와 LAN TCP를 듀얼 플레인으로 묶고, `contracts/protocol/v1/messages.json`에 클라 12종·서버 16종을 고정했다. `crypto-plane.worker`로 암호화를 분리하고, `verify` 체인 7단계로 wasm 빌드부터 벤치마크까지 증명한다. 속도를 위해 만든 게 아니라, 속도를 *측정 가능*하게 만들기 위해 리디자인했다.
PonsWarp Bulk Plane 설계도 02
— ## 1. 문제 정의 — 왜 원본을 찢어야 했나 원본 `ponswarp`는 프로덕션이 먼저였다. 10GB 전송은 되지만, 세 가지를 답할 수 없었다. 1. **어디서 느린가:** `swarmManager`와 `WASM`과 `directFileWriter`가 한 덩어리라, WebRTC가 느린지 WASM이 느린지 LAN이 가능한지 구분 불가. 2. **프로토콜이 흔들린다:** `PARTITION_ACK`에 `offset/runId` 추가, `PAUSE/RESUME` 추가할 때마다 클라/서버 양쪽을 동시에 고쳐야 했다. 계약 없이. 3. **암호화가 메인 스레드를 잡아먹는다:** AES-GCM을 메인 워커에서 돌리면 500MB 이상에서 프레임 드랍. `vnext`의 답은 “전송 코어를 도메인으로 격리하고, 계약을 먼저 고정한다” 다. — ## 2. 설계도 한눈에 — 헥사고날과 두 개의 플레인 중앙에 큰 `transfer` 코어 박스: `ports.ts → state.ts → events.ts → useCases.ts` (application). 그 바깥을 `adapters/`가 감싼다. * **위:** `hybridBulkTransport`가 두 화살표를 동시에 쏜다 — ① `WebRTC DataChannel` 플레인 ② `LAN TCP 보조` 플레인 (`lanTcpAssist.ts → Python TCP helper`). 어느 쪽이든 먼저 도착한 조각을 `SpeedReceiver`가 합친다. * **옆:** `cryptoPlaneClient + crypto-plane.worker` — 암호화 평면 분리. 메인 전송 워커는 바이트 이동만, 암호화는 별도 워커에서. * **아래:** `contracts/protocol/v1/messages.json` 점선 — 송수신 양단이 같은 파일을 참조. 클라 12종, 서버 16종, `CONTROL PAUSE/RESUME`, `PARTITION_ACK(offset/runId)`, `RESUME_REQUEST`, `DOWNLOAD_COMPLETE`. * **우측:** `verify` 파이프라인 화살표 — `wasm test → build → provenance → benchmark → type-check → frontend → backend cargo` 7단계. 기존 UI를 깨지 않기 위해 `adapters/senderCompatibilityFacade.ts`, `receiverCompatibilityFacade.ts`가 구 UI와 새 코어 사이를 중개한다. — ## 3. 아키텍처 해부 — 헥사고날 4층 “` transfer/ ├── ports.ts # 인터페이스: BulkTransport, CryptoPlane, EvidenceSink ├── state.ts # 순수 상태머신: pending → sending → paused → verifying → completed ├── events.ts # 도메인 이벤트: PieceAck, PartitionAck, ResumeRequested ├── useCases.ts # 유스케이스: startBulkSend, handleAck, pause, resume └── adapters/ ├── hybridBulkTransport.ts ├── lanTcpAssist.ts ├── cryptoPlaneClient.ts └── *CompatibilityFacade.ts “` * **Ports:** 전송을 `interface`로 정의. WebRTC든 LAN TCP든 같은 `sendPiece(piece)`를 구현하면 된다. 테스트에서 `FakeTransport`로 교체해 10Mbps 시나리오를 재현. * **State:** `useCases`가 `state`를 직접 mutate하지 않는다. `events`를 `reduce`해 다음 상태를 만든다. 이른바 Redux식 상태머신이지만, 의존성 없이 순수 함수. * **Adapters:** `hybridBulkTransport`는 두 transport를 `Promise.race`처럼 다룬다. WebRTC가 막히면 TCP가 메꾸고, TCP가 느리면 WebRTC가 앞선다. `networkAdaptiveController`가 RTT를 보고 비율을 조절한다. — ## 4. 로직 딥다이브 — 3개만 깊게 ### 4.1 SpeedSender/SpeedReceiver 프레임 프로토콜 (speedFrames) “` [Header: runId(16) | seq(32) | offset(64) | length(32) | flags(8)] [Payload: chunk] [Trailer: CRC32] “` * `runId`로 재전송 세션 구분 — 오래된 seq는 버린다. * `flags`에 `PAUSE/RESUME` 비트. 컨트롤 메시지도 같은 프레임으로 가서 순서 보장. * `CRC32`는 무결성 1차 필터. 최종은 Merkle 루트. ### 4.2 LAN TCP Assist 폴백 `lanTcpAssist.ts`는 같은 방(roomCode)에 있는 피어에게 `Python TCP helper`를 띄우라고 제안한다. `mDNS`로 `192.168.x.x:8787`을 찾으면, `WebRTC ICE` 실패 전에 TCP를 먼저 시도. 사무실·강의장처럼 같은 LAN에 있으면 WebRTC 시그널링만 하고 바이트는 TCP로 간다. `benchmarks/v1`에서 LAN은 WebRTC보다 1.8배 빨랐다. ### 4.3 컨트랙트 고정과 Compatibility Facade `contracts/protocol/v1/messages.json`이 진실. 예: “`json {“type”:”PARTITION_ACK”,”offset”:1048576,”runId”:”a3f1″,”crc”:”9e0a”} “` 클라/서버 양쪽이 `zod`로 검증. 필드 하나 추가하려면 JSON을 먼저 바꾸고, 양쪽 테스트가 깨지는 걸 확인한 뒤 코드를 고친다. `CompatibilityFacade`는 구 UI의 `onProgress(percent)` 호출을 새 코어의 `onPieceAck(seq, offset)`로 변환. UI를 안 고치고 코어만 갈아끼운 이유다. — ## 5. 왜 이 선택인가 — Rejected | 선택 | 채택 | 버린 대안 | 버린 이유 | |—|—|—|—| | 코어 구조 | 헥사고날 Ports & Adapters | 기존 일체형에 메서드 추가 | 테스트 불가, LAN TCP를 붙일 자리 없음 | | 전송 | 듀얼 플레인 (WebRTC+TCP) | WebRTC 단일 플레인 | LAN에서 TURN 경유 불필요, TCP가 더 빠름 | | 계약 | messages.json 고정 | 코드에 타입만 정의 | 클라/서버 드리프트, 조용한 필드 누락 | | 암호화 | crypto-plane.worker 분리 | 메인 워커에서 AES-GCM | 1GB 이상에서 메인 루프 블로킹 | — ## 6. 실패와 복구 * **실패 1:** 처음엔 LAN TCP를 `WebRTC DataChannel` 위에서 에뮬레이션하려 했다. 그러나 DataChannel의 `ordered:false`가 TCP의 순서 보장과 충돌. 결국 별도 플레인으로 분리. * **실패 2:** `verify` 체인 없이 `wasm-pack build`만 돌리다 `provenance` 해시가 달라져 배포 롤백. 이후 `wasm test → build → provenance → benchmark → type-check → cargo` 7단계를 `pnpm verify` 한 방으로 묶음. 하나라도 깨지면 머지 불가. — ## 7. 운영 증거 * `contracts/protocol/v1/messages.json` — 클라 12종·서버 16종, `CONTROL PAUSE/RESUME`, `PARTITION_ACK offset/runId`, `RESUME_REQUEST`, `DOWNLOAD_COMPLETE` * `transfer/` 4파일 + `adapters/` 4파일 — 의존성 방향: `adapters → ports`, 역방향 금지 * `benchmarks/v1` — LAN TCP 1.8배, `Playwright` 측정, `wasm-provenance` 해시 고정 — ## 8. 다음 편 예고 **W3: `ponswarp-lan-10mbps` — 10Mbps에서 증명한 것.** `networkAdaptiveController` 피드백 루프와 `lanEvidenceAdapter`가 10Mbps 제약을 어떻게 측정 가능한 증거로 바꿨는지, `scripts/lan-evidence.mjs`가 무엇을 재는지 다룬다. *부록: `blueprints/02-ponswarp-bulk-plane-vnext.png` 프롬프트 `prompts.part1.json#02`, 코드 진입점 `ponswarp-bulk-plane-vnext/transfer/useCases.ts`, `hybridBulkTransport.ts`*

Related

관련 운영 노트

같은 주제에서 이어서 읽기 좋은 글입니다.

  • [Blueprint L3] Pons 포트폴리오 — 의존성 0, 단일 index.html이 오래 가는 이유

    의존성 0, 단일 index.html 41KB. 디자인 토큰 SSOT와 13개 제품 쇼케이스로 만든 정적 포트폴리오가 오래 가는 이유.

  • [Blueprint L2] PonsLink Control Tower — Threads에 dry-run으로 쏘는 이유

    Python Threads 컨트롤타워. cardnews 재사용→생성 폴백, n8n 패치, dry-run 기본 게이트로 안전하게 쏘는 자동발행.

  • [Blueprint L1] PonsLink P2P Room — WebRTC 화상회의를 BFF로 감싼 이유

    React SPA와 Express BFF가 Socket.IO로 시그널링하고, 미디어는 mesh로 가는 2티어. TURN 그랜트와 presence lease로 룸을 만든 화상회의.

댓글

아직 댓글이 없습니다.

현재 이 글의 댓글은 닫혀 있습니다.