개발 slecs

수익 KPI 화면에 당일·월간 결제 비용 집계 추가

목차

system-revenue-kpi 영역에 pgExpenseToday, pgExpenseMonth 두 컬럼을 추가했다. selectSystemRevenuePendingLiveSummary 쿼리 하나, SQL 매퍼 파일 하나. 변경 자체는 작은데 건드리는 맥락이 결제 도메인이라 손이 조심스러워지는 작업이었음.

쿼리 레벨부터 설계한 이유

KPI 화면에 숫자 하나를 더 띄우는 건 프론트에서 필드 하나 추가하면 끝처럼 보일 수 있다. 근데 "당일 결제 비용"이라는 집계는 생각보다 정의가 복잡함. 어떤 상태의 건을 포함할 건지 (pending만? settled만? 환불 차감?), 기준 시각은 UTC인지 KST인지, 월간 집계의 "월 시작"은 1일 00:00:00인지 아닌지.

이걸 화면 레벨이나 서비스 레이어에서 걸러내면 나중에 "그 화면에서 봤던 숫자랑 다른 화면이 왜 달라요?" 질문이 반드시 들어온다. 그래서 집계 기준을 SQL 안에서 한 번에 확정하고, 다른 화면이 같은 기준으로 조회하도록 같은 쿼리를 공유하게 만든 게 이번 방향이었음.

결제 도메인에서 숫자 불일치는 단순히 버그가 아니라 신뢰 문제가 된다. 두 화면의 숫자가 다르면 어느 쪽이 맞냐는 질문이 나오고, 그 답을 찾는 데 드는 비용이 처음부터 정합성 맞추는 비용보다 훨씬 크다. 집계 쿼리를 짤 때 고민한 포인트는 대략 이랬음.

항목 결정 이유
시간 기준 DB 서버 기준 (UTC) 애플리케이션 레이어 변환 최소화
상태 필터 settled + partial_refund pending은 변동성 높아 집계 제외
NULL 처리 COALESCE(SUM(...), 0) 건수 없을 때 NULL 반환 방지
인덱스 활용 paid_at + status 복합 일별 집계 풀스캔 방지

COALESCE는 빠뜨리기 쉬운데 빠뜨리면 데이터 없는 날 화면이 비거나 합산 로직이 NaN을 뱉는다. 사소해 보이지만 당일 0건인 날을 테스트 안 하면 프로덕션 나가고 나서야 발견되는 유형이다.

구현 흐름

변경은 SQL 매퍼 한 파일이지만 실제로 확인한 범위는 좀 넓었음.

  • selectSystemRevenuePendingLiveSummary 쿼리에 두 컬럼 추가
  • 내부 DTO/결과 클래스에 필드 추가 및 매핑 확인
  • 기존 결과셋에 컬럼이 붙는 거라 기존 필드 매핑이 깨지지 않는지 검증
  • 화면 렌더링 연동 후 실제 값 노출 확인
  • 실시간 갱신이 필요한지 여부 결정 - 이 화면은 폴링으로도 충분해서 이벤트 방식은 사용하지 않음

쿼리 구조는 대략 이런 형태였음.

SELECT
    -- 기존 컬럼들 ...
    COALESCE(
        SUM(CASE WHEN DATE(pg_paid_at) = CURDATE() THEN pg_amount ELSE 0 END),
        0
    ) AS pgExpenseToday,
    COALESCE(
        SUM(CASE WHEN DATE_FORMAT(pg_paid_at, '%Y-%m') = DATE_FORMAT(NOW(), '%Y-%m')
            THEN pg_amount ELSE 0 END),
        0
    ) AS pgExpenseMonth
FROM
    payment_transactions
WHERE
    status IN ('settled', 'partial_refund')
    -- 기존 조건 ...

CASE WHEN 안에서 날짜 함수를 쓰면 인덱스를 못 타는 케이스가 있어서 실행 계획을 한 번 돌려봤음. 데이터 볼륨이 크지 않으면 풀스캔이어도 허용 범위 안에 들어올 수 있는데, 이건 운영 데이터 기준으로 직접 EXPLAIN 찍어서 확인하는 게 맞다. "아마 괜찮겠지"로 넘어가면 데이터가 쌓인 후에야 느려지기 시작하고, 그때 원인 찾는 게 훨씬 피곤하다.

기존 로직과 충돌 여부도 한 번 짚었음. 같은 summary 쿼리를 참조하는 다른 화면이나 배치가 있는지 확인하고, 컬럼이 늘어난 게 그쪽 로직에 영향을 주지 않는지 체크했다. 결과셋 뒤에 컬럼이 추가되는 구조라 기존 매핑이 깨질 가능성은 낮지만, 금융 도메인에서 "낮다"와 "없다"는 다르기 때문에 직접 눈으로 확인하는 쪽을 택했음.

검증과 커밋 습관

기능 완성 후 화면에서 직접 확인한 것들.

  • 당일 집계 숫자가 실제 결제 내역 합산과 일치하는지
  • 월간 집계도 다른 월별 리포트 화면 숫자와 cross-check
  • 데이터 없는 케이스(0건 상태) 재현해서 NULL 안 뱉는지 확인
  • 기존 항목들이 깨지지 않았는지 화면 전체 한 번 더 훑기

숫자가 맞다는 걸 "느낌"으로 넘기지 않고 다른 화면이나 원천 데이터와 맞춰보는 게 금융 도메인에서 기본값이어야 한다. "대충 맞는 것 같다"로 덮으면 다음 분기 정산 시즌에 반드시 다시 돌아온다.

커밋은 논리적으로 독립된 단위로 쪼갰음. 쿼리 추가, DTO 필드 추가, 화면 연동 각각을 별도 커밋으로 만들면 나중에 어느 지점에서 뭔가 바뀌었는지 추적이 명확하다. 메시지도 "pgExpenseToday 컬럼 추가"보다 "당일 결제 비용 집계 KPI 노출 위해 쿼리 확장"처럼 왜 했는지를 담으려 노력했음. 6개월 뒤 git log 볼 때 무엇을 했는지가 아니라 왜 했는지가 보여야 맥락을 빠르게 복원할 수 있다.

사내 서비스를 만들다 보면 기능 하나가 단순히 화면에 버튼 하나 추가하는 것으로 끝나지 않는다는 걸 계속 체감함. SQL 집계, 상태 필터, 예외 처리, 화면 렌더링, 권한 체크가 모두 엮여 있어서 어느 하나만 빠뜨려도 숫자가 맞지 않거나 특정 상황에서 이상한 화면이 나타난다. 작은 변경처럼 보여도 결제 집계는 방어적으로 짜는 게 맞다. COALESCE 하나, EXPLAIN 한 번, cross-check 한 번이 나중에 밤 새워 디버깅하는 것보다 낫다.

댓글 0

첫 댓글 달아줘.