개발 slecs

파트너 포털 정산 마감잔액 정책 일원화

목차

파트너 포털에서 마감잔액이 화면마다 다르게 찍히는 문제가 있었다. 처음엔 렌더링 타이밍 문제겠지 싶었는데, 파고 들어가니 DB에서 잔액 컬럼을 가져오는 기준 자체가 제각각이었다.

SQL 매퍼 하나는 A 컬럼을 읽고, 내부 클래스는 조건 분기 끝에 B 컬럼을 fallback으로 쓰고, 뷰단에서는 또 다른 집계 결과를 뿌리는 구조. 각자 "맞는" 컬럼을 쓴다고 생각하면서 독립적으로 작성됐을 텐데, 특정 엣지 케이스(마감 기준일이 정산 주기 경계에 걸리거나 잔액이 0인 상태에서 집계되는 케이스)에서 세 값이 미묘하게 달랐다.

왜 잔액 컬럼이 여러 개가 됐나

정산 도메인은 시간이 지날수록 컬럼이 늘어나는 경향이 있다. 초기에는 단순한 balance 하나로 시작해도, 마감 처리가 생기면 closing_balance, 중간 정산이 붙으면 estimated_balance, 환불 케이스가 추가되면 net_balance... 이런 식으로 점점 파생된다. 각 컬럼이 생길 때 "이건 이 화면에서만 쓰는 거니까 괜찮아"라는 생각으로 국소적으로 추가됐을 가능성이 높다.

문제는 시간이 지나면 어떤 화면이 어떤 컬럼을 보고 있는지 추적이 안 된다는 것. 신규 입사자는 물론이고 작성자 본인도 3개월 후엔 헷갈린다. 그러다 마감 정책이 바뀌면, 일부 화면은 업데이트되고 일부는 빠진 채로 나가게 됨.

이번 수정의 핵심은 "어떤 컬럼이 정답인가"를 결정하고, 모든 경로가 동일한 DB 원본을 참조하도록 통일하는 것이었다.

수정 범위와 접근 방식

변경된 파일은 네 곳 - 설정/문서 1개, 내부 클래스 1개, SQL 매퍼 1개, 뷰/스타일 1개. 파일 수만 보면 작아 보이지만, 각 레이어를 순서대로 따라가며 정합성을 맞춰야 해서 수정 난이도는 단순하지 않았다.

수정 순서를 대략 정리하면 아래와 같다:

  • SQL 매퍼부터 - 어떤 컬럼을 기준으로 집계할지 결정. 마감잔액 기준을 명확히 하고, 엣지 케이스(잔액 0, 마감일 경계)에서 집계 조건이 의도대로 동작하는지 확인.
  • 내부 클래스 - SQL 결과를 받아서 처리하는 로직 수정. 기존에 fallback 컬럼을 참조하던 분기를 제거하고 단일 소스만 보도록 정리.
  • 뷰/스타일 - 화면에 표시되는 값이 수정된 집계 결과와 일치하는지 확인 후 렌더링 수정. 프론트 스크립트도 여기서 함께.
  • 설정/문서 - 정책 변경 내용 반영.

SQL 쪽에서 가장 많이 신경 쓴 부분은 집계 조건의 NULL 처리와 경계값이었다. 핵심만 추리면 이런 패턴이었음:

-- 수정 전: NULL일 때 집계에서 누락되는 케이스 존재
SELECT SUM(closing_balance)
FROM settlements
WHERE closed_at <= :targetDate

-- 수정 후: NULL과 0 구분, 마감 기준일 inclusive 처리, 상태 조건 추가
SELECT COALESCE(SUM(COALESCE(closing_balance, 0)), 0)
FROM settlements
WHERE closed_at <= :targetDate
  AND status IN ('CLOSED', 'FINALIZED')

SUM(NULL)이 0이 아니라 NULL을 반환한다는 점, 상태값 조건이 없으면 진행 중인 건까지 집계에 포함된다는 점이 이번 엣지 케이스의 핵심이었다. 실제 쿼리는 이것보다 복잡하지만 방향은 동일했다.

금융 도메인 버그의 특성

일반 기능 버그는 "화면이 안 나온다", "버튼이 안 눌린다" 같이 재현이 비교적 명확하다. 하지만 금액 집계 버그는 다름. 대부분의 케이스에서는 정상으로 보이다가 특정 조건(마감 처리 직후, 환불 건이 섞인 케이스, 잔액이 정확히 0인 상태)에서만 틀린 값이 나온다. 틀렸다는 것 자체를 알아채기까지 시간이 걸리고, 알아챈 시점에는 이미 여러 화면에 잘못된 값이 누적되어 있을 수 있다.

그래서 단순히 증상만 픽스하는 게 아니라 왜 발생했는지 원인을 먼저 파악했고, 비슷한 패턴이 다른 경로에도 있는지 확인했다. 이번엔 뷰와 API 경로 두 군데서 동일한 패턴이 있었고, 둘 다 수정했음.

버그 수정 시 개인적으로 챙기는 체크 포인트:

  • 같은 로직이 다른 경로에도 있는지 - 중복 코드 체크
  • 수정이 기존 정상 케이스를 망가뜨리지 않는지 - 잔액 있는 케이스, 없는 케이스, 여러 건이 묶인 케이스 각각 확인
  • 숫자를 관련 화면과 cross-check - 목록 화면 합산과 상세 화면 값이 일치하는지
  • 커밋 단위 - SQL 수정, 클래스 로직 수정, 뷰 수정으로 분리해서 어느 변경이 어떤 동작을 바꿨는지 추적 가능하게

엣지 케이스를 꼼꼼히 따지는 게 귀찮아 보여도, 나중에 같은 버그로 다시 오는 시간 비용이 훨씬 크다. 특히 금융/정산 도메인은 숫자 하나가 틀리면 기술적으로 사소한 버그가 신뢰 문제로 번진다. "대충 맞는 것 같다"로 넘어가면 반드시 다시 돌아옴.


사내 서비스를 만들다 보면 기능 하나가 단순히 버튼 하나 추가로 끝나지 않는다는 걸 계속 체감한다. SQL 집계, 상태 머신, 예외 처리, 화면 렌더링, 권한 체크가 모두 엮여 있어서 어느 하나만 빠뜨려도 숫자가 맞지 않거나 특정 케이스에서 이상한 화면이 나옴. 이번 수정도 파일 4개지만 각 레이어를 따라가며 정합성을 검증하는 과정이 작업의 대부분이었다.

변경 전 해당 화면 수치를 메모해두고 수정 후 동일 케이스로 재확인하는 습관이 이번에도 유효했다. 엣지 케이스는 의도적으로 만들어서 테스트했고, 커밋을 논리 단위로 쪼개는 것도 실질적으로 도움이 됐다. SQL 수정 후 집계가 맞는 걸 확인하고 커밋, 그다음 클래스 수정 후 확인하고 커밋 - 이렇게 가면 나중에 "이 커밋 이전까지는 괜찮았다"를 찾아가기가 훨씬 빠르다. 한 번에 몰아서 커밋하면 그 추적이 불가능해진다.

댓글 0

첫 댓글 달아줘.