정산 SQL 수수료 집계 버그 수정
목차
정산 도메인에서 수수료 단가 하나가 바뀌면 그 여파가 생각보다 깊이 퍼진다. 이번엔 VA PG 수수료가 300원에서 330원으로 조정됐는데, 해당 값이 SystemRevenue_sql 매퍼에 박힌 조건에서 제대로 반영되지 않고 있었다. SQL 매퍼 파일 1개 수정으로 마무리됐지만, 그 파일 하나에 도달하기까지 과정이 단순하지 않았다.
정산 집계 버그는 특이하게도 "화면이 멀쩡해 보인다"는 점 때문에 발견이 늦어지는 경우가 많다. 숫자가 출력되니까 당장은 정상처럼 보이는데, 기대값과 실제값 사이에 조용히 차이가 쌓이고 있다. 이번 버그도 그 유형이었다. 집계 결과가 아예 0이거나 에러가 터지는 게 아니라, 숫자가 조금씩 틀려 있었다. 그리고 이런 종류의 버그는 한 화면만 봐선 잘 안 보임. 다른 집계 경로와 나란히 두고 비교할 때 비로소 드러난다.
어디서 숫자가 깨지는지 찾는 법
버그를 좁히는 방법은 단순하다. 집계를 수행하는 쿼리를 단계별로 쪼개서, 중간 결과를 직접 실행해서 비교하는 것. 복잡하게 조인된 정산 쿼리일수록 "어느 서브쿼리까지는 맞고, 어디서부터 차이가 생기는지" 추적하는 게 핵심이다.
예를 들어 수수료 집계 로직이 이런 형태로 작성되어 있다면:
SELECT
order_id,
revenue,
revenue * 0.03 AS pg_fee -- 300원 기준 비율 잔재
FROM orders
WHERE payment_type = 'VA'
AND status = 'SETTLED'
단가 정책이 330원으로 바뀌었을 때 이 비율이 갱신되지 않으면, 건별 오차는 작아도 월 집계 기준으로는 유의미한 차이가 생긴다. 30원 차이가 1만 건이면 30만원 오차다. 그러니 "어차피 작은 차이겠지"라고 넘기면 안 된다.
이번 케이스는 XML 매퍼 안의 쿼리 조건이 정책 변경을 따라가지 못한 것이 원인이었다. 수정 자체는 해당 조건을 330원 기준에 맞게 갱신하는 것이었고, 같은 패턴이 다른 결제 타입 쿼리에도 있는지 수평으로 훑었다. 위험한 케이스는 같이 손봤다.
정산 버그 수정 체크 흐름
단순히 틀린 값 하나 고치고 끝내면 같은 문제가 다른 경로에서 다시 올라온다. 그래서 이런 류의 버그를 잡을 때 아래 순서로 체크하는 걸 원칙으로 삼고 있다.
- 문제 쿼리 격리: 집계 쿼리를 단계 분리해서 어느 서브쿼리에서 차이가 생기는지 확인
- 패턴 수평 탐색: 같은 로직이 다른 결제 타입, 다른 기간 조건에도 있는지 grep/검색
- 수정 범위 최소화: 연관 없는 쿼리까지 손대면 회귀가 생길 수 있으니 영향 범위를 명시적으로 제한
- 수정 전후 수치 기록: 수정 전 쿼리 결과와 수정 후 결과를 나란히 두고 비교
- 관련 화면 cross-check: 정산 합계가 나오는 다른 화면이 있으면 같은 기간 값이 일치하는지 대조
마지막 cross-check가 귀찮아 보이지만 이게 제일 확실한 검증이다. DB에서 직접 집계한 값과 화면에서 렌더링되는 값이 다르면 쿼리 문제가 아니라 다른 레이어에 버그가 있다는 신호이기도 해서, 어디가 진짜 원인인지 빠르게 좁힐 수 있다.
집계 쿼리에서 자주 틀리는 패턴
수수료 집계 쿼리는 JOIN 방향이나 GROUP BY 기준을 조금만 틀려도 결과가 크게 달라진다. 자주 마주치는 실수 패턴을 정리하면 이렇다.
| 패턴 | 영향 |
|---|---|
| LEFT JOIN 대신 INNER JOIN 사용 | 수수료 매핑이 없는 건 집계에서 누락 |
| GROUP BY 기준 칼럼 누락 | 중복 합산으로 실제보다 큰 값 |
| 단가 하드코딩 후 정책 변경 미반영 | 이번 케이스처럼 기간별 집계 오차 |
| NULL 처리 누락 (COALESCE 없음) | 수수료 없는 건이 합산에서 NULL 전파 |
| 날짜 범위 경계 조건 오류 (< vs <=) | 마지막 날 데이터 누락 또는 중복 |
"단가 하드코딩"은 처음 작성할 땐 편하지만, 정책이 바뀌는 순간 SQL 매퍼를 전수 검색해야 하는 상황이 된다. 이상적으로는 수수료 단가를 별도 설정 테이블이나 공통 상수로 관리하는 게 낫다. 쿼리가 약간 복잡해지는 트레이드오프는 있지만, 수수료 정책 변경이 잦은 도메인이라면 그 복잡도 비용이 훨씬 작다. 매퍼 기반 프로젝트라면 조건값을 파라미터로 빼거나, 설정 조회 서브쿼리를 한 군데 두고 재사용하는 방식도 있다. 관리 포인트가 1개로 줄어드는 것만으로도 이런 류의 버그를 원천 차단할 수 있다.
금융 도메인 작업을 계속 하다 보면 "대략 맞는 것 같다"는 감각을 믿으면 안 된다는 걸 반복해서 배운다. 100건 중 99건이 맞아도 1건이 틀리면 그게 반드시 민감한 케이스일 가능성이 높다. 정산 숫자가 틀리면 신뢰 문제가 되고, 신뢰 문제는 기능 추가로 복구되는 게 아니다.
수정 후 검증 기준을 "돌아가는지"가 아니라 "숫자가 맞는지"로 잡는 게 기본이 되어야 한다는 것. 그리고 그 숫자를 증명하는 건 눈으로 훑는 게 아니라 다른 화면, 다른 집계 경로와 cross-check하는 것이다.
이번 수정은 SQL 매퍼 1개짜리 변경이었지만, 그 1개를 확신하기까지 비교하고 확인하는 단계가 훨씬 길었다. 그게 맞는 흐름이라고 생각한다. 고치는 데 5분, 확인하는 데 30분이어도 그 30분이 나중에 훨씬 큰 시간 낭비를 막는다.
작은 커밋을 논리 단위로 쪼개는 습관도 같은 맥락이다. 문제가 생겼을 때 어느 변경에서 깨졌는지 찾는 시간을 줄이는 것. 커밋이 작을수록 버그 탐색이 git bisect 한 번으로 끝나는 경우도 생긴다. "무엇을 바꿨는지"보다 "왜 바꿨는지"를 커밋 메시지에 담으려는 것도 같은 이유에서다. 나중에 이 커밋을 다시 봤을 때, 당시의 컨텍스트가 메시지 안에 있어야 판단 근거가 남는다.
댓글 0
첫 댓글 달아줘.