외부 채널 매출 대시보드 KPI 산식 정정
목차
dashboard-revenue 영역 작업. 외부 채널 매출·수익 카드 레이아웃 개선과 KPI 산식 정정이 주된 내용이고, 변경 파일은 뷰/스타일 1개다. 파일 수가 적다고 작업이 단순했던 건 아니다. 오히려 화면 코드보다 쿼리 레벨 설계에 더 많은 시간이 들었음.
왜 산식을 정정해야 했나
대시보드 숫자가 다른 화면 숫자와 맞지 않는 상황이 생겼다. 같은 "외부 채널 매출"이라는 이름의 지표가 화면마다 다른 쿼리로 계산되고 있었음. 어느 쪽은 취소 건을 포함해서 합산하고, 어느 쪽은 제외하는 식이었다. 집계 기준이 통일되지 않은 채로 시간이 지나면서 생긴 불일치였다.
금융·결제 도메인에서 이런 상황은 그냥 넘길 수 없다. 사용자가 화면 A에서 본 숫자와 화면 B의 숫자가 다르면 "어느 쪽이 맞아요?"라는 질문이 나온다. 그게 한 번 나오기 시작하면 대시보드 전체에 대한 신뢰가 흔들림. 숫자 하나가 틀리면 신뢰가 무너진다는 말이 과장처럼 들릴 수 있는데, 실제로 그렇다. "대충 맞는 것 같다"로 넘어가면 반드시 다시 돌아온다는 것도.
그래서 이번 작업은 카드 레이아웃을 손보는 게 목적이 아니라, 쿼리 레벨부터 집계 기준을 다시 맞추는 게 본론이었음. 기존 화면과 API가 제공하지 않던 데이터나 동작이 필요해졌고, 단순 UI 추가가 아니라 쿼리부터 설계해서 정합성을 맞췄다.
SQL 집계 설계와 숫자 포맷
일별·월별·누적 세 단위로 집계 쿼리를 따로 작성했다. 하나의 쿼리에서 파생값으로 처리하면 쿼리 자체는 간결해지는데, 그러면 나중에 어느 기준이 왜 이 값인지 추적하기 어려워진다. 명시적으로 분리하는 쪽이 유지보수할 때 훨씬 낫다.
핵심은 집계 기준 통일이다. 취소·환불 건을 어떻게 처리할지가 화면마다 달랐던 게 원래 문제였으니, 이번 정정에서 기준을 명시적으로 고정했음.
SELECT
DATE_FORMAT(created_at, '%Y-%m-%d') AS day,
SUM(
CASE WHEN status NOT IN ('CANCELLED', 'REFUNDED')
THEN amount ELSE 0 END
) AS revenue,
SUM(
CASE WHEN status NOT IN ('CANCELLED', 'REFUNDED')
THEN margin ELSE 0 END
) AS profit
FROM channel_orders
WHERE channel_type = 'EXTERNAL'
AND created_at >= :start_date
AND created_at < :end_date
GROUP BY day
ORDER BY day
NOT IN ('CANCELLED', 'REFUNDED') 조건이 핵심. 이걸 명시적으로 박아둬야 이후에 집계 기준을 바꾸거나 다른 화면에 같은 쿼리를 참조할 때 의도가 명확하게 읽힌다.
NULL 방어도 쿼리 단에서 처리했음. NULL이 집계에 섞이면 프론트의 숫자 포맷 함수가 예상치 못한 결과를 내놓는다. COALESCE나 집계 함수 레벨 기본값 보장으로 막았고, 쿼리 실행 계획도 미리 확인했다. 폴링이 붙는 쿼리라 주기마다 실행되는데, 풀 스캔이 발생하면 빈도와 곱해져서 부담이 커진다. 인덱스 활용 여부를 먼저 점검하고 들어간 이유가 그거다.
숫자 포맷은 프론트에서 천단위 콤마와 원 단위를 공통 유틸 하나로 통일했다. 처음엔 별것 아닌 것 같아도, 포맷 함수가 여러 컴포넌트에 흩어져 있으면 나중에 기준이 바뀔 때 전부 찾아서 고쳐야 한다. 이런 통일이 쌓이면 코드베이스가 버텨주는 구간이 달라짐.
실시간 갱신, 검증, 커밋
실시간 갱신은 5초 폴링으로 처리했다. 이 선택이 항상 옳은 건 아니고, 방식마다 트레이드오프가 있다.
| 방식 | 장점 | 단점 | 선택 여부 |
|---|---|---|---|
| 폴링 (5초) | 구현 단순, 서버 상태 독립적 | 주기마다 요청 발생 | 채택 |
| WebSocket | 실시간성 높음 | 연결 상태 관리 복잡 | 오버스펙 |
| SSE | 서버 push 가능, 단방향 | 브라우저 연결 수 제한 | 검토 후 제외 |
대시보드는 사용자가 오랜 시간 열어두는 화면이다. 5초마다 요청이 들어오는 구조가 서버에 얼마나 부담인지는 사용자 수와 쿼리 비용을 함께 봐야 한다. 집계 쿼리를 가볍게 유지하고 인덱스를 제대로 쓰면 이 정도 빈도는 충분히 감당됨. 다만 사용자가 늘어나면 이 판단을 다시 해야 한다는 전제를 달아두는 게 맞다. 지금의 선택이 영구적이라고 생각하지 않음.
검증은 구현 후 직접 화면에서 동작을 확인했다. 산식 정정이 포함된 작업이라 기존에 보이던 숫자와 달라질 수 있다. 그 차이가 의도한 정정인지 새로운 버그인지 구분하려면 변경 전 수치를 미리 메모해두는 게 필요했음. 스크린샷이든 직접 쿼리로 뽑은 값이든, 비교 기준이 있어야 "바뀐 게 맞는 방향"인지 확인할 수 있다. 관련 화면이 있으면 같은 지표를 cross-check해서 숫자가 일치하는지도 봤다.
커밋은 논리적으로 독립된 단위로 쪼갰다. SQL 집계 기준 정정, 카드 레이아웃 변경, 숫자 포맷 통일을 한 커밋에 몰아넣지 않고 구분했음. 한 덩어리로 올리면 나중에 문제가 생겼을 때 어느 변경이 범인인지 찾는 데 시간이 배로 든다. 작은 커밋을 자주 하는 습관의 이유가 거기 있다.
커밋 메시지는 "무엇을"보다 "왜"를 쓰려 한다. fix: KPI card layout보다 fix: 외부 채널 매출 집계에서 취소/환불 건 포함 시 타 화면과 수치 불일치 발생이 나중에 git log를 볼 때 훨씬 쓸모 있다. 변경 내용은 diff로 알 수 있으니 메시지에는 맥락이 있어야 하기 때문이다.
사내 서비스라고 기준을 낮추는 일은 없다. 같은 숫자를 매일 보는 내부 사용자일수록 이상한 걸 빠르게 잡아낸다. 꼼꼼하게 하는 게 기본값이어야 한다는 게 이런 작업을 할 때마다 드는 생각이고, 금융·결제 도메인에서는 더욱 그렇다.
댓글 0
첫 댓글 달아줘.