파트너 이용수수료 관리 화면 도입으로 운영 DB 직접 수정 제거
목차
운영 DB를 콘솔로 직접 수정하는 게 얼마나 위험한지는 다들 알면서도 "당장 급하니까"로 버티다가 어느 순간 폭발한다. 이번 파트너 이용수수료 관리 화면이 딱 그 케이스였음.
이커머스 결제 플랫폼 특성상 파트너마다 요율이 다르고, 같은 파트너라도 등급이 올라가거나 프로모션 기간에 따라 달라지는 구조. 근데 이걸 조작하는 UI가 아예 없었음. 요율 바꿔달라는 슬랙 메시지 오면 개발자가 직접 DB 들어가서 UPDATE 쳤던 거임. 오타 한 번이면 정산 전체가 뒤집어질 수 있는 구조인데, 리뷰 없이 개발자 판단에 다 맡겨두고 있었던 셈.
왜 이게 문제였나
단순히 "불편하다"가 아니라 구조적으로 무너질 수 있는 포인트가 세 군데 있었음.
첫째, 파트너 등급 다양화. 초기엔 단일 요율이었는데 충전·결제·판매대금 정산 세 가지로 쪼개지면서 UPDATE 한 줄로 끝나지 않는 상황이 됐음. 컬럼 하나 잘못 건드리면 어디서 틀렸는지도 바로 안 보임.
둘째, 변경 이력 없음. 분쟁이 생겼을 때 "이 날짜 정산이 왜 이 금액이냐"를 역추적하려면 변경 타이밍과 값을 알아야 하는데, 그게 어디에도 안 남았음. git blame처럼 추적할 수 있는 게 없으니 재무팀은 매번 개발자한테 기억을 캐물어야 했음.
셋째, 개발자 호출 빈도 증가. 처음엔 한 달에 두어 번이던 게 파트너가 늘면서 주 단위로 올라왔음. 개발자 입장에선 집중 흐름이 끊기고, 운영팀 입장에선 처리가 늦어지니 양쪽 다 스트레스.
세 개가 겹치는 시점에 "지금 당장 만들어야겠다"로 결론났음.
설계할 때 고민한 것들
요율 구조를 먼저 정의해야 화면이 나왔음. 충전·결제·정산 세 항목이 각각 단가 형태도 다르고 적용 시점도 달라서 단일 컬럼으론 못 담음.
| 항목 | 단가 형태 | 적용 시점 |
|---|---|---|
| 충전수수료 | %+고정금액 | 충전 요청 시 |
| 결제수수료 | % | 결제 확정 시 |
| 판매대금 정산 | % | 홀딩 해제 시 |
이걸 유형별 매트릭스로 뽑으면서 테이블 구조가 한 번 더 바뀜. 기존엔 파트너 테이블 컬럼 몇 개에 요율값 때려박던 걸, partner_fee_rate 같은 별도 테이블로 분리하고 fee_type 컬럼으로 행을 나눴음. 조회 쿼리는 좀 복잡해졌지만 타입별로 이력을 독립적으로 추적할 수 있게 된 게 훨씬 중요했음.
가장 헷갈린 건 홀딩 중 취소 처리. PENDING 상태로 잡혀 있다가 CANCELLED로 전환되면 잔액 변동이 0이어야 하는데, 기존 코드엔 충전 시점에 수수료를 즉시 차감하는 로직이 섞여 있었음. 취소가 나면 차감했던 수수료를 돌려줘야 하는데 그 흐름이 명확하지 않았고, 엣지케이스에서 간혹 잔액이 어긋나는 버그가 이미 존재했음. 화면 만드는 김에 이 부분 같이 걷어내고, 수수료는 거래 확정 시점에만 계산하도록 통일했음. DB 건드리는 김에 리팩토링까지 묶는 건 범위가 늘어나는 리스크가 있지만, 이 경우엔 안 하면 화면 만들어도 계산 결과를 신뢰할 수 없으니 같이 처리했음.
요율 변경 적용 시점도 두 케이스가 동시에 필요했음. 즉시 반영(오류 정정 같은 케이스)과 다음 정산 주기부터 반영(등급 업그레이드처럼 예고된 변경). 이걸 하나의 UI에 담으려면 변경 예약 개념이 들어가야 함. 결국 effective_from 컬럼을 따로 빼고, 조회할 때 해당 시점 기준으로 가장 최근의 유효한 요율을 가져오는 방식으로 통일했음.
SELECT *
FROM partner_fee_rate
WHERE partner_id = :partnerId
AND fee_type = :feeType
AND effective_from <= :targetDate
ORDER BY effective_from DESC
LIMIT 1;
단순해 보이지만 이 패턴 하나로 "지금 요율", "내일 적용될 예약된 요율", "3개월 전 정산 당시 요율"을 전부 같은 쿼리로 커버할 수 있음. 히스토리 테이블 따로 안 만들고 이 테이블 자체가 이력이 되는 구조. 단, 예약 삭제나 수정이 가능하게 할 거라면 soft delete랑 버전 관리를 같이 고려해야 함. 이번엔 예약 삭제는 넣고 수정은 "삭제 후 재등록"으로 제한해서 복잡도를 낮췄음.
권한 분리도 명확하게 잡았음. 처음엔 관리자 권한 하나로 다 열려 있었는데, 요율 변경은 재무 영향이 크니까 승인 단계를 끼워넣는 게 맞다고 판단했음.
조회(운영팀) → 변경안 작성(운영팀) → 재무 승인 → 예약 적용
이 플로우를 한 화면에 다 녹이려다가 초기 구현에서 상태 관리가 지저분해졌음. 현재 요율, 예약된 변경, 과거 이력을 탭으로 나눴는데 탭 간에 데이터가 연동되는 케이스가 생기면서 컴포넌트 상태가 꼬임. 결국 두 덩어리로 쪼갬. 요율 조회·이력 표시 담당 컴포넌트 따로, 변경 작성·승인 플로우 담당 컴포넌트 따로. 처음엔 한 화면에 욱여넣었다가 테스트 두 번 만에 포기한 케이스. 작게 쪼개는 게 정답인 건 알고 있는데 실제로는 귀찮아서 뭉쳐두다가 결국 다시 나누는 패턴을 또 반복했음.
결과와 남은 것
가장 큰 수확은 운영 DB 직접 수정이 사라진 것. 배포 직후 운영팀에서 "이제 우리가 직접 한다"는 반응이 나왔고, 개발자 호출 티켓이 한 주 만에 체감상 눈에 띄게 줄었음. 이전까진 요율 변경 메시지 오면 개발자가 컨텍스트 스위칭해서 DB 들어가고, 실수 없나 확인하고, 완료 알림 보내는 흐름이었는데 이게 통째로 사라졌음.
변경 이력이 쌓이기 시작하니까 분쟁 발생 시 "그 날짜에 누가 무슨 요율로 변경했는지"를 바로 꺼내볼 수 있게 됐음. 재무팀에선 이게 제일 좋다는 피드백이 왔음. 이전엔 슬랙 로그 뒤지고 개발자 기억 캐물어서 맞춰보던 걸 이제 UI에서 직접 조회할 수 있으니까.
운영 자동화의 역설이 이런 것 같음. 화면 하나 만드는 게 기술적으로 어려운 작업은 아님. 근데 만들기로 결정하는 데 시간이 가장 오래 걸림. "그냥 DB 직접 치면 되지"라는 관성이 있고, 우선순위에서 항상 뒤로 밀림. 막상 만들고 나면 왜 진작 안 했나 싶은 것들이 대부분임.
다음 스텝으로는 변경 예약 알림(특정 시간 이후 적용될 요율이 있으면 사전 안내)과 요율 시뮬레이터를 붙여볼 생각. 시뮬레이터는 변경 전에 이번 달 정산에 얼마나 영향 주는지 숫자로 미리보여주는 기능인데, 정산 계산 로직을 화면 단에서 재현해야 해서 의외로 손이 많이 갈 것 같긴 함. 그래도 변경 임팩트를 사전에 숫자로 보여주는 게 재무팀 입장에서 훨씬 안심이 될 테니 우선순위를 올려볼 예정.
댓글 0
첫 댓글 달아줘.