【제31회】 중간 참여자도 대화에 합류할 수 있게! 피어 간 직접 통신을 통한 채팅 과거 로그 차분 동기
요약
본 글은 중앙 서버에 로그를 남기지 않는 P2P 채팅 시스템의 약점(중간 참여자의 과거 대화 로그 접근 불가)을 해결하는 방법을 제시합니다. 피어 간 직접 DataChannel을 통해 차분 동기, 중복 제거 등의 프로토콜을 구현하여 사용자 경험을 개선했습니다.
핵심 포인트
- 서버 의존성 없이 P2P 채팅의 약점을 보완함.
- 차분 동기(Incremental Sync)로 최소 대역폭으로 로그를 가져옴.
- UUID 기반 중복 제거 및 UI 분리 기능을 추가함.
- 사용자 설정에 따라 로컬 저장 정책을 선택할 수 있게 함.
안녕하세요! 2채널 호환 게시판 시스템 "つゔぁいちゃんねる (2ch.biz)" 개발 기록, 제31회입니다.
지난번(제30회)에는 문의 양식이나 P2P 시그널링 API를 노린 봇 공격을 허니팟과 IP 규제로 멋지게 격퇴했습니다.
하지만 P2P 암호화 채팅이 실제로 많은 사용자에 의해 사용되기 시작하면서, 중앙 서버가 없는 아키텍처 특유의 **'결정적인 약점'**이 드러났습니다.
그것은, **'도중에 입장한 사용자나 전파가 안 좋아서 일시적으로 연결이 끊긴 사용자가 과거 대화 로그를 전혀 볼 수 없다'**는 문제입니다.
Discord나 LINE 같은 일반적인 클라이언트-서버형 채팅에서는 서버의 DB에 전체 기록이 저장되어 있기 때문에, 도중에 참여한 사용자도 과거 로그를 스크롤하여 거슬러 올라갈 수 있습니다.
하지만 저희 P2P 채팅은 '서버에 로그를 단 한 글자도 남기지 않는' 제로 스토리지 설계입니다.
그 결과, 다음과 같은 비극이 발생하고 있었습니다.
【지금까지의 중간 참여자 경험】
1. 룸 내에서 Alice와 Bob가 흥겹게 30분 동안 열띤 토론을 벌인다.
2. 그곳에 Carol가 입장한다.
...
더 나아가, 모바일 환경에서 터널링을 거치다가 회선이 몇 초 끊기는 것만으로도, 그 사이에 오간 메시지가 빠져나가 대화의 맥락을 따라갈 수 없었습니다.
이 문제를 해결하기 위해, 저희는 **'서버를 거치지 않고, 피어(단말)끼리 직접 DataChannel을 통해 과거 로그를 공유하는 P2P 분산 로그 동기 프로토콜'**을 고안하고 구현했습니다.
서버에 의존하지 않으면서도 악의적인 변조나 통신 끊김을 방지하며 과거 로그를 공유하기 위해, 다음 프로토콜을 수립했습니다.
-
차분 동기(Incremental Sync): 일시적 연결 끊김 후 복귀 시에는 '자신이 가지고 있는 최신 메시지의 타임스탬프'를 첨부하여
history_req를 전송함으로써, 부재했던 사이의 차분 로그만 최소 대역폭으로 가져옵니다. -
다중 수신 및 중복 제거(De-duplication): 룸에 10명의 기존 참여자가 있는 경우, 10명 모두로부터 일제히
history_res가 돌아올 가능성이 있습니다 (로그 스톰). 각 메시지의 고유 ID(UUID)를 메모리상의Set으로 대조하여 중복 표시를 완벽하게 방지합니다. -
시각적 분리(UI Separator): 실시간 발언과 동기화된 과거 로그가 뒤섞여 혼란스럽지 않도록,
--- 📜 과거 로그 동기 여기까지 (HH:mm:ss) ---라는 경계선을 동적으로 삽입합니다. -
실시간 발언과 동기화된 과거 로그가 뒤섞여 혼란스럽지 않도록,
로그의 동기가 가능해짐에 따라, '애초에 자신의 브라우저에 로그를 저장하고 싶지 않다', '브라우저를 닫으면 깨끗하게 사라졌으면 좋겠다'는 프라이버시 최우선주의 사용자의 존재도 있습니다.
그래서, 룸을 개설하는 호스트가 다음의 **불변 단말 저장 정책(persistHistory)**을 선택할 수 있도록 했습니다.
| 정책 | 동작 | 스토리지 |
|---|---|---|
| 💾 단말 저장 허용 | 재방문 시 과거 로그가 복원되며, 새로 온 참여자에게도 로그를 배포함 | 브라우저의 IndexedDB에 암호화 페이로드 상태로 저장 |
| ⚡ 완전 휘발(금지) | 브라우저를 닫거나 탭을 닫는 순간 전체 삭제. 일시적 연결 끊김 후 복귀 시에만 메모리상의 로그를 동기화 | 메모리 배열만 (디스크 쓰기 제로) |
이 설정은 시그널링 서버를 통해 참여자 전원에게 고정 배포되며, 채팅 상단 토픽 바에 배지(💾 저장: 허용 / ⚡ 저장: 금지 (휘발)) 형태로 명시됩니다.
룸의 정책과는 별개로, 참여자 개인의 통신량이나 프라이버시에 대한 생각에 배려하여, 토픽 바의 '📜 로그 설정'에서 언제든지 다음 항목을 전환할 수 있도록 했습니다.
📤 다른 참여자에게 과거 로그를 배포(ON / OFF) -📥 입실/복귀 시 과거 로그를 수신(ON / OFF)
설정값은 각 단말의 localStorage에 저장됩니다. '기가(모바일 통신량)를 절약하고 싶어서 로그 배포는 OFF로 한다'와 같은 유연한 운영이 가능합니다.
/**
- P2P 채팅용 IndexedDB 스토리지
*/
...
// 중복 렌더링을 방지하는 Set
const renderedMsgIds = new Set();
let hasRenderedHistorySeparator = false;
...
여러 브라우저 인스턴스를 사용하여 다음과 같은 가혹한 시나리오를 테스트했습니다.
신규 입장 시 로그 동기화: 방에서 20건의 대화가 진행된 후, 신규 사용자가 입장합니다.
-
DataChannel 연결 후 단 35밀리초 만에 전체 20건의 로그가 복원되었으며, 끝부분에 「
--- 📜 과거 로그 동기화 여기까지 (15:20:11) ---」가 보기 좋게 표시되는 것을 확인했습니다.
5명 동시 접속으로부터의 로그 스톰 내성: 5명의 참가자가 있는 방에 입장했을 때, 5명 전원에게서 동시에 history_res가 도착하더라도, renderedMsgIds를 통해 로그 중복이 0건임을 입증했습니다.
- 5명의 참가자가 있는 방에 입장했을 때, 5명 전원에게서 동시에
- 네트워크 단절 복구 테스트: Chrome DevTools의 네트워크를 「Offline」으로 설정하고 3건 발언시킨 후, 「No throttling」으로 되돌렸을 때, 빠졌던 3건만 차이(diff) 동기되는 것을 확인했습니다.
중앙 서버에 로그를 전혀 두지 않는다는 제약 하에, WebRTC DataChannel과 IndexedDB를 결합한 「P2P 분산 로그 동기화」를 완성했습니다.
이를 통해 도중에 참여하는 사람도 원활하게 대화에 합류할 수 있게 되어, P2P 채팅의 실용성이 비약적으로 향상되었습니다.
하지만 테스트를 거치면서 또 다른 매우 심각한 문제가 부상했습니다.
그것은, 「방을 만든 호스트(방장)가 브라우저를 닫으면, 모두가 퇴출되거나 방이 붕괴해버리는」 P2P 특유의 리더 상실 문제입니다.
방장이 갑자기 잠들거나 회선이 끊겼을 때, 방을 해산시키지 않고 지속하게 하려면 어떻게 해야 할까요?
본 서비스 : https://2ch.biz/
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기