원의 생각

기록하고, 분석하고, 성장한다

Productivity Notes/Vibe Coding

데이터 실적이 맞지 않을 때, PM은 어디서부터 확인해야 할까 (feat. CODEX)

Gelasio 2026. 5. 16. 07:00

 

Databricks 쿼리 정합성 검증 과정 기록

데이터 대시보드를 만들거나 자동 리포트를 운영하다 보면 가장 난감한 순간이 있다.

 

같은 지표를 보고 있다고 생각했는데, 숫자가 다르게 나오는 순간이다.

 

거래액이 다르고, 주문건수가 다르고, 취소건수도 다르다.
처음에는 단순한 쿼리 오류처럼 보인다.
하지만 실제로 파고들어가면 문제는 훨씬 복잡하다.

 

이번 작업은 Databricks에서 운영 중인 교통 서비스 실적 쿼리와 새로 만든 Slack 리포트/대시보드 쿼리의 숫자가 맞지 않으면서 시작됐다. 이 글에서는 구체적인 테이블명이나 내부 URL, 민감한 수치는 제외하고, 어떤 순서로 오류 확인 범위를 좁혀갔는지를 중심으로 정리해보려 한다.


1. 문제는 “숫자가 다르다”에서 시작된다

처음 발견한 문제는 단순했다.

 

기존 대시보드에서 보는 거래액과 주문건수, 그리고 새로 만든 자동 리포트에서 보는 거래액과 주문건수가 달랐다.

 

겉으로 보면 둘 다 같은 지표를 보는 것 같았다.

  • 같은 서비스
  • 같은 날짜
  • 같은 거래액
  • 같은 주문건수
  • 같은 취소건수

그런데 결과가 달랐다.

 

이때 가장 위험한 접근은 바로 “어느 쪽이 맞다”라고 단정하는 것이다.
데이터 정합성 문제에서 중요한 것은 정답을 빨리 정하는 것이 아니라, 두 쿼리가 정말 같은 것을 보고 있는지 확인하는 것이다.

 

그래서 먼저 질문을 바꿨다.

“왜 숫자가 다르지?”가 아니라
“두 쿼리는 정말 같은 기준으로 집계하고 있는가?”

2. 먼저 비교 범위를 좁혔다

처음부터 전체 서비스를 비교하면 원인을 찾기 어렵다.

 

서비스가 여러 개이고, 지표도 여러 개이며, 날짜 기준도 여러 개다.
거래액, 주문건수, 판매티켓수, 취소건수, 취소액을 한 번에 보면 어디서 차이가 나는지 보이지 않는다.

 

그래서 범위를 좁혔다.

 

처음에는 특정 서비스 하나를 기준으로 잡았다.
그리고 지표도 가장 핵심적인 두 개로 제한했다.

  • 거래액
  • 주문건수

이렇게 범위를 줄이면 비교가 쉬워진다.

비교 대상 이유
특정 서비스 1개 서비스별 조건 차이를 줄이기 위해
거래액 금액 기준 차이 확인
주문건수 row grain 차이 확인
동일 날짜 기준일 차이 제거

처음부터 모든 것을 보려 하면 아무것도 못 본다.
데이터 검증에서는 범위를 좁히는 것이 곧 분석의 시작이다.


3. 쿼리의 “결과”가 아니라 “집계 기준”을 비교했다

숫자가 다른 상황에서 결과만 비교하면 원인을 찾기 어렵다.
중요한 것은 쿼리가 어떤 방식으로 숫자를 만들고 있는지다.

 

그래서 두 쿼리를 다음 기준으로 나눠 비교했다.

비교 항목 확인한 내용
원천 테이블 같은 테이블을 보고 있는가
서비스 식별 조건 같은 서비스를 같은 조건으로 필터링하는가
날짜 기준 주문일, 취소일, 사용일 중 무엇을 기준으로 보는가
주문건수 기준 예약번호 기준인지, 주문번호 기준인지, 티켓 기준인지
거래액 기준 결제금액인지, 환불/취소 반영 금액인지
상태값 처리 취소/완료/실패 상태를 어떻게 처리하는가
집계 grain 어떤 컬럼 조합으로 먼저 묶는가

특히 중요했던 것은 집계 grain이었다.

 

같은 예약번호라도 상태값, 취소일, 회원번호, 상품 카테고리 등에 따라 여러 row로 나뉠 수 있다.
이 상태에서 단순히 금액을 합산하면, 같은 주문이 여러 번 더해질 수 있다.

 

문제의 가능성은 여기에 있었다.

동일한 주문번호가 여러 상태 또는 여러 row로 분리되면, 주문건수는 distinct로 맞아도 거래액은 과다 집계될 수 있다.

 

그래서 쿼리의 구조를 바꾸는 방향은 명확해졌다.

 

주문/거래액 집계용 base와 취소 집계용 base를 분리해야 했다.


4. 주문 기준과 취소 기준을 분리했다

데이터 실적에서 자주 발생하는 혼선 중 하나는 주문일과 취소일을 같은 기준으로 처리하는 것이다.

 

예를 들어 5월 10일에 주문한 건이 5월 13일에 취소될 수 있다.

 

이때 지표는 이렇게 나뉘어야 한다.

 

지표 기준일
주문건수 주문일
거래액 주문일
판매티켓수 주문일
취소건수 취소일
취소액 취소일

만약 주문과 취소를 하나의 base에서 같이 처리하면, 쿼리는 편해질 수 있지만 해석은 어려워진다.

 

그래서 집계 구조를 다음처럼 나눴다.\

구분 역할
주문 base 주문일 기준 거래액, 주문건수, 판매티켓수 집계
취소 base 취소일 기준 취소건수, 취소액, 취소티켓수 집계
일별 master 주문 base와 취소 base를 일자/서비스 기준으로 결합

이 구조로 바꾸면 지표의 의미가 명확해진다.

 

주문은 주문일 기준.
취소는 취소일 기준.
이 기준을 분리하지 않으면, 이후 리포트에서 계속 “왜 숫자가 다르냐”는 질문이 반복된다.


5. “주문건수”와 “판매티켓수”는 같은 지표가 아니었다

중간에 또 하나의 차이가 발견됐다.

 

주문건수는 맞는데 판매티켓수가 맞지 않았다.

 

원인은 단순했다.

새 쿼리에서 판매티켓수를 주문건수와 같은 값으로 처리하고 있었다.

 

하지만 실제로는 한 주문 안에 여러 티켓이 포함될 수 있다.

 

예를 들어 한 사람이 2명의 승차권을 한 번에 예매하면:

지표
주문건수 1
판매티켓수 2

따라서 판매티켓수는 주문건수가 아니라 item_count 같은 수량 기준 컬럼으로 집계해야 한다.

 

이 차이는 사소해 보이지만, 리포트에서는 매우 중요하다.
주문건수는 “거래 단위”이고, 판매티켓수는 “이용 수량 단위”다.

 

두 지표를 혼용하면 객단가, 티켓당 단가, 전환율 해석이 모두 틀어진다.


6. 검증은 반드시 “차이값”으로 했다

쿼리를 수정한 뒤에는 단순히 결과가 그럴듯한지 보지 않았다.
기존 쿼리와 수정 쿼리를 나란히 놓고 차이값을 계산했다.

 

검증 테이블은 이런 형태로 만들었다.

날짜 기존 주문건수 신규 주문건수 차이 기존 거래액 신규 거래액 차이
D-1 100 100 0 1,000,000 1,000,000 0

핵심은 차이가 0인지 확인하는 것이다.

 

특히 다음 지표는 반드시 비교했다.

  • 주문건수 차이
  • 거래액 차이
  • 판매티켓수 차이
  • 취소건수 차이
  • 취소액 차이

수정이 성공했다는 말은 “쿼리가 실행됐다”가 아니다.
기존 기준과 비교했을 때 차이가 0으로 수렴했다는 뜻이어야 한다.


7. 대시보드와 Slack 리포트의 기준도 맞춰야 했다

쿼리 정합성을 맞춘 뒤에는 Slack 리포트와 Databricks 대시보드를 연결하려고 했다.

 

여기서 또 다른 문제가 나왔다.

 

대시보드는 현재 D-1 기준을 보고 있었고, Slack dry-run은 특정 과거 날짜를 보고 있었다.
즉, 둘이 같은 화면처럼 보였지만 실제로는 다른 날짜를 보고 있었다.

 

여기서 배운 점은 명확하다.

대시보드에는 반드시 “현재 이 숫자가 어떤 기준일의 숫자인지” 표시해야 한다.

 

그래서 대시보드 상단에 기준일 정보를 넣는 방향으로 정리했다.

 

예를 들면 이런 식이다.

KPI 기준일: 2026-05-14
기준: current_date() - 1
주문/거래액/판매티켓수: 주문일 기준
취소건수/취소액: 취소일 기준

대시보드에서 날짜 기준이 보이지 않으면, 숫자가 맞아도 신뢰하기 어렵다.
운영 리포트에서는 “숫자”만큼이나 숫자의 기준이 중요하다.


8. 캐시와 실행 환경도 의심해야 했다

중간에는 대시보드가 예전 값을 보여주는 문제도 있었다.

 

새로 고침 전에는 이전 값이 보였고, refresh 후에는 최신 값으로 바뀌었다.
또 SQL Warehouse에서 실행한 결과와 Job compute에서 실행한 결과가 다르게 나오는 상황도 있었다.

 

이 과정에서 확인한 것은 다음이다.

확인 대상 이유
Published Dashboard refresh 상태 이전 캐시값 표시 여부 확인
SQL Warehouse 직접 실행 결과 현재 기준 데이터 확인
Job dry-run 결과 실제 Slack 리포트 기준 확인
원천 테이블 적재 시점 D-1 데이터 후행 반영 여부 확인
Workspace Notebook 버전 Job이 최신 노트북을 실행하는지 확인

Databricks 같은 환경에서는 같은 SQL처럼 보여도 실행 위치에 따라 결과가 달라질 수 있다.

  • SQL Warehouse에서 실행한 결과
  • Notebook에서 실행한 결과
  • Job compute에서 실행한 결과
  • Published Dashboard가 보여주는 결과

이 네 가지가 항상 같은 시점의 데이터를 보고 있다고 가정하면 안 된다.


9. 작업 중 가장 중요했던 판단 기준

이번 작업에서 가장 중요했던 원칙은 이것이었다.

링크를 바꾸기 전에, 숫자의 기준을 먼저 맞춘다.

 

대시보드 링크를 Slack 메시지에 넣는 것은 기술적으로는 간단하다.
상수 하나만 바꾸면 된다.

 

하지만 그 전에 확인해야 할 것이 많았다.

  • Slack 메시지 숫자와 대시보드 숫자가 같은 기준인가
  • 같은 날짜를 보고 있는가
  • 같은 SQL 로직인가
  • 같은 원천 데이터를 보고 있는가
  • 대시보드가 최신 refresh 상태인가
  • 사용자가 기준일을 이해할 수 있는가

이 중 하나라도 맞지 않으면, Slack 메시지 하단의 대시보드 링크는 오히려 혼란을 만든다.

 

숫자가 다르면 사용자는 이렇게 생각한다.

“Slack 리포트가 틀린 건가? 대시보드가 틀린 건가?”

 

운영 리포트에서 이 질문이 나오면 신뢰가 깨진다.
그래서 링크 교체는 마지막 단계로 미뤘다.


10. 데이터 검증은 쿼리 수정이 아니라 범위 축소의 과정이다

이번 작업을 통해 다시 확인한 것은, 데이터 검증은 단순히 SQL을 고치는 일이 아니라는 점이다.

 

처음에는 거래액과 주문건수가 다르다는 문제였다.
하지만 범위를 좁혀가면서 실제로 확인한 것은 훨씬 많았다.

단계 확인한 것
1단계 어떤 서비스에서 차이가 나는가
2단계 어떤 지표에서 차이가 나는가
3단계 주문일/취소일/사용일 기준이 같은가
4단계 집계 grain이 같은가
5단계 주문건수와 판매티켓수를 혼용하지 않았는가
6단계 Dashboard와 Slack이 같은 SQL 기준인가
7단계 실행 환경과 캐시 상태가 같은가
8단계 사용자가 기준일을 이해할 수 있는가

처음부터 답을 찾으려고 하면 길을 잃는다.
대신 하나씩 범위를 좁혀야 한다.

  • 서비스 범위를 좁힌다.
  • 지표 범위를 좁힌다.
  • 날짜 기준을 확인한다.
  • row grain을 확인한다.
  • 한쪽에만 포함된 row를 찾는다.
  • 실행 환경을 비교한다.
  • 마지막에 운영 링크를 바꾼다.

이 순서가 중요하다.


정리: 숫자가 다를 때 PM이 해야 할 일

실적 숫자가 다를 때 PM이 해야 할 일은 SQL을 직접 다 고치는 것이 아니다.
더 중요한 역할은 무엇을 비교해야 하는지 정의하는 것이다.

 

특히 아래 질문을 던져야 한다.

  1. 이 지표의 기준일은 무엇인가?
  2. 주문건수와 판매수량을 구분하고 있는가?
  3. 취소는 주문일 기준인가, 취소일 기준인가?
  4. 같은 원천 테이블을 보고 있는가?
  5. 같은 상태값을 포함하고 있는가?
  6. 같은 grain으로 먼저 집계하고 있는가?
  7. Dashboard와 리포트가 같은 SQL 기준인가?
  8. 지금 보고 있는 값은 최신 refresh 값인가?
  9. 사용자가 숫자의 기준을 이해할 수 있는가?

데이터 리포트의 신뢰는 화려한 대시보드에서 나오지 않는다.
숫자의 기준을 끝까지 추적하는 과정에서 나온다.

 

이번 작업은 그걸 다시 확인한 과정이었다.

 

데이터가 맞지 않을 때 필요한 것은 더 복잡한 쿼리가 아니라, 더 정확한 질문이다.

 

나도 곧 DA 담당자에게 요청을 하게 될 것 같다.