결제 대시보드에 포인트 캐시 재계산
목차
결제 대시보드에 포인트 캐시 수동 재계산 기능을 추가했다. 내부 클래스 2개, SQL 매퍼 1개, 뷰/스타일 파일 1개가 바뀌었는데, 파일 수만 보면 작은 작업이지만 실제로는 쿼리 설계부터 UI 렌더링 정합성까지 여러 레이어를 건드린 작업이었다.
왜 수동 재계산이 필요했나
포인트 캐시는 성능을 위해 존재한다. 매 요청마다 포인트 잔액을 집계 쿼리로 실시간 계산하면 DB 부하가 커지니까, 적립/차감/만료 같은 이벤트가 발생할 때마다 캐시를 갱신하는 패턴을 쓴다. 이 방식이 정상 작동할 때는 문제가 없다. 문제는 이벤트가 누락되거나 트랜잭션이 예외 경로로 빠져나왔을 때다. 캐시 값이 실제 잔액과 어긋나 있어도 겉으로는 멀쩡해 보이는 상태가 된다.
그래서 결제 도메인에서는 캐시와 원장(source of truth) 간의 드리프트를 보정하는 수단이 필요하다. 자동화된 reconciliation 배치가 이상적이지만, 운영 중에 특정 케이스만 즉시 맞춰야 할 때 관리자가 대시보드에서 직접 트리거할 수 있으면 대응이 훨씬 빠르다. 이번 기능이 그 수단이었다.
구현 흐름과 설계 판단
변경된 파일은 크게 네 군데다.
- 내부 클래스 2개: 캐시 재계산 로직을 역할별로 분리. 하나는 DB에서 실제 포인트 합산을 계산하는 역할, 다른 하나는 캐시 키를 무효화하고 새 값을 write하는 역할. 한 클래스에 다 때려넣으면 테스트 짜기 어려워지고 나중에 재사용도 안 된다.
- SQL 매퍼 1개: 일별/월별/누적 집계 쿼리. 실행 계획 미리 확인했고, 인덱스 타는 조건으로 작성했다.
- 뷰/스타일 1개: KPI 카드 섹션 추가, 숫자 포맷(천단위 콤마, 원 단위) 통일.
-- 집계 쿼리 예시 구조 (컬럼명 생략)
SELECT
COALESCE(SUM(CASE WHEN type = 'EARN' THEN amount ELSE 0 END), 0) AS total_earned,
COALESCE(SUM(CASE WHEN type = 'USE' THEN amount ELSE 0 END), 0) AS total_used,
COALESCE(SUM(CASE WHEN type = 'EXPIRE' THEN amount ELSE 0 END), 0) AS total_expired,
COALESCE(SUM(CASE WHEN type = 'EARN' THEN amount ELSE 0 END), 0)
- COALESCE(SUM(CASE WHEN type IN ('USE','EXPIRE') THEN amount ELSE 0 END), 0) AS balance
FROM point_history
WHERE user_id = #{userId}
AND status = 'CONFIRMED'
COALESCE 처리가 사소해 보여도 빠뜨리면 행이 없을 때 SUM이 NULL을 돌려주고, 그게 프론트로 그냥 흘러가면 "null 포인트"나 NaN 계산이 터진다. 집계 쿼리에서 NULL 방어는 기본값이어야 한다.
폴링 주기를 5초로 잡은 것도 선택이었다. 폴링 vs 이벤트 기반은 늘 고민되는 지점인데, 이번엔 폴링이 맞다고 봤다. 재계산은 관리자가 수동으로 트리거하는 동작이고 결과가 반영되는 시점이 예측하기 쉽지 않다. WebSocket이나 SSE를 붙이면 구조가 복잡해지는 것 대비 체감 이득이 크지 않았다. 5초면 "바로 됐나?" 확인하기에 충분하다.
숫자 포맷도 신경 썼다. 같은 지표가 이 화면에서는 "1234500"으로 보이고 다른 화면에서는 "1,234,500"으로 보이면 값이 다르다고 오해한다. 숫자 자체가 맞더라도 포맷 불일치는 신뢰 문제다. 서비스 전반의 숫자 표현 방식과 통일하고, 관련 화면과 같은 값 찍히는 거 눈으로 확인했다.
검증과 cross-check 습관
구현 후 직접 화면에서 동작 확인했다. 이때 내가 루틴처럼 하는 것들이 있다.
- 변경 전 기존 수치 메모해두기. 스크린샷이든 텍스트든.
- 재계산 트리거 후 DB에서 직접 쿼리 돌려서 캐시 값과 원장 값 비교.
- 관련 화면(포인트 내역, 사용자 상세 등)에서 같은 지표 숫자가 일치하는지 확인.
cross-check 빠뜨리면 반드시 다음 날 "이 숫자 왜 다르냐"는 질문이 온다. 금융/결제 도메인에서 숫자 불일치는 단순 버그 리포트로 끝나지 않고 신뢰 문제로 번진다. 조금 번거롭더라도 관련 화면 전부 열어두고 같은 값 찍히는 거 확인하는 걸 습관으로 굳혀뒀다.
커밋도 논리 단위로 쪼갰다. SQL 매퍼 변경, 클래스 로직 변경, 뷰 변경을 한 커밋에 다 넣으면 나중에 어느 변경이 문제를 일으켰는지 추적하기 어렵다. 작업 중간에 "일단 돌아가는 상태"를 커밋으로 남겨두면 롤백 단위도 생기고, 코드 리뷰할 때 리뷰어가 맥락 잡기도 쉽다. 커밋 메시지는 "무엇을" 보다 "왜"를 담으려고 노력한다. 석 달 뒤에 git log 뒤졌을 때 "왜 이걸 바꿨지?"를 코드가 아닌 메시지에서 바로 알 수 있어야 하니까.
사내 서비스라고 외부 서비스보다 덜 꼼꼼해도 된다는 생각은 오래전에 버렸다. 내부 도구일수록 운영자가 직접 쓰는 화면이고, 그 사람이 숫자를 보고 판단을 내린다. 캐시 값이 틀린 채로 운영되고 있었다면, 그동안의 판단 근거가 틀린 데이터였다는 뜻이다. "대충 맞는 것 같다"로 넘어가면 반드시 다시 돌아온다. 기능 하나가 버튼 하나로 끝나지 않는다는 걸, 작업할 때마다 새삼스럽게 느낀다.
댓글 0
첫 댓글 달아줘.