파트너 포털 잔액변동 상세 패널의 카드 hold 만료건 누락 수정
목차
파트너 포털 잔액변동 상세 패널에서 카드 hold 만료건이 누락되던 버그를 수정했다. 변경 파일은 SQL 매퍼 1개. 커밋 자체는 작지만, 원인 파악하고 검증하는 과정이 훨씬 길었다.
왜 hold 만료건이 빠졌나
카드 hold는 결제 흐름에서 꽤 특수한 상태다. 승인(authorized)이 났지만 아직 청구(captured)되지 않은 금액을 묶어두는 것이고, 일정 기간이 지나면 자동으로 만료(expired)된다. 이 만료 시점에 잔액이 다시 풀리는데, 잔액변동 패널에서는 이 케이스를 별도로 잡아줘야 한다.
문제는 SQL 매퍼의 조건 혹은 집계 로직에서 이 만료 상태를 처리하는 분기가 빠져 있었다는 것이다. 더 나쁜 건, 그 처리가 누락되는 게 예외로 터지지도 않았다는 점이다. 쿼리가 조용히 해당 건을 제외하고 결과를 반환했고, 로그에는 아무것도 안 남았다.
이런 종류의 버그는 증상만 보면 "데이터가 없다"로 읽힌다. 누가 hold 만료 케이스를 화면에서 직접 확인하지 않으면 실제로 누락인지, 원래 없는 건지 구분이 안 된다. 오래된 버그일수록 "원래 그런 거 아닌가?" 하고 넘어가기 쉽다.
SQL 집계 쿼리에서 상태(status) 조건을 빠뜨리거나, GROUP BY 기준이 hold 만료 레코드의 컬럼 구조와 맞지 않을 때 이런 일이 생긴다. 단순화하면 아래 같은 구조다:
-- 수정 전: HOLD_EXPIRED 상태가 IN 조건에서 빠져 있음
SELECT
tx_date,
SUM(amount) AS total_change
FROM balance_history
WHERE status IN ('CAPTURED', 'REFUNDED')
GROUP BY tx_date
-- 수정 후: 만료건을 명시적으로 포함
SELECT
tx_date,
SUM(amount) AS total_change
FROM balance_history
WHERE status IN ('CAPTURED', 'REFUNDED', 'HOLD_EXPIRED')
GROUP BY tx_date
실제 쿼리는 이것보다 복잡하지만 핵심은 같다. HOLD_EXPIRED가 조건에서 빠지면 해당 건은 아무 경고 없이 집계에서 제외된다. 쿼리 자체는 정상적으로 실행되고, 반환 행 수도 "정상" 범위 안이다. 문제를 터뜨릴 트리거가 없다는 게 핵심이다.
원인 파악 없는 픽스는 반쪽이다
조건 하나 추가하고 끝낼 수도 있었다. 근데 그 전에 몇 가지를 더 확인했다.
첫 번째는 같은 패턴이 다른 곳에도 있는지다. 잔액변동 패널 외에 유사한 집계를 사용하는 화면이나 API가 있다면, 거기서도 같은 누락이 생길 수 있다. 한 군데 발견했다는 건 코드베이스 전체에서 같은 실수가 반복됐을 가능성이 있다는 신호다. 이번 케이스는 해당 화면 특화 매퍼라 다른 경로에 직접적인 영향은 없었지만, 유사한 상태 조건을 쓰는 쿼리들은 따로 훑어봤다.
두 번째는 수정이 기존 동작을 깨뜨리지 않는지다. 잔액 집계는 조건 하나 바꾸면 이전에 정상적으로 잡히던 건이 중복 계산되거나 반대로 제외되는 경우가 생길 수 있다. 특히 HOLD_EXPIRED가 다른 상태와 날짜 기준이 다르게 처리되고 있었다면 단순히 IN 조건에 추가하는 것만으로는 부족하다. 이 부분은 수정 전후 수치를 직접 비교해서 확인했다.
버그 수정 시 확인 흐름을 정리하면:
| 체크 항목 | 이유 |
|---|---|
| 같은 로직이 다른 경로에도 있는지 | 동일 버그 재발 차단 |
| 수정이 기존 정상 케이스를 망가뜨리지 않는지 | 회귀 방지 |
| 해당 화면 또는 API에서 실제 동작 확인 | 픽스가 실제로 동작하는지 |
| 관련 화면과 숫자 cross-check | 데이터 정합성 |
이 흐름이 귀찮아 보이는 건 맞다. 근데 같은 버그로 두 번 돌아오는 쪽이 시간적으로 훨씬 비싸다. 특히 금융 도메인에서는 한 번 "숫자가 이상해요"가 나오면 어디서 깨졌는지 다시 추적하는 데 드는 비용이 크다.
검증과 커밋 습관
수정 후에는 hold 만료 케이스를 직접 재현해서 상세 패널에 정상적으로 나타나는지 확인했다. 그리고 관련 화면에서 잔액 합산 수치가 맞는지 비교했다. "아마 맞겠지"로 넘기면 검증한 게 아니다.
커밋 메시지는 무엇을 바꿨는지보다 왜 바꿨는지를 담으려고 한다. fix SQL condition for HOLD_EXPIRED 보다는 HOLD_EXPIRED omitted from balance history query, causing silent gap in partner portal detail panel 쪽이 나중에 git blame 했을 때 맥락이 바로 보인다. 파일 하나 바꾼 커밋이라도 메시지가 부실하면 몇 달 뒤에 "왜 이걸 바꿨지?" 하고 다시 파야 한다.
작은 커밋을 자주 하는 습관도 같은 이유다. 이번처럼 SQL 매퍼 파일 하나 수정이면 커밋 하나로 끝나지만, 여러 파일을 건드리는 작업이라면 논리적으로 독립된 단위로 쪼개는 편이다. 그래야 나중에 어느 변경에서 뭔가 깨졌는지 bisect 하기가 훨씬 쉽다.
사내 서비스를 만들다 보면 기능 하나가 SQL 집계, 상태 머신, 예외 처리, 화면 렌더링, 권한 체크가 모두 맞물려 있다는 걸 반복해서 체감한다. 어느 하나가 빠지면 숫자가 틀리거나 특정 사용자에게 이상한 화면이 나온다. 금융/결제 도메인에서 숫자 하나가 틀리면 신뢰가 흔들린다는 건 들을 때는 뻔하게 들리지만, 실제로 그렇다. "대충 맞는 것 같다"가 아니라 "검증했다"가 기본값이어야 한다.
예외가 소리 없이 삼켜지는 코드, 특정 상태값을 빠뜨린 IN 조건, 로그 한 줄 안 남기는 누락, 이런 것들이 쌓이면 운영하면서 가장 피곤한 종류의 버그가 된다. 겉으로는 멀쩡한데 숫자가 안 맞는 상황. 이번 건은 작은 수정이었지만, 잡는 과정에서 왜 이런 버그가 조용히 오래 살아남는지 다시 확인한 케이스였다.
댓글 0
첫 댓글 달아줘.