PG 실비용 결제 유형별 세부 항목을 대시보드에 분해 표기
목차
dashboard-pg 영역에 PG 실비용 세부 항목 분해 표기 기능을 추가했다. 기존 대시보드에서는 PG 비용이 합산된 단일 숫자로만 보였는데, 이번에 가상계좌충전·카드충전·카드결제·가상계좌결제 네 가지 유형별로 쪼개서 표시하도록 변경했음. 변경 파일 자체는 뷰/스타일 1개지만, 준비 과정은 그보다 훨씬 길었다.
합산 숫자 하나로 PG 비용을 보는 건 어느 순간까지는 충분하다. 전체 규모를 파악하는 수준이라면 합계만 봐도 된다. 문제는 운영 관점이 생길 때부터다. "이번 달 카드 수수료가 왜 이렇게 나왔지?", "가상계좌 충전 건수가 줄었는데 비용이 똑같네?" 같은 질문이 나오기 시작하면, 합산 값은 아무것도 설명해주지 못한다. 결제 유형마다 수수료 구조가 다르고, 이상 징후를 탐지하려면 유형별 추이를 볼 수 있어야 한다. 기존 화면과 API가 이 데이터를 제공하지 않았기 때문에, 단순한 UI 추가가 아니라 쿼리 레벨부터 설계해서 올라왔음.
SQL부터 화면까지
집계 쿼리 설계에서 제일 먼저 한 일은 조건 정의를 명확히 하는 거였다. 일별·월별·누적 집계 각각을 어떻게 끊느냐에 따라 숫자가 달라지기 때문에, "어느 시점 기준으로 잡을 것인가"부터 정해야 했다. 생성 시각 기준인지, 처리 완료 시각 기준인지 하나만 달라져도 당일 집계가 어긋난다.
집계 쿼리에서 항상 챙겨야 하는 게 NULL 처리다. 데이터가 아예 없는 날짜는 SUM(amount) 결과가 NULL로 나오는데, 이게 그대로 화면으로 흘러가면 숫자 포맷 함수가 터지거나 KPI 카드가 빈 칸으로 표시된다. COALESCE(SUM(amount), 0) 처리는 특히 금융 집계에서는 습관처럼 박아야 하는 작업임.
SELECT
payment_type,
COALESCE(SUM(fee_amount), 0) AS total_fee,
COUNT(*) AS txn_count
FROM pg_transactions
WHERE created_at >= :start_date
AND created_at < :end_date
GROUP BY payment_type
실제 쿼리는 이것보다 조건이 더 붙는다. 여기서 한 가지 더 신경 쓴 부분이 있는데, GROUP BY payment_type 결과가 네 가지 유형 전부를 항상 돌려주는 게 아니라는 점이다. 특정 기간에 가상계좌 충전 거래가 한 건도 없으면 그 행 자체가 없다. 클라이언트에서 "없으면 0"으로 보정하는 로직을 별도로 넣어야 화면이 의도한 대로 나온다.
화면 쪽에서는 포맷 통일이 생각보다 중요했음. 천단위 콤마, 원 단위 표기, 소수점 처리 방식이 화면마다 제각각이면 같은 숫자도 다르게 읽힌다. 이번 기회에 포맷 함수를 한 군데로 모아서 다른 KPI 카드와 동일한 함수를 재사용하도록 정리했다.
실시간 갱신은 5초 폴링으로 처리했다. 이벤트 기반(SSE, WebSocket)으로 갈 수도 있었는데, 이 대시보드의 사용 패턴상 PG 비용 데이터가 초 단위로 즉각 반영돼야 할 이유가 크지 않았음. 폴링은 구현 복잡도가 낮고, 서버 재시작이나 네트워크 단절 후 자동으로 회복된다는 장점이 있다.
| 방식 | 장점 | 단점 | 적합한 경우 |
|---|---|---|---|
| 폴링 | 구현 단순, 자동 복구 | 불필요한 요청 발생 | 분 단위 갱신, 운영 대시보드 |
| SSE | 서버 푸시, 효율적 | 단방향, 재연결 관리 필요 | 로그 스트리밍, 서버 알림 |
| WebSocket | 양방향 실시간 | 인프라·구현 복잡도 높음 | 채팅, 협업 편집 |
불필요한 요청이 계속 나가는 건 단점이지만, 대시보드 특성상 탭을 열어두고 모니터링하는 경우가 대부분이라 그 비용을 감수하기로 했다. 폴링 간격 조정이나 visibility change 이벤트로 탭 비활성 시 중단하는 최적화는 나중에 필요해지면 추가할 수 있음.
숫자 정합성이 전부다
금융/결제 도메인에서 숫자 하나가 어긋나면 신뢰가 무너진다. 같은 날짜 범위를 조회했는데 화면마다 값이 달라지면, 어느 쪽이 맞는 건지 알 수 없게 되고 결국 아무 화면도 믿지 못하는 상황이 된다. 그래서 이번 기능 추가 후 가장 시간을 쓴 부분이 cross-check였음.
기존에 다른 화면에서 보여주던 PG 비용 합산치와, 이번에 추가된 세부 항목 네 가지의 합이 정확히 일치하는지 확인했다. 일치하지 않으면 집계 조건 어딘가가 다른 거다. 시간대 처리, 취소 건 포함 여부, 특정 상태값 필터링 같은 사소해 보이는 조건 차이가 숫자를 어긋나게 만든다. 쿼리가 다른 화면 쿼리와 같은 논리를 공유하는지 확인하는 게 여기서 핵심이었음.
검증 순서는 이렇게 잡았다.
- 빈 데이터 상태에서 화면 정상 렌더링 여부 확인 (0원 표시 vs 빈 칸 vs 에러)
- 세부 항목 네 가지의 합 = 기존 합산치 일치 여부 확인
- 일별·월별·누적 집계 각각 따로 검증
- NULL이 포함된 기간 조회 시 포맷 함수가 터지지 않는지 확인
- 권한 없는 접근 시 API 응답이 적절히 차단되는지 확인
구현 후 직접 화면에서 확인했을 때 숫자가 맞았고, 기존 데이터도 깨지지 않았다. 변경 파일이 1개뿐이어서 리스크 자체는 크지 않았지만, before/after 수치를 메모해두고 비교하는 과정은 생략하지 않았음.
이번 작업에서 다시 한번 체감한 건, 화면에서 KPI 카드 네 개 추가처럼 보이는 기능도 SQL 집계 설계, NULL 방어, 포맷 통일, 갱신 전략 결정, 정합성 검증이 전부 엮여 있다는 거다. 어느 하나라도 느슨하게 처리하면 나중에 반드시 다시 돌아온다. "대충 맞는 것 같다"로 배포하면 3개월 뒤 특정 사용자에게만 이상한 숫자가 보이는 형태로 돌아오거나, 정산 데이터 신뢰를 흔드는 상황이 된다.
커밋은 논리적으로 독립된 단위로 쪼갰다. SQL 변경, API 변경, 화면 변경을 한 커밋에 몰아 넣지 않았음. 뭔가 잘못됐을 때 어느 변경에서 문제가 생겼는지 추적하는 속도가 커밋 단위에 비례해서 좋아지기 때문이다. 커밋 메시지도 "PG 비용 세부 항목 추가" 같은 what 보다는 "운영 중 유형별 비용 추이 파악을 위해 PG 비용 분해 표기" 처럼 why를 담으려 했다. 몇 달 뒤 git log를 볼 때 그게 훨씬 쓸모 있다.
댓글 0
첫 댓글 달아줘.