뱅크샐러드, 낙관적 락으로 게임 데이터 무결성 확보

by DD
4개월 전
조회수 162

뱅크샐러드는 앱테크 게임 '일해라 김뱅샐'에 낙관적 락(Optimistic Lock)을 도입하여 데이터 정합성(Data Consistency)을 확보함

낙관적 락(Optimistic Lock)은 낮은 충돌 확률, 인프라 복잡도 최소화, 매끄러운 게임 경험 제공을 위해 선택됨

낙관적 락(Optimistic Lock)은 시간 차분(Time-delta) 기반 보상 이월 구조자기 수정(Self-correcting) 아키텍처를 통해 재시도 로직 없이 구현됨

분산 DB 환경에서의 복제 지연(Replication Lag), Stale Read 문제 해결을 위해 Consistent Read 강제

재시도 전략으로 지수 백오프(Exponential Backoff), 지터(Jitter), 멱등성(Idempotency) 보장, 최대 재시도 횟수 제한 적용

낙관적 락(Optimistic Lock) 선택 배경: 트레이드오프 분석

뱅크샐러드는 데이터 정합성(Data Consistency) 확보를 위해 비관적 락(Pessimistic Lock), 분산 락(Distributed Lock), 낙관적 락(Optimistic Lock)을 비교 분석했다. 비관적 락(Pessimistic Lock)은 데이터 일관성을 보장하지만, DB 커넥션 병목(DB Connection Bottleneck)을 유발할 수 있다. 분산 락(Distributed Lock)은 외부 인프라 운영 비용(Infrastructure Cost)이 발생하며, 낙관적 락(Optimistic Lock)은 충돌 시 애플리케이션 레벨(Application Level)에서 에러 처리 필요하다.

낮은 충돌 확률: 유저별 데이터 격리 구조로 인해 낙관적 락(Optimistic Lock)의 충돌 가능성이 낮음

인프라 복잡도 최소화: 기존 DB 스키마에 update_version 컬럼 추가만으로 구현 가능

매끄러운 경험: DB Lock 대기 없이 유저 경험(User Experience) 개선

낙관적 락(Optimistic Lock) 구현 상세: CAS(Compare-And-Swap) 연산

낙관적 락(Optimistic Lock)은 데이터베이스(Database) 스키마에 `update_version` 필드를 추가하고, 애플리케이션(Application) 레이어에서 버전 검증을 수행한다. `character_state` 테이블에서 정적 데이터(Static Data)와 동적 데이터(Dynamic Data)를 분리하여 I/O 효율(I/O Efficiency)을 높이고 락 범위(Lock Scope)를 최소화했다. `UpdateState` 함수에서 `CharacterID`와 `UpdateVersion`을 조건으로 업데이트 쿼리(Update Query)를 실행하며, 업데이트된 행(Row)의 개수를 확인하여 충돌 여부를 판단한다.

`update_version` 필드: 동시성 제어(Concurrency Control)를 위한 버전 관리

CAS(Compare-And-Swap) 연산: 원자적(Atomic) 연산을 통해 데이터 무결성(Data Integrity) 보장

충돌 감지: `numUpdated == 0`인 경우, `ErrConcurrentStateUpdate` 에러 반환

분산 환경에서의 낙관적 락(Optimistic Lock) 운영 노하우

분산 DB 환경에서 낙관적 락(Optimistic Lock)은 복제 지연(Replication Lag)Stale Read 문제를 야기할 수 있다. 뱅크샐러드는 자산 업데이트(Asset Update)와 상태 변경(State Change)과 같이 정합성이 중요한 경로에서는 반드시 Primary DB 조회를 강제하여 최신 버전을 읽도록 했다. 이벤트 기반 아키텍처(Event-driven Architecture)에서는 메시지에 포함된 버전과 DB 현재 버전을 비교하고, DB가 아직 업데이트 전이라면 전파가 완료될 때까지 대기 후 재시도하는 핸들러(Handler) 설계를 적용했다.

Consistent Read 강제: 최신 데이터(Latest Data)를 읽어 Stale Read 방지

이벤트 대조 핸들러: 메시지 버전과 DB 버전 비교, 재시도 로직 구현

재시도 전략: 충돌은 시스템 장애가 아닌 정상적인 제어 흐름의 일부

낙관적 락(Optimistic Lock) 재시도 전략: 4가지 원칙

낙관적 락(Optimistic Lock)에서 충돌(Conflict)은 시스템 장애가 아닌 정상적인 제어 흐름의 일부이므로, 비즈니스(Business) 성격에 맞는 정교한 재시도 전략이 필요하다. 뱅크샐러드는 재시도 구현 시 4가지 원칙을 준수했다.

Fresh Read의 원칙: 재시도 루프 내부에서 반드시 Primary DB에서 최신 버전 정보를 다시 읽어와야 함

지수 백오프(Exponential Backoff)와 지터(Jitter): 재시도 간격을 2n2^n 형태로 늘리고, 무작위 값인 지터(Jitter)를 섞어 요청 시점 분산

멱등성(Idempotency) 보장: 클라이언트(Client)가 생성한 `request_id`를 기반으로 멱등성 테이블(Idempotency Table)을 운용하여 중복 처리 방지

최대 재시도 횟수 제한: 일정 횟수 이상 충돌 시, 에러 반환 또는 비관적 락(Pessimistic Lock) 전환

낙관적 락(Optimistic Lock)의 장점: 재시도 로직 없는 서비스 구조

뱅크샐러드는 낙관적 락(Optimistic Lock) 도입을 위해 복잡한 재시도 로직(Retry Logic)을 최소화했다. 유저가 벌어들인 수익 계산 로직은 개별 클릭 이벤트(Click Event)에 의존하는 ‘이벤트 적립’ 방식이 아닌, 마지막 업데이트 시점과 현재 시점 사이의 시간 간격(Time-delta)을 계산해 보상을 합산하는 상태 기반 정산 방식을 사용한다. 특정 요청이 동시성 충돌로 실패하더라도 유저의 수익은 누락되지 않으며, 시스템은 결국 최신 정산 상태로 자기 수정(Self-correcting)하며 수렴한다.

시간 차분(Time-delta)을 활용한 보상 이월 구조: 실패 시 보상 누락 방지

자기 수정(Self-correcting) 아키텍처: 별도의 보정 로직 없이 시스템 스스로 수렴

결과적 일관성(Eventual Consistency): 분산 환경에서 시스템의 복잡도 감소

뱅크샐러드가 게임을 만들 때 데이터 정합성을 유지하는 법 (feat. 낙관적 락)