[PonsWarp Grid] 브라우저 그리드 엔진을 처음부터 다시 짜다
desktop이 네이티브 그리드로 가는 동안, 브라우저 쪽에는 별도의 엔진이 필요했다. ponswarp-grid 레포다. 데스크톱이 146개 커밋으로 무겁게 쌓이는 동안 이쪽은 29개 커밋으로 새로 시작했는데, "새로"라는 말이 과장이 아니다 — 기존 PonsWarp 웹 전송 코드를 이식한 게 아니라 그리드 엔진을 제로베이스로 다시 짰다. 기존 웹 전송 코드가 없던 것도 아니고, 동작하는 코드였다. 그런데도 버리고 시작했다. 이 편은 그 29개 커밋의 시작점을 읽고, 왜 이식이 아니라 재작성이었는지, 그리고 무엇을 코어로 남겼는지를 본다.
시작 전에 한 가지 짚을 것이 있다. 이 레포가 만드는 제품은 데스크톱 그리드의 축소판이 아니다. 브라우저는 자기만의 제약 — 소켓 부재, 메모리 상한, 다운로드 경로의 특수성 — 을 갖고 있고, 그 제약이 설계의 출발점이다. 같은 문제를 다른 환경에서 푸는 것이지, 같은 해법을 다른 환경에 이식하는 것이 아니다. 이 구분이 이후 모든 판단 — 패키지 구조, coordinator 표면, export 전략 — 을 결정한다.
왜 desktop과 분리했나
| 레포 | 실행 환경 | 전송 수송체 |
|---|---|---|
ponswarp-desktop | 네이티브/데스크톱 | QUIC |
ponswarp-grid | 브라우저/웹 | WebRTC |
브라우저는 QUIC 소켓을 열 수 없다. WebRTC DataChannel이 수송체다. 실행 환경이 다르면 수송체가 다르고, 수송체가 다르면 테스트·배포·패키징이 갈라진다. QUIC 경로의 회귀 테스트는 브라우저 업데이트와 무관하고, WebRTC 경로의 테스트는 브라우저 버전에 종속된다. 한 레포에 두 세계를 얹으면 이 두 가지 테스트 체계가 서로의 CI 시간을 잠식한다. 두 세계를 한 레포에 얹는 대신 엔진을 분리한 것은 이상이 아니라 빌드 현실의 판단이다.
여기에 사람의 리듬이라는 요소도 있다. 데스크톱 전송의 변경은 조심스럽고 느리게 리뷰된다 — GB 단위 데이터의 경로니까. 브라우저 쪽은 UI와 공유 흐름의 실험이 빠르게 반복된다. 한 레포에서 이 두 리듬이 충돌하면 보통 느리고 중요한 쪽이 진다. 브라우저 실험의 잡음이 데스크톱 전송 경로의 커밋 히스토리를 오염시키고, 중요한 전송 변경이 잡음에 묻힌다. 레포 분리는 결국 커밋 히스토리의 소음 분리다.
분리의 부작용도 예상했다. 두 엔진이 서로 다른 방식으로 같은 개념을 구현하면, "PonsWarp의 조각"이라는 개념 자체가 갈라진다. 이걸 막는 장치가 아래의 패키지 구조다.
분리를 고민할 때의 대안도 있었다. 데스크톱 레포 안에 브라우저 패키지를 두고 빌드 타깃만 나누는 방식이다. 이 방식은 코드 검색이 편하고 공용 유틸을 즉시 쓸 수 있다는 장점이 있지만, 데스크톱 릴리스 주기와 브라우저 배포 주기가 묶이는 문제를 해결하지 못한다. 브라우저 쪽은 핫픽스가 잦은 쪽이고, 데스크톱은 무겁고 신중한 쪽이다. 두 리듬이 다른 코드가 한 레포에 있으면, 빠른 쪽이 느린 쪽의 릴리스를 기다리거나 느린 쪽이 빠른 쪽의 잡음을 함께 실어 나르게 된다. 분리는 "어디에 두면 편하나"의 문제가 아니라 "어떤 리듬으로 흘러가나"의 문제였다.
첫 엔진 범위
| 커밋 | 내용 |
|---|---|
3fe5bd8 | Initial PonsWarp Grid engine |
2eac0f0 | Node CLI grid transfer |
43d25f4 | 웹 공유 흐름 + coordinator CLI |
세 커밋의 순서가 설계 우선순위를 말해준다. 첫 커밋이 엔진이고, 두 번째가 CLI고, 세 번째가 공유 흐름과 coordinator다. UI 어댑터는 세 커밋 어디에도 없다 — React 패키지는 그 뒤에 온다. "엔진 → CLI → 공유 흐름 → UI"의 순서는 데스크톱 편의 CLI-first 규율이 그대로 재현된 모습이다. 실행 가능한 최소 코어를 먼저 굴리고, 텍스트 계약으로 다룰 수 있게 하고, 그다음에 사람이 쓰는 표면을 입히는 것.
2eac0f0의 "Node CLI grid transfer"가 특히 중요하다. 브라우저 엔진 레포에 CLI가 들어간다는 건 역설처럼 보인다 — 브라우저 제품인데 CLI로 뭘 하나. 그러나 이 CLI의 존재가 "packages/core는 UI 독립적"이라는 설계를 강제하는 장치다. 코어를 CLI에서 돌릴 수 없다면 코어는 이미 브라우저 환경에 기생하고 있다는 뜻이고, 순수 도메인이라는 주장은 깨진 것이다. CLI는 코어의 환경 독립성을 검증하는 상시 테스트 역할을 한다.
패키지 구조는 README에 고정돼 있다.
textpackages/core UI 없는 manifest·piece·storage·scheduler·integrity·events packages/webrtc 브라우저 WebRTC 전송 래퍼 packages/signaling 배포 가능한 WebSocket 시그널링 서버 + 클라이언트 packages/react React 어댑터 apps/demo 로컬/루프백/시그널 두 브라우저 데모
핵심은 packages/core가 UI가 없는 순수 도메인이라는 점이다. 스케줄러와 무결성 로직이 React와 분리되어 있어, CLI와 브라우저가 같은 코어를 공유한다. CLI가 코어를 쓴다는 건 — 데스크톱 편에서 CLI-first 규율을 봤는데 — 브라우저 엔진에도 같은 규율이 적용됐다는 뜻이다. UI는 코어 위의 클라이언트일 뿐이고, 코어는 독립적으로 검증 가능해야 한다.
이 구조가 시험대에 오르는 순간은 두 엔진이 같은 파일을 다뤄야 할 때다. 데스크톱이 만든 release의 manifest를 브라우저 그리드가 받는 상황에서, manifest 스키마와 piece 정의가 양쪽에서 다르면 전송 자체가 불가능하다. 코어가 "UI 없는 도메인"으로 유지되는 이유는 검증 용이성만이 아니라 두 레포 간의 의미 호환을 지키는 마지막 방어선이기 때문이다.
코어의 구성요소 목록(manifest·piece·storage·scheduler·integrity·events)에서 알 수 있는 것도 있다. piece와 scheduler가 있으니 이 엔진은 처음부터 그리드(조각 분산 수신)를 전제로 태어났고, integrity가 코어에 있으니 검증은 코어의 책임이지 UI의 책임이 아니며, events가 코어에 있으니 상태 전이는 데스크톱 QUIC 편의 9253ddb 교훈과 같은 이벤트 주도 규율을 따른다. 패키지 나열은 구성요소 목록이 아니라 설계 결정의 요약이다.
apps/demo가 하는 일
패키지 목록의 마지막, apps/demo가 하는 역할도 짚고 넘어가자. 로컬/루프백/시그널 세 가지 데모 모드는 "코어가 실제로 브라우저에서 동작하는가"의 상시 검증대다. 데모를 데모로만 보면 낭비처럼 보이지만, 이 세 모드는 사실 테스트 환경의 3단계다. 로컬은 네트워크 없음, 루프백은 같은 머신 통신, 시그널은 두 브라우저 간 실연결. 코어를 고칠 때마다 이 세 단계를 거치면 회귀가 어느 층에서 터졌는지 즉시 좁혀진다. apps/demo가 패키지 목록 끝에 있지만 실제로는 개발 루프의 중심이다.
제로베이스의 대가
기존 웹 전송 코드를 이식하지 않고 다시 짠 이유를 좁혀 보자. 기존 코드는 단일 피어 전송 중심으로 설계됐다. 그리드는 여기에 조각 지도, multi-peer 스케줄, coordinator 소개라는 다른 뼈대가 필요하다. 기존 코드 위에 그리드를 얹으면 단일 전송의 가정들이 곳곳에 남아, 나중에 "왜 이 경로는 그리드로 안 가지"라는 부류의 버그가 되어 돌아온다. 제로베이스의 대가는 검증된 코드를 버리는 비용이고, 제로베이스의 수익은 가정이 섞이지 않은 뼈대다. 짧은 레포(29커밋)를 감수하고 후자를 택했다.
이 판단에는 "다시 짜는 데 얼마나 걸릴까"의 계산도 깔려 있다. 이식은 겉보기에 싸 보인다 — 코드가 이미 있으니까. 하지만 이식 비용의 본체는 코드 이동이 아니라 가정 제거다. 단일 피어 가정이 박힌 코드에서 조각 지도와 multi-peer 스케줄을 분리해내는 작업은, 사실상 코어 부분의 재작성과 비슷한 공수가 들면서 구 코드의 복잡성까지 떠안는다. 재작성은 처음부터 다르게 쓸 수 있는 자유가 있는 작업이다. 뼈대가 바뀔 때는 이식보다 재작성이 총비용이 낮은 경우가 많다 — 29커밋이라는 짧은 역사가 그 계산의 결과다.
물론 전부 버린 건 아니다. 개념 어휘 — manifest, piece, 무결성 검증, 재개 — 는 데스크톱 쪽과 공유된다. 버려진 것은 코드이고, 공유된 것은 개념이다. 코드 공유의 실패(QUIC 편의 WASM 교훈)를 뒤집어 개념 공유로 착지시킨 사례가 이 레포 구조다.
남은 구멍
브라우저 엔진은 "시그널 데모" 수준을 넘어 공개 프로덕션(grid.ponslink.com)으로 가는 길에 TLS 전송·React adapter·WebRTC bridge·CI를 한 번에 붙인 커밋(4327bcf)이 있다. 여러 관심사가 한 커밋에 몰린 것은 이상적이지 않지만, 공개화라는 전환점에서 데모의 임시 부품들이 한꺼번에 프로덕션 부품으로 교체된 흔적이기도 하다. 이 부품들은 다음 편에서 coordinator와 함께 본다.
4327bcf의 네 부품을 나눠 보면 브라우저 그리드가 데스크톱과 다른 이유가 또 드러난다. TLS 전송 — 브라우저는 자기가 만든 소켓에 TLS를 씌울 수 없으니 전송 경로가 플랫폼의 TLS 정책을 따른다. WebRTC bridge — DataChannel은 자체 연결 성립 절차(시그널링, ICE)를 요구하니 브리지가 필요하다. CI — 브라우저 환경 테스트는 헤드리스 브라우저라는 별도 인프라를 요구한다. 네 부품 모두 "브라우저라서" 필요한 부품이다. 데스크톱 레포에서 같은 이름의 부품을 찾을 수 없는 이유다.