멀티플레이어를 더 안정적으로 (새 탭에서 열림)
Figma는 인메모리 상태와 30~60초 간격의 체크포인트에 의존하던 멀티플레이어 시스템에 변경 이력을 기록하는 저널(write-ahead log)을 도입했다. 저널은 파일의 전체 상태가 아닌 증분 변경을 자주 저장하므로 장애 발생 시 최신 체크포인트 이후의 변경을 재생해 복구할 수 있으며, 목표 데이터 손실을 1초 미만으로 줄였다. 또한 배포 시 모든 파일을 동시에 체크포인트하는 쓰기 부하 급증도 해소했다.
기존 멀티플레이어 구조
- 브라우저 클라이언트는 WebSocket으로
multiplayer서비스에 연결한다. - 서버는 파일 상태를 메모리에 보관하면서 여러 클라이언트의 변경 사항을 수신·검증·정렬·충돌 해결한 뒤 전체 클라이언트에 전달한다.
- 메모리 상태는 휘발성이므로 30~60초마다 파일 전체를 바이너리로 인코딩하고 압축해 S3에 체크포인트로 저장한다.
- 체크포인트는 버전 기록 등 일부 기능의 기반이 된다.
체크포인트 중심 방식의 문제점
- 서버가 장애를 일으키면 마지막 체크포인트 이후 최대 60초의 작업을 잃을 수 있다.
- 파일 전체를 저장하므로 파일의 크기와 복잡도가 커질수록 저장 비용도 증가한다.
- 멀티플레이어를 재배포하면 메모리에 있던 모든 파일을 닫아야 하므로 동시에 대량의 체크포인트 쓰기가 발생한다.
- 이로 인해 데이터베이스 부하가 급증하고, 배포가 사용자에게 보이지 않는 작업이어야 한다는 목표를 방해한다.
증분 변경을 저장하는 저널
- Figma는 파일 변경 사항을 기록하는 내구성 있는 트랜잭션 로그인 저널을 추가했다.
- 멀티플레이어가 변경을 수락하면 변경 내용을 비동기적으로 저널에 기록한다.
- 각 변경에는 파일별로 증가하는 시퀀스 번호를 부여한다.
- 체크포인트에도 해당 시점의 시퀀스 번호를 함께 저장한다.
- 저널에는 전체 파일이 아니라 사용자가 수행한 증분 변경만 저장한다.
- 예: 텍스트 수정, 디자인 요소의 위치 변경, 목업 업데이트 등
- 증분 변경은 전체 파일보다 훨씬 작기 때문에 더 자주 기록해도 효율적이다.
장애 복구 방식
- 서버가 재시작되면 기존 체크포인트를 먼저 불러온다.
- 체크포인트의 시퀀스 번호보다 큰 시퀀스 번호를 가진 저널 항목을 조회한다.
- 해당 변경들을 순서대로 재생해 최신 파일 상태를 복원한다.
- 기존 체크포인트 방식은 약 60초 간격으로 저장했지만, 저널은 약 0.5초 수준으로 변경 사항을 기록하는 방향을 취한다.
- 그 결과 장애 시 데이터 손실 목표를 1초 미만으로 낮췄다.
배포 시 쓰기 부하 안정화
- 배포할 때 모든 연결을 종료하고, 아직 저장되지 않은 변경이 저널에 기록될 때까지 기다린다.
- 99번째 백분위수 기준으로 이 과정은 1초 이내에 완료된다.
- 배포를 위해 대규모 체크포인트를 한꺼번에 생성할 필요가 없어졌다.
- 저널 쓰기는 평상시에도 지속적으로 발생하므로 데이터베이스 부하가 일정하고 예측 가능해진다.
데이터 저장소 선택
- 저널의 백엔드 저장소로 DynamoDB를 사용했다.
- Postgres와 로컬 디스크 등 여러 선택지를 검토했지만, 높은 쓰기량을 수평 확장해야 한다는 점 때문에 Postgres는 선택하지 않았다.
- 이 사례에서는 익숙한 데이터베이스보다 쓰기 규모와 확장성을 감당할 수 있는 저장소가 더 중요한 기준이었다.
변경 사항 배치 처리
- 클라이언트는 초당 30프레임, 즉 약 33ms마다 업데이트를 보낸다.
- 모든 업데이트를 같은 빈도로 저널에 기록할 필요는 없으므로 여러 변경 사항을 묶어 일정 주기로 저장한다.
- 이 배치 처리는 저널 쓰기 횟수를 줄이고 성능을 개선하면서도 체크포인트보다 훨씬 짧은 복구 지연 시간을 유지하기 위한 방식이다.
Figma의 사례는 전체 상태를 드물게 저장하는 체크포인트와, 작은 변경을 자주 저장하는 저널을 함께 사용하는 구조가 실시간 협업 시스템에 적합하다는 점을 보여준다. 장애 복구 시간과 데이터 손실을 줄이려면 증분 로그를 도입하고, 시퀀스 번호를 기준으로 체크포인트와 로그를 연결하는 방식을 고려할 수 있다.