개발 slecs

출금 실패 시 잔액 미복원 버그 수정과 회원 엑셀 컬럼 보강

목차

출금 실패 후 잔액이 복원되지 않는 버그를 잡았다. 동시에 회원 엑셀 다운로드 컬럼도 보강했는데, 작업 규모는 작지만 둘 다 운영 임팩트가 꽤 큰 변경이었다.


왜 이 버그가 위험한가

결제 시스템에서 출금(pay-withdraw) 흐름은 대략 이렇게 생겼다.

  1. charge_balance(충전 잔액)에서 금액 차감
  2. 외부 출금 요청 실행
  3. 출금 성공 → 완료 처리 / 출금 실패 → 차감분 복원(롤백)

여기서 3번 실패 분기의 복원 로직이 빠져 있었다. 즉, 출금이 외부에서 실패해도 내부 잔액은 이미 깎인 채로 남아 있었던 것이다.

[정상 흐름]
charge_balance 차감 → 출금 성공 → 완료

[버그 상태]
charge_balance 차감 → 출금 실패 → ❌ 복원 없음 → 잔액 소실

[픽스 후]
charge_balance 차감 → 출금 실패 → ✅ charge_balance 복원 → 실패 처리

이런 유형의 버그는 트랜잭션 경계를 어디서 끊느냐에 따라 자주 발생한다. 외부 API 호출이 포함된 흐름에서는 DB 트랜잭션 하나로 묶는 게 불가능하거나 권장되지 않기 때문에, 실패 분기마다 보상 트랜잭션(compensating transaction) 을 명시적으로 작성해야 한다. 그걸 누락하면 이번처럼 잔액이 음지에서 조용히 사라진다. 로그에도 에러는 찍히지만 잔액 불일치 자체는 별도로 알람이 없으면 한참 뒤에야 발견되는 경우가 많다.

변경된 파일들이 하는 일

파일 역할 이번 변경 의미
pay/web/내부 클래스 출금 요청·응답 컨트롤러 실패 분기에 잔액 복원 로직 추가
payment/web/내부 클래스 결제 전반 처리 컨트롤러 연관 흐름 정합성 확인 및 수정
ExcelConfigRegistry.java 엑셀 컬럼 설정 레지스트리 회원 엑셀에 누락된 컬럼 추가
pay/쿼리 매퍼 출금 관련 SQL 복원용 UPDATE 쿼리 추가

paypayment 두 패키지가 나뉘어 있다는 점도 눈여겨볼 부분이다. 이런 구조에선 출금 로직이 어느 레이어에 있는지 명확히 하지 않으면 비슷한 버그가 다른 쪽에도 잠복해 있을 수 있다. 이번 기회에 payment 쪽 흐름도 같이 훑어봤다.

ExcelConfigRegistry 와 컬럼 보강

ExcelConfigRegistry.java는 엑셀 다운로드 시 어떤 컬럼을 어떤 순서로 내보낼지 중앙에서 관리하는 설정 클래스다. 이런 레지스트리 패턴을 쓰면 각 기능별로 컬럼 정의가 흩어지지 않아서 유지보수가 편하다는 장점이 있다.

이번에 회원 엑셀에 컬럼이 보강됐는데, 운영자 입장에서는 "다운로드했더니 내가 보려는 항목이 없다"는 게 생각보다 큰 불편함이다. 특히 운영 담당자들이 엑셀로 데이터를 뽑아서 직접 가공하는 경우가 많기 때문에, 컬럼 하나 빠진 게 실제 업무 플로우 전체를 막아버리기도 한다. 작은 변경이지만 운영 현장 영향은 무시 못 한다.

회고

이번 버그의 근본 원인은 코드 리뷰 단계에서 실패 분기를 충분히 따라가지 않은 것이다. 결제 관련 코드 리뷰를 할 때는 "성공 케이스" 외에 반드시 다음 체크리스트를 같이 보도록 팀 내 기준을 세울 필요가 있다고 느꼈다.

  • 외부 호출 실패 시 내부 상태가 원래대로 돌아오는가
  • 부분 성공(외부 성공, DB 실패 또는 그 반대) 케이스가 처리되어 있는가
  • 실패 로그만으로 잔액 불일치를 사후에 추적할 수 있는가

팀원들한테도 이번 케이스를 예시로 공유했다. "출금 성공 경로만 그려놓고 실패 분기는 Exception 던지고 끝" 이런 패턴이 생각보다 자주 나온다. 커밋 메시지에 "미복원 버그"라고 명시한 것도 나중에 히스토리 볼 때 원인 파악이 빠르도록 의도한 거다.

다음에 비슷한 출금/충전 흐름 작업이 들어오면 보상 트랜잭션 설계를 초기부터 리뷰 항목에 넣을 것.

댓글 0

첫 댓글 달아줘.