운영자 수익 적재에 거래일자
목차
system-revenue 영역에서 운영자 수익 적재 시 거래일자를 같이 쌓도록 수정했다. 변경한 파일은 내부 클래스 하나, SQL 매퍼 하나, SQL 파일 하나. 규모로 보면 작지만, 금융/결제 도메인에서 "날짜" 컬럼 하나를 추가하는 건 생각보다 따져볼 게 많다.
거래일자가 왜 필요한가
수익 집계 테이블에는 보통 두 가지 시점이 섞인다. 레코드가 시스템에 들어온 시점(처리일자, created_at 계열)과 실제 거래가 일어난 시점(거래일자). 처음에는 처리일자 하나로도 충분해 보이지만, 운영 요구가 쌓이다 보면 "3월 거래분 수익이 실제로 얼마냐"는 질문이 나오기 시작한다. 처리 지연, 야간 배치, 정산 사이클 차이 때문에 두 날짜가 달라지는 케이스가 분명히 생기기 때문이다.
거래일자가 없으면 그 질문에 답할 수 없다. 집계를 어떤 날짜 기준으로 자르느냐에 따라 숫자가 달라지는데, 그 기준이 화면마다 다르면 운영자 입장에서는 어떤 숫자를 믿어야 할지 모르게 된다. 이번 작업은 그 출발점을 맞추는 작업이었다.
설계 단계에서 "이 데이터를 실시간으로 반영해야 하는가, 배치로 쌓으면 되는가"도 먼저 판단했다. 폴링으로 계속 갱신하면 최신 값이 유지되지만 DB 부하가 생기고, 배치로 쌓으면 지연이 생기는 대신 집계 정합성이 더 안정적이다. 운영자 수익 특성상 T+1 배치로도 충분하다는 판단을 내렸고, 그에 맞게 적재 구조를 설계했다.
구현 흐름
내부 클래스에 메서드를 추가하고, SQL 매퍼에 쿼리를 붙이는 식으로 기존 레이어 구조를 그대로 따랐다. 새 컬럼이 추가되면서 매퍼 ResultMap도 함께 손봤다.
쿼리 구조는 대략 이런 형태다.
-- 운영자 수익 적재 (거래일자 포함)
INSERT INTO operator_revenue (
operator_id,
revenue_amount,
trade_date, -- 추가된 컬럼
created_at
)
SELECT
t.operator_id,
SUM(t.amount) AS revenue_amount,
t.trade_date,
NOW() AS created_at
FROM transactions t
WHERE t.status = 'SETTLED'
AND t.trade_date = :targetDate
GROUP BY t.operator_id, t.trade_date
ON DUPLICATE KEY UPDATE
revenue_amount = VALUES(revenue_amount);
ON DUPLICATE KEY UPDATE를 넣은 건 배치가 재실행됐을 때 중복 적재를 막기 위해서다. 멱등성이 없으면 재처리 시 숫자가 두 배로 불어나는 사고가 생긴다. 운영자 수익처럼 정산에 쓰이는 데이터라면 이 방어 코드는 선택이 아니다. NULL 처리도 같이 챙겼는데, 집계 함수 안에서 NULL이 섞이면 SUM 결과가 의도와 다르게 나오는 케이스가 있어서 COALESCE로 감싸는 습관을 유지하고 있다.
백필과 기존 데이터 정합성
새 컬럼을 추가하면 기존 레코드에는 값이 없다. NULL을 그냥 두면 거래일자 기준 집계 화면에서 기존 데이터가 통째로 빠져 보이는 문제가 생긴다. 그래서 백필 SQL도 함께 작성했다.
백필의 기본 원칙은 원거래 테이블에서 날짜를 역산해서 채운다는 것인데, 조인 키가 깔끔하게 맞아야 한다. 키가 없거나 관계가 모호하면 백필 자체가 불가능하고 그냥 NULL로 남겨야 하는 케이스도 생긴다. 이번에는 operator_revenue와 원거래 테이블 사이에 조인 가능한 키가 있어서 백필을 돌릴 수 있었다.
백필 SQL 실행 전에 확인한 것들:
- WHERE 조건이 충분히 좁은지(전체 테이블 풀스캔 여부 EXPLAIN으로 확인)
- UPDATE 대상 레코드 수를 먼저 SELECT로 확인
- 트랜잭션 범위를 작게 잘라서 실행(한 번에 전체 UPDATE는 락 경합 위험)
- 실행 전 COUNT, 실행 후 COUNT 비교로 누락 확인
이번 작업에서 날짜 컬럼 추가 시 결정한 사항들을 정리하면 이렇다.
| 항목 | 결정 | 이유 |
|---|---|---|
| NULL 허용 여부 | 임시 허용, 백필 후 NOT NULL 검토 | 기존 레코드 마이그레이션 시간 필요 |
| 인덱스 추가 | 거래일자 단독 인덱스 추가 | 날짜 범위 조회 빈도 높음 |
| 배치 재실행 처리 | ON DUPLICATE KEY UPDATE | 멱등성 보장 |
| 기존 화면 영향 | 기존 쿼리 조건 변경 없음 | 기존 집계는 created_at 기준 유지 |
백필은 일회성이지만 실수하면 되돌리기 어렵다. 실행 계획 확인 없이 빠르게 돌리고 끝낸다는 마인드로 접근하면 꼭 한 번은 실수가 생긴다.
이 작업에서 다시 확인한 것들
구현 완료 후 직접 화면에서 동작을 확인했다. 거래일자가 화면에 올바르게 표시되는지, 날짜 기준으로 필터를 걸었을 때 집계값이 다른 화면의 숫자와 일치하는지 cross-check했다. 기존 데이터가 백필 이후 깨지지 않았는지도 같은 방식으로 확인했다.
구현보다 검증에 시간이 더 걸렸다. 변경 전에 현재 집계값을 메모해두고, 수정 후 같은 조건으로 쿼리를 돌려서 비교하는 루틴인데 귀찮아 보여도 이 단계를 건너뛰면 나중에 "왜 이 화면이랑 저 화면이랑 숫자가 다르냐"는 질문이 돌아온다.
커밋은 내부 클래스 변경, 매퍼 추가, 백필 SQL 세 개로 나눴다. 한 커밋에 몰아 넣으면 나중에 어느 변경이 문제를 일으켰는지 추적하기 어렵고, 롤백할 때도 범위가 넓을수록 사이드 이펙트가 생긴다. 커밋 메시지도 "거래일자 컬럼 추가"보다 "거래일자 기준 수익 조회를 위해 적재 시 trade_date 포함"처럼 왜 필요했는지를 담으려고 한다. 몇 달 뒤에 git log를 볼 때 차이가 크다.
사내 서비스를 만들다 보면 기능 하나가 화면에 버튼 하나 추가하는 것으로 끝나지 않는다는 걸 계속 체감한다. SQL 집계, 상태 머신, 예외 처리, 화면 렌더링, 권한 체크가 모두 엮여 있어서 어느 하나만 빠뜨려도 숫자가 맞지 않거나 특정 사용자에게 이상한 화면이 나온다. 특히 금융/결제 도메인에서 숫자 하나가 틀리면 신뢰가 먼저 무너진다. "대충 맞는 것 같다"로 넘기면 그 코드는 반드시 나에게 다시 돌아온다.
댓글 0
첫 댓글 달아줘.