Digital Wallet DB High Availability Demo 💰
| 신성혁 @ShinSungHyuk |
유예원 @Yewon0106 |
이준호 @Junhoss |
조민우 @MinWoo Cho |
금융/결제 도메인인 Digital Wallet(디지털 지갑) 서비스를 기반으로, 단일 장애점(SPoF)이 없는 고가용성 인프라를 구축하고 검증한 프로젝트입니다.
단순한 기능 구현을 넘어, 대규모 트래픽 환경에서 필수적인 데이터 정합성, 멱등성, 그리고 DB 고가용성을 위한 DB Failover 과정을 엔지니어링 관점에서 구현했습니다.
- Infrastructure: MySQL Replication, ProxySQL, Orchestrator를 활용한 자동화된 HA 환경 구축
- Failover Validation: 장애 발생 시 복구 프로세스 시각화
- Replication(이중화) → Failover(승격) → Observability(관측)
- Data Consistency: 동시성 이슈 제어를 위한 Pessimistic Lock 및 중복 결제 방지를 위한 Idempotency Key 적용
애플리케이션은 DB 토폴로지를 알 필요 없이 단일 엔드포인트(ProxySQL) 만 바라보며, HA와 Failover는 인프라 레이어에서 전담하는 구조입니다.
- Application Layer: Spring Boot API (Wallet Service)
- Proxy Layer: ProxySQL (Read/Write Splitting & Failover Routing)
- Database Layer: MySQL 8.0 GTID Replication (1 Primary - 2 Replicas)
- Management Layer: Orchestrator (Topology Management & Auto-Failover)
- Monitoring Layer: Prometheus & Grafana
DB가 죽어도 서비스는 멈추지 않는다는 것을 검증하기 위한 핵심 시나리오입니다.
실제 운영 환경을 모사하여 Primary DB에 장애를 유발하고 시스템을 복구하는 과정을 검증했습니다.
-
Normal State: 트래픽이 ProxySQL을 통해 Primary DB로 정상 유입
-
Failure Injection: “docker stop mysql_primary” 명령어로 강제 장애 발생
-
Detection & Decision: Orchestrator가 장애 감지 및 관리자 Slack 알림
관리자가 Grafana 대시보드에서 Replication Lag 및 Replica DB 상태 확인 후 Failover 실행
-
Failover: Orchestrator가 최적의 Replica DB를 새로운 Master DB로 승격
-
Routing Update: ProxySQL이 토폴로지 변경을 감지하고 트래픽을 새로운 Master DB로 즉시 전환
-
Recovery: 모니터링으로 서비스 복구 이후 결제/충전 기능 정상 동작 확인
장애 감지(MTTD)와 복구(RTO)를 수치로 증명하기 위해 정밀한 모니터링 대시보드를 구축했습니다.
- Failover Detection: read_only 상태 변화를 감지하여 Master 승격 시점 시각화
- Service Impact: Total QPS의 V자 반등 곡선을 통해 트래픽 복구 확인
- Data Safety: Replication Lag 및 Aborted Connections 모니터링을 통한 데이터 유실 가능성 차단
- 동시성 제어 (Concurrency Control):
SELECT ... FOR UPDATE(Pessimistic Lock)을 사용하여 충전/결제 시 Race Condition 원천 차단
- 멱등성 보장 (Idempotency):
- 네트워크 타임아웃/재시도 상황에서 중복 결제를 막기 위해 Idempotency-Key 기반의 중복 요청 필터링 구현
- MySQL GTID Replication:
- 데이터 일관성을 위한 GTID 기반 비동기 복제 구성
- ProxySQL Routing:
- 쿼리 패턴에 따른 Read/Write Splitting (쓰기: Master, 읽기: Replica 분산)
- Orchestrator-Assisted Failover:
- 완전 자동화의 위험성을 배제하기 위해 모니터링 지표(Lag, Thread Status) 확인 후 운영자가 승인하는 'One-Click Failover' 프로세스 구축
- 복구 자체는 Orchestrator가 수행하되, 의사결정(Decision Making)은 데이터에 기반하여 수행
-
Problem: docker-compose로 다중 컨테이너 실행 시, MySQL 컨테이너가 완전히 구동되기 전에 복제 설정 명령어가 실행되어 연결 실패 및 엔드포인트 불일치 문제가 지속적으로 발생
-
Process:
- 초기에는 command 옵션과 depends_on 옵션으로 해결하려 했지만 컨테이너 초기화 속도 차이로 인한 Race Condition을 제어하기 어려움
- 수동 설정은 휴먼 에러를 유발하므로 배제
-
Solution: 'Setup Container(Sidecar Pattern)' 도입
별도의 일회성 컨테이너를 추가하여, Master/Replica의 헬스 체크를 선행하고 이후 복제 설정까지 하는 쉘 스크립트를 실행하도록 구성
-
Outcome:
docker compose up -d명령어 하나 만으로 Master-Replica 이중화 환경이 완벽하게 구성되도록 자동화 설정
-
Problem: 초기에 도입한 HAProxy는 TCP(Layer 4) 기반으로 동작하여 쿼리의 내용을 분석해 라우팅하지 못하는 문제 발생 단순 Port 기반 분산은 가능했으나, 동시성 제어를 위한
SELECT ... FOR UPDATE쿼리까지 Replica로 분산되어 Lock이 동작하지 않는 치명적인 정합성 이슈 발생 -
Process:
- 외부 스크립트를 통해 read_only 값을 체크하여 라우팅하려 했으나, 구조가 복잡해지고 관리 포인트가 늘어나는 단점 존재
- 쿼리 내용을 분석하여 라우팅 할 수 있는 Layer 7 Database Proxy의 필요성 확인
-
Solution: ProxySQL로 기술 스택 변경 및 Query Rules 적용
- Rule 1:
^SELECT.*FOR UPDATE$정규식을 매칭하여 강제로 Hostgroup 10(Writer) 로 라우팅 - Rule 2: 일반
^SELECT쿼리는 Hostgroup 20(Reader) 로 라우팅하여 부하 분산
- Rule 1:
-
Outcome: 애플리케이션 코드 수정 없이 인프라 레벨에서 Read/Write Splitting과 Transactional Consistency를 동시에 달성
| Category | Technology |
|---|---|
| Language & Framework | Java 17, Spring Boot 3.5, Spring Data JPA, Hibernate |
| Database | MySQL 8.0 (GTID Replication) |
| HA & Routing | ProxySQL, Orchestrator |
| Monitoring | Prometheus, Grafana, mysqld_exporter |
| Infrastructure | Docker, Docker Compose |
이 프로젝트는 Docker Compose를 통해 로컬 환경에서 즉시 실행 가능합니다.
- Docker & Docker Compose installed
- Git installed
# 1. Repository Clone
git clone https://github.com/fisa-tech-seminar/HA-project.git
# 2. Execute (All-in-One)
# MySQL(Primary/Replica), ProxySQL, Orchestrator, App, Monitoring이 모두 실행됩니다.
docker-compose up -d
# 3. Initialize Topology (Orchestrator Discovery) 컨테이너가 정상적으로 실행된 후(약 20~30초 대기), Orchestrator가 DB 클러스터를 인식하도록 아래 명령어를 실행합니다.
# Orchestrator에게 Primary DB를 찾아보라고(Discover) 명령
docker exec -it orchestrator orchestrator-client -c discover -i mysql_primary- Wallet API:
http://localhost:8080 - Grafana Dashboard:
http://localhost:3000(ID/PW:admin/admin) - Orchestrator UI:
http://localhost:3001 - ProxySQL Admin:
mysql -u admin -p... -h 127.0.0.1 -P 6032
📂 Domain Model & API Specs (Click to Expand)
Wallet (지갑): 사용자의 잔액(Balance) 상태 관리
user_id (PK),balance,version
Wallet_tx (거래 원장): 모든 자금 흐름의 불변 기록 (Ledger)
tx_id (PK),type (TOPUP/PAY),amount,status,idempotency_key (UNIQUE)
Base path: /api/v1
| Method | URI | Description |
|---|---|---|
GET |
/wallets/{userId} |
잔액 조회 |
POST |
/wallets/{userId}/topup |
잔액 충전 (Header: Idempotency-Key 필수) |
POST |
/wallets/{userId}/pay |
결제 (Header: Idempotency-Key 필수) |



