STILLCODING

STILL CODING / NOTES

서버는 방만 만든다 — CollaBoard의 WebRTC P2P 협업 구조

JH Kim

CollaBoard는 화이트보드, 브레인스토밍, Q&A, 퀴즈, 투표, 파일 공유, 공지, 피드백의 8가지 도구를 하나의 비밀 방에서 쓰는 협업 앱입니다. 설계 문장은 하나였습니다. “방은 서버가 만들지만, 자료는 서버가 갖지 않는다.”

회의 메모나 워크숍 답변은 생각보다 민감합니다. 그래서 자료를 서버 데이터베이스에 쌓지 않고, 참가자의 브라우저끼리 직접 주고받는 구조를 택했습니다. 이 글은 그 구조를 어떻게 나눴는지 정리합니다.

1. 서버가 하는 일과 하지 않는 일

WebRTC로 두 브라우저를 연결하려면, 연결 전에 서로의 주소 후보(ICE candidate)와 연결 조건(SDP)을 교환해야 합니다. 이 교환을 시그널링이라고 하며, 여기에는 어쩔 수 없이 서버가 필요합니다.

CollaBoard의 서버는 Cloudflare Worker와 Durable Objects로 만들었습니다. 방 하나가 Durable Object 인스턴스 하나에 대응하고, 그 인스턴스가 다음만 맡습니다.

화이트보드의 선, 아이디어 카드, 퀴즈 문제, 파일 조각 같은 실제 협업 내용은 RTCDataChannel로 참가자 사이에서만 오갑니다. DataChannel은 DTLS로 암호화되므로 중간에서 내용을 볼 수 없습니다. 아직 DataChannel이 열리지 않은 상대에게 보낼 내용은 서버로 우회시키지 않고, 기기 안의 대기열(outbox)에 두었다가 연결되면 보냅니다. 서버를 거치는 것은 방장 권한 요청처럼 16KB 이하의 작은 제어 메시지뿐입니다.

2. 누가 먼저 연결을 거는가

여러 사람이 동시에 들어오면 두 사람이 서로에게 동시에 연결 제안(offer)을 보내는 글레어(glare) 가 생깁니다. 이를 피하려고 규칙을 하나 정했습니다. 두 참가자 중 세션 ID가 큰 쪽이 연결을 시작합니다. 모든 참가자가 같은 규칙을 따르므로, 한 쌍 사이에는 항상 한 쪽만 제안을 보냅니다.

const shouldInitiate = topology === "star" ? state.session.role === "host" : myId > peerId;

NAT 뒤에 있는 기기들이 서로를 찾도록 공개 STUN 서버를 기본으로 쓰고, 회사 방화벽처럼 직접 연결이 막힌 환경에서는 선택적으로 TURN 서버를 거칩니다. TURN은 암호화된 패킷을 중계만 하므로 내용은 여전히 참가자끼리만 볼 수 있습니다.

3. 메시와 스타, 두 가지 토폴로지

구분메시(Full Mesh)스타(Star)
연결모든 참가자가 서로 연결모든 참가자가 방장에게만 연결
연결 수(n명)n(n−1)/2n−1
적합한 상황2–8명 팀, 화이트보드·파일 공유10명 이상 워크숍, Q&A·투표·브레인스토밍
장점방장이 나가도 계속 동작, 지연이 짧음참가자 기기 부담이 적음

8명이 메시로 연결하면 연결 28개, 각자 7개입니다. 50명이면 1,225개가 되어 휴대폰으로는 감당할 수 없습니다. 그래서 대규모 워크숍용 앱은 방장을 허브로 하는 스타 구조로 전환합니다. 스타에서는 방장만 연결을 시작하고, 참가자가 보낸 메시지는 방장이 필요한 사람에게 다시 전달합니다. 앱을 바꿀 때 토폴로지도 함께 바뀌도록, “앱 변경” 메시지에 토폴로지 값을 같이 담아 서버가 방 전체에 알립니다.

4. 큰 데이터를 안전하게 보내기: 16KB 청크와 흐름 제어

DataChannel 메시지 크기는 브라우저마다 한계가 다르고, 큰 메시지는 조용히 실패하기도 합니다. 브라우저 간 호환성이 가장 좋은 크기는 16KB 안팎이라서, CollaBoard는 파일과 큰 상태 스냅샷을 16,000바이트 청크로 나눠 보냅니다. 받는 쪽은 청크 번호로 제자리에 채워 넣고, 개수가 다 차면 원래 파일로 합칩니다.

보내는 속도도 조절해야 합니다. 청크를 한꺼번에 밀어 넣으면 bufferedAmount가 계속 쌓이다가 연결이 끊어질 수 있습니다. 그래서 버퍼가 상한을 넘으면 전송을 멈추고, bufferedAmountLowThreshold 이벤트가 올 때까지 기다렸다가 이어서 보냅니다. 이 흐름 제어 덕분에 느린 모바일 회선에서도 파일 공유가 끝까지 진행됩니다.

5. 늦게 들어온 사람도 같은 화면을 보게 하기

협업 앱에서 까다로운 문제는 중간 입장입니다. 서버에 저장된 상태가 없으니, 새 참가자는 이미 방에 있는 참가자에게 현재 상태를 받아야 합니다. 화이트보드는 새 참가자가 연결되면 기존 참가자가 벡터 스냅샷을 보내 줍니다. 연결이 잠깐 끊겼다 돌아오는 경우에 대비해, 참가자끼리 주기적으로 상태를 비교해 빠진 부분을 채우는 anti-entropy 동기화도 돌립니다.

이 구조에는 분명한 대가가 있습니다. 모두가 나가면 방의 자료도 사라집니다. CollaBoard는 이를 버그가 아니라 성격으로 받아들였고, 필요한 결과물은 PNG 저장이나 Markdown 복사로 각자 챙기도록 했습니다.

6. 방을 지키는 입장 보안

자료를 서버에 두지 않아도, 아무나 방에 들어올 수 있으면 의미가 없습니다. 그래서 입장은 서버가 엄격하게 관리합니다.

브레인스토밍의 블라인드 모드에서는 공개 전까지 아이디어 본문이 작성자 기기 밖으로 나가지 않습니다. “서버가 갖지 않는다”를 한 단계 더 밀어붙여, 다른 참가자의 기기에도 공개 전에는 보내지 않는 것입니다.

정리

P2P 구조는 서버 비용과 개인정보 부담을 크게 줄여 주지만, 그만큼 연결 순서, 전송량 조절, 중간 입장, 연결 복구를 클라이언트가 떠안아야 합니다. CollaBoard를 만들며 얻은 교훈은 서버가 할 일을 줄이는 것과 서버를 없애는 것은 다르다는 점입니다. 신뢰가 필요한 일(입장, 권한)은 서버에 남기고, 내용은 참가자 사이에만 흐르게 나누는 것이 이 앱의 핵심 설계입니다.

팀과 함께 직접 써 보려면 CollaBoard에서 방을 만들고 링크를 공유하면 됩니다.