RoundWaitArchitectureFirebaseGoCloud Runk6Dual Write
RoundWait 아키텍처 — 왜 Firebase로 재구축했나
2026. 6. 1.3분 읽기
배경: 왜 실시간 상태 갱신을 Firebase로 옮겼나
마비노기 Fantasy Party 2026은 1만 명+ 규모 행사다. 참가자 대시보드, 부스별 대기열, 호스트 화면이 실시간으로 갱신돼야 했는데, 이걸 자체 서버(WebSocket/SSE)로 감당하려면 행사 당일 트래픽 폭증에 대비해 서버를 상시 크게 켜둬야 해서 비용 부담이 컸다.
핵심 설계: MySQL은 그대로, 실시간 전파만 이관
DB(MySQL, 이후 Aurora Serverless v2)는 그대로 소스오브트루스로 남기고, 실시간 상태 전파만 Firebase Realtime Database(RTDB)로 옮겼다. Firestore가 아니라 RTDB다 — 단순 키-값 실시간 동기화가 목적이라 Firestore의 쿼리 기능은 필요 없었다.
Firebase RTDB (읽기 전용 미러)
- 이유: 백엔드가 MySQL 트랜잭션 커밋 후 Admin SDK로 RTDB에 상태를 쓰면, 프론트는 Firebase 클라이언트 SDK로 그걸 구독만 하면 됨 — 자체 WebSocket 인프라를 상시 띄워둘 필요가 없음
- 원칙: 클라이언트 쓰기는 규칙으로 차단, 인증은 백엔드가 발급하는 커스텀 토큰으로만
- 안전장치: RTDB 연결이 끊겨도 예약 상태가 갱신되도록, 참가자 쪽엔 API 폴백 폴링(보일 때 30초·숨김 60초·호출됨 5초 간격)을 같이 붙였다
Cloud Run + k6 (부하 테스트)
- 이유: k6를 GCP 내부 Cloud Run Job으로 실행하면 외부 네트워크 비용 없이 현실적인 부하 재현 가능
- 방법: Cloud Run Job이 k6를 실행 → asia-northeast3 리전에서 직접 부하 생성
[Cloud Run Job] → k6 부하 생성 → [백엔드: MySQL 커밋] → [Firebase Admin SDK] → [RTDB 미러]
↓
[KakaoTalk 알림]
Dual Write (무중단 마이그레이션)
- 이유: 기존 서비스를 내리지 않고 새 실시간 경로로 전환해야 했음
- 방법: 배포 상태값을
dual로 두고 신·구 실시간 경로를 동시에 운영하며 데이터 정합성을 확인한 뒤, 완전히 RTDB 경로로 전환
결과
| 지표 | 수치 |
|---|---|
| 1만 명 동시 부하 예약 성공률 | 99.94% |
| 평균 예약 처리 시간 | 0.26초 |
| 다운타임 | 0 (Dual Write 전환) |
배운 것
서버리스 실시간 계층은 "서버를 상시 띄워두지 않아도 된다"는 장점이 있지만, 소스오브트루스를 명확히 하나로 고정해야 한다. MySQL을 진실의 원천으로, RTDB는 읽기 전용 미러로 역할을 딱 자르고 나서야 데이터 정합성 문제 없이 운영할 수 있었다.