배경
최고 점수 랭크 및 성능 개선
지난 랭킹 시스템 개선에서 ‘최고 기록 랭킹’을 도입하고, ‘최고 기록’ 집계 객체(테이블)을 만들었다.
쓰기 성능을 약간 희생해 조회 성능을 개선하고, ‘최고 기록 갱신’을 코드로 표현할 수 있게 되었다.
“해치웠나?”라고 생각할 때가 가장 위험하다고 했던가.
집계 객체 갱신에 대해 고민하던 중 동시성 문제가 생길 수 있는 상황을 발견했다.
상황
최고 기록이 전무한 경우
최고 기록은 게임을 한 판이라도 마쳐야 생성된다.
최고 기록이 없는 사용자가 동시에 다수의 게임을 완료한다면 어떨까?
방금 가입한 사용자 1이 2개의 게임을 동시에 진행하는 상황을 생각해보자.
두 트랜잭션은 격리되어 있어, 커밋되기 전까지 다른 최고 점수의 존재를 모른다.
따라서 두 트랜잭션은 각각 새 최고 점수를 저장한다.
결과적으로 유니크 제약조건이 있다면 예외가, 없다면 한 사용자에게 2개의 최고 점수가 생긴다.
최고 기록이 이미 있는 경우
기존 사용자 사용자 1의 최고점수는 10점이다.
사용자 1은 동시에 2개의 게임을 진행했고, 트랜잭션 1은 70점, 트랜잭션 2는 80점을 쓰려고 한다.
논리적으로는 80점이 되는 것이 맞지만, 결국 70점이 되었다.
두 트랜잭션은 격리되어 있어 각각의 입장에선 최고 점수가 10점으로 보이기 때문이다.
실제로 그럴까?
어떻게 풀 것인가?
잦은 문제인가?
잦은 문제라면, 처리 성능도 챙겨야 한다.
DB 커넥션은 한정적이기 때문에 이 트랜잭션의 처리 속도가 늦어지면 다른 트랜잭션이 대기하기 때문이다.
‘한 사용자가 동시의 2개의 게임을 플레이하는 경우’는 잘 발생하지 않는다.
이 점을 염두해서 해결 방법을 고민해보자.
동시 게임 실행을 막는다면?
우선 이 방법은 사용자의 행위를 제한하는 대신, 모든 문제를 근본적으로 해결한다.
하지만 ‘동시 실행 방지 시스템’을 개발하고 유지하는 비용을 고려했을 때, 약간 망설여졌다.
동시 게임 실행을 막기 위해선 상태를 관리해야 한다.
그 ‘상태’는 또 어떻게 관리할 것인가?
자주 일어나지도 않는 문제를 해결하기 위한 비용이 너무 크다는 생각이 들었다.
대표적으로 몇 가지가 생각났다.
- 개발자가 실행 상태가 관리되고 있다는 것을 신경쓰는 비용
- 상태 관리 실패를 관리하는 비용
- 상태 관리 오류로 발생하는 비용
- 사용자 이탈
- 디버깅 및 버그 데이터 수리 인력(시간)
ex) 사용자 연결이 끊기면 게임을 종료시킨다, 클라이언트와 heartbeat 체크를 한다.
그래서 이 방법은 최후의 방법으로 사용하는 것이 좋겠다고 생각했다.
기술적으로 풀 것인가?
문제를 항상 기술적으로 풀어야 하는 것은 아니다.
조회수나 좋아요 같이, 비즈니스적 중요도가 낮은 문제는 실시간성을 낮추는 선택지도 있다.
메모리에 쌓은 후 주기적으로 반영해주는 것이다. 이 주기를 짧게 가져가면 꽤 쓸만할 것이다.
랭크 시스템은 어떨까?
실시간으로 보여야하는것이 아니라면, 큐를 통한 순차 반영으로 문제를 우회할 수 있다.
하지만 그러지 않고 문제를 직면하기로 했다.
욕심일 수 있지만, 사용자에게 실망감을 주고싶지 않았기 때문이다.
최고 점수를 갱신했는데, 바로 보이지 않는다면 실망스러울 것이라는 생각이 들었다.
다음 글에서 이 문제를 실제로 풀어보자.