결제대행사 등급 동기화가 기본등급으로 폴백되던 버그 수정
목차
결제대행사에서 내려주는 사용자 등급을 시스템이 동기화하는 로직, 생각보다 많은 게 얽혀 있다. 단순히 "API 응답 값 저장"이 아니라 - webhook이 들어오는 타이밍, SQL 매퍼가 집계하는 방식, 폴백 조건이 맞물린다. 이번 버그는 그 폴백 경로가 잘못 발동하던 케이스였다.
결제대행사는 특정 이벤트 발생 시 webhook으로 등급 정보를 밀어준다. 시스템 입장에서는 이 값을 받아 DB에 반영하고, 이후 조회 시 해당 등급을 사용해야 한다. 그런데 특정 조건에서 "결제대행사가 내려준 등급" 대신 "시스템 기본등급"이 반환되는 상황이 발생했다. 사용자 입장에선 자기 등급이 틀리게 보이거나, 기능 접근 권한이 잘못 판단되는 것과 직결되는 문제다.
원인 파악
변경 파일은 SQL 매퍼 하나였다. 처음엔 작은 변경처럼 보였는데, 뜯어보니 집계 쿼리의 조건 처리에서 엣지 케이스가 누락되어 있었다.
등급 동기화 이력이 존재하지 않거나, 혹은 특정 상태값을 가진 행만 남아 있는 경우, 집계 결과가 NULL 또는 의도치 않은 값으로 내려왔다. 애플리케이션 레이어에서는 이 값이 비어 있으면 기본등급으로 폴백하도록 설계되어 있었는데 - 원래는 합리적인 방어 코드였지만, 결제대행사가 정상적으로 등급을 내려줬음에도 SQL이 그 값을 제대로 집계하지 못하니 폴백이 잘못 발동한 것이다.
-- 수정 전 (단순화한 예시)
SELECT grade_code
FROM user_grade_sync
WHERE user_id = #{userId}
ORDER BY synced_at DESC
LIMIT 1;
-- 수정 후
SELECT grade_code
FROM user_grade_sync
WHERE user_id = #{userId}
AND sync_status = 'APPLIED' -- 유효한 상태 조건 명시
AND grade_code IS NOT NULL -- NULL 행 제외
ORDER BY synced_at DESC
LIMIT 1;
위는 단순화한 예시지만, 핵심은 "어떤 행을 유효한 동기화 결과로 볼 것인가"를 쿼리 안에서 명확히 정의하지 않았다는 점이다. 집계 레이어에서 걸러야 할 것을 애플리케이션 레이어 폴백에 떠넘긴 구조였고, 그 폴백이 오히려 잘못된 결과를 만들었다.
"폴백이 있으니 안전하다"는 전제 자체가 독이 되는 상황. 방어 코드는 진짜 예외 상황을 위한 것이지, 집계 레이어의 부정확함을 덮는 용도로 쓰이면 안 된다. 이번 케이스가 그걸 다시 확인시켜줬다.
버그 수정 프로세스
증상만 픽스하면 같은 버그가 다른 형태로 돌아온다. 이번엔 원인 파악 후 아래 순서로 진행했다.
- 같은 패턴의 SQL 매퍼가 다른 곳에도 있는지 훑었다. 비슷한 집계 구조를 쓰는 파일 몇 개를 더 확인했고, 위험한 케이스 하나를 함께 정리했다.
- 수정이 정상 케이스를 건드리지 않는지 확인했다. 등급 이력이 정상적으로 존재하는 유저, 이력이 아예 없는 유저, 특정 상태만 남은 유저 - 케이스를 나눠서 쿼리 결과를 직접 확인했다.
- 수정 후 버그를 직접 재현해서 더 이상 폴백이 잘못 발동하지 않는 걸 확인했다.
- 등급이 노출되는 관련 화면과 숫자를 cross-check했다.
| 확인 항목 | 방법 |
|---|---|
| 동일 패턴 SQL 중복 여부 | 매퍼 파일 전수 검색 |
| 정상 케이스 회귀 여부 | 케이스별 쿼리 직접 실행 |
| 버그 재현 후 수정 확인 | 수정 전/후 결과 비교 |
| 숫자 정합성 | 관련 화면 cross-check |
엣지 케이스를 꼼꼼히 따지는 게 귀찮아 보여도, 나중에 같은 버그로 다시 오는 시간 비용이 훨씬 크다. 특히 결제/금융 도메인은 숫자 하나가 틀리면 신뢰가 바로 무너진다. "대충 맞는 것 같다"로 넘기면 반드시 다시 돌아온다.
작업 후기
사내 서비스를 만들다 보면 기능 하나가 단순히 화면에 뭔가 추가하는 걸로 끝나지 않는다는 걸 계속 체감한다. SQL 집계, 상태 머신, 예외 처리, 화면 렌더링, 권한 체크가 모두 엮여 있어서 어느 하나만 빠뜨려도 숫자가 맞지 않거나 특정 사용자에게 이상한 화면이 나온다.
개발 습관으로는 이런 것들을 계속 유지하려 한다.
- 변경 전 현재 동작과 수치를 메모해두기
- 수정 후 같은 케이스로 다시 확인
- 관련 화면이 있으면 숫자 cross-check
- 커밋 메시지는 "무엇을"보다 "왜"를 담으려 노력
작은 커밋을 논리적 단위로 쪼개는 습관도 계속 유지 중이다. 문제가 생겼을 때 어느 변경에서 깨졌는지 찾는 게 훨씬 쉬워진다. 이번처럼 SQL 매퍼 1개만 바꾼 커밋도, 변경 이유가 커밋 메시지에 담겨 있으면 나중에 히스토리를 볼 때 맥락을 다시 복원하는 수고가 없다.
webhook 하나, SQL 매퍼 하나짜리 버그지만 - 결제 등급이 잘못 동기화되면 그건 인프라 문제가 아니라 사용자 경험 문제가 된다. 숫자를 다루는 도메인일수록, 작은 픽스도 꼼꼼하게.
댓글 0
첫 댓글 달아줘.