계층별 수수료 정산 로직 검증 강화
목차
수수료 정산 로직에 손댄 날이었다. 거래 금액 대비 비율 수수료와 건당 고정 수수료를 합산해서 계층별로 나눠 정산하는 구조인데, 겉으로 보면 단순하지만 건드릴수록 조심스러운 영역이다. 금전 관련 로직은 오류 하나가 곧바로 숫자로 드러나고, 숫자가 틀리면 신뢰 손상이 먼저 온다.
이번 작업의 핵심은 두 가지였다. 계층별 요율 순서에 대한 validation 추가, 그리고 일일 정산 결과를 Discord Embed로 발송하는 리포트 기능. 규모는 크지 않았지만 운영 안정성을 위해 필요한 변경이었다.
계층 수수료 구조와 마진 역전 문제
수수료 구조는 유통 계층별로 요율이 다르게 설정되는 차등 모델이다. 하위 계층이 상위 계층보다 높은 요율을 부담하고, 그 차액이 상위 계층의 수익이 된다.
| 계층 | 요율 | 비고 |
|---|---|---|
| 최하위 | 1.0% | 최대 부담 |
| 중간 | 0.8% | 차액 0.2% 수익 |
| 최상위 | 0.6% | 차액 0.2% 수익 |
실제 수수료 계산은 비율 수수료와 건당 고정 수수료를 합산한다.
// 비율 수수료 + 건당 고정 수수료 합산
long feeAmount = (long)(txAmount * feeRate) + perTxFee;
단순해 보이지만 (long) 캐스팅 시점에 소수점 이하가 버려진다. 거래 건수가 많아지면 그 버림 오차가 누적된다. 반올림 정책은 명시적으로 정해두는 게 낫다. 일반적으로 수수료를 내는 쪽은 내림 처리해서 덜 내게 하고, 받는 쪽은 올림 없이 정확한 금액만 받는 방향이 분쟁을 줄인다. 양쪽 다 유리하게 처리하면 총액이 맞지 않고, 양쪽 다 불리하게 처리하면 민원이 생긴다.
계층 구조에서 조심해야 할 건 마진 역전이다. 하위 계층 요율이 상위 계층 요율보다 낮거나 같아지면 차액이 0 또는 음수가 된다. 설정값 실수로 이게 발생해도 코드가 그냥 통과해버리면, 상위 계층이 수익 대신 손해를 보는 상황이 배치 실행 후에야 발견된다. 그 시점엔 이미 정산이 완료된 뒤라 되돌리는 게 훨씬 복잡해진다.
왜 validation을 배치 실행 직전에 넣었나
요율 설정은 DB나 설정 파일에서 읽어오는 구조인데, 입력 시점에 검증이 있더라도 배치가 실제로 돌기 직전에 한 번 더 확인하는 게 맞다고 봤다. 설정 입력 단계의 validation과 실행 단계의 validation은 목적이 다르다. 전자는 잘못된 값이 저장되는 걸 막는 것이고, 후자는 어떤 경로로든 이상한 값이 들어왔을 때 정산 자체를 막는 안전장치다.
추가한 검증 포인트는 이렇다.
- 계층 내 요율이 음수이거나 0인 경우
- 하위 계층 요율이 직상위 계층 요율 이하인 경우 (마진 역전)
- 건당 고정 수수료가 음수인 경우
세 번째는 당연해 보이지만, 타입이 허용한다면 막아두는 게 낫다. 있을 수 없는 상태를 코드가 그냥 통과시키면 나중에 원인 파악이 어려워진다. 하나라도 걸리면 해당 정산 배치는 실행되지 않고 오류 로그를 남긴다. Discord Embed 리포트에도 실패 사유가 포함되게 해서 모니터링하는 쪽에서 바로 확인할 수 있도록 했다.
정산 배치의 멱등성과 Discord 리포트
정산 배치에서 멱등성은 거의 필수 조건이다. 같은 기간을 두 번 실행했을 때 결과가 달라지면 어느 쪽이 맞는지 판단하기 어려워진다. 배치가 중간에 실패해서 재실행할 때 이미 처리된 건이 다시 처리되는 상황을 막으려면 처리 상태를 트랜잭션과 함께 기록해둬야 한다.
멱등성을 확보하는 일반적인 방법은 처리 단위마다 상태를 명시적으로 관리하는 것이다. "미처리 - 처리중 - 완료 - 실패" 같은 상태 컬럼을 두고, 배치가 시작할 때 "미처리" 상태인 건만 집계 대상으로 잡는다. 처리 도중 서버가 죽으면 "처리중" 상태가 남는데, 이때 타임아웃 기준을 정해두지 않으면 해당 건이 영구적으로 애매한 상태로 남는다. 일정 시간이 지난 "처리중" 건은 재처리 대상으로 돌리거나, 관리자 확인이 필요한 예외로 분류하는 것 중 하나를 명시적으로 선택해야 한다.
이번에 추가한 Discord Embed 리포트는 정산 배치가 끝날 때마다 결과를 채널로 발송하는 형태다. 정산 금액 합계, 처리 건수, 계층별 수수료 합계, 실패 건수 같은 정보를 포함한다. 배치는 주기적으로 돌기 때문에 결과를 매번 로그에서 찾는 것보다 채널에서 한눈에 확인하는 게 운영 효율이 높다. 이상한 수치가 보이면 그 자리에서 바로 스레드를 열고 확인할 수 있다.
작업 자체는 크지 않았지만, 이런 류의 보강이 쌓이면 배치 오류가 생겼을 때 원인 파악 시간이 줄어든다. 금전 관련 배치일수록 조용히 돌다가 조용히 틀리는 상황을 막는 게 핵심이다.
댓글 0
첫 댓글 달아줘.