+ 단계별 측정·허용 변화·증거 보존을 충실히 다뤘다.
- 전체 후보군별 쿼리까지 다루지 않아 다소 불완전하다.
최종 숫자가 이상할 때 어느 단계에서 틀어졌는지 거슬러 올라갑니다.
| 분류 | 개발 › 데이터·DB |
|---|---|
| 태그 | 분석검토개발자 |
Trace this pipeline failure. Approach — bisect by stage, not by reading code: 1. For each stage, the two numbers to capture: **row count in versus out, and the sum of the key measure in versus out.** A stage where either changes unexpectedly is your stage. 2. What each stage is *allowed* to change. Filters reduce rows legitimately; joins may multiply them illegitimately. 3. Order to check in, starting where a discrepancy is cheapest to detect. 4. Given my symptom, which stages it already rules out. Then, by symptom shape: - **Too high** — a join fanning out, a re-run that appended instead of replacing, double-counted late-arriving data - **Too low** — an inner join dropping unmatched rows, a filter on a nullable column, a partition not yet loaded - **Off by a constant** — timezone boundary, currency, unit - **Off for some rows only** — type coercion, encoding, a code not present in a lookup table For each candidate: the query that confirms or rules it out. Rules: - *Do not guess the cause from the description.* Give the check. - Note where reprocessing would destroy the evidence, and what to snapshot first. - Say whether the pipeline is idempotent, since if it is not, re-running to investigate makes it worse.
파이프라인은 에러 없이 틀린 값을 낸다. 이 프롬프트는 각 단계의 행 수와 합계를 대조해 어디서 값이 바뀌었는지 이분 탐색하듯 좁힌다.
ChatGPT가 가장 정확하고 완결적이다. Gemini는 구체적이지만 과도한 단정과 임의 날짜가 문제이며, [C]는 답이 없다.
+ 단계별 측정·허용 변화·증거 보존을 충실히 다뤘다.
- 전체 후보군별 쿼리까지 다루지 않아 다소 불완전하다.
+ 유력 후보별 판정 기준과 확인 쿼리가 구체적이다.
- 근거 없이 원인·날짜를 단정하고 일부 가능성을 배제했다.
| 기준 | ChatGPT | Gemini | 앞선 모델 |
|---|---|---|---|
| 지시 준수 | 9 | 7 | ChatGPT +29% |
| 정확성 | 9 | 5 | ChatGPT +80% |
| 구체성 | 9 | 8 | ChatGPT +13% |
| 구성·가독성 | 9 | 8 | ChatGPT +13% |
| 분량 적정성 | 7 | 6 | ChatGPT +17% |
gpt-5.6-sol가 모델 이름을 가린 채 1~10점으로 채점했습니다 (2026-09-25). 측정값이 아니라 AI 심사 결과입니다.
같은 입력을 세 모델에 넣어 받은 답을 그대로 옮겼습니다. 각 CLI(에이전트 하네스)에서 실행했습니다. ChatGPT·Claude 앱이나 웹에서는 답이 다를 수 있습니다.
파이프라인 단계: raw_orders(원본 수집 로그) → stg_orders(정제·중복 제거) → fct_orders(일별 배치 팩트 테이블) → BI 대시보드 집계 어떻게 틀렸나: 어제부터 fct_orders의 일별 매출 합계가 실제보다 약 1.8배 높게 찍힘. 어제 새벽 프로모션 이벤트 로그가 늦게 도착해서 재처리 스크립트를 한 번 더 돌렸음 확인한 것: raw_orders 행 수는 평소와 비슷한 수준인 것 확인함, stg_orders 중복 제거 로직 코드는 최근 변경 없음, 재처리 스크립트가 append인지 overwrite인지는 아직 확인 안 함
현재 증상만으로 원인을 단정할 수는 없습니다. 다만 재처리 후 fct 매출이 약 1.8배이므로, 가장 먼저 재처리의 append/overwrite와 fact 적재의 중복 여부를 확인해야 합니다.
대상 날짜를 동일하게 맞춰 <D>로 표시합니다. 금액 컬럼은 <amount>, 주문 키는 <order_id>로 대체하세요.
| 단계 | 행 수 | 매출 합계 |
|---|---|---|
| raw_orders | COUNT(*) |
SUM(<amount>) |
| stg_orders | COUNT(*) |
SUM(<amount>) |
| fct_orders | COUNT(*) |
SUM(<amount>) |
| BI 원천 집계 | COUNT(*) 또는 주문 수 |
SUM(<amount>) |
| BI 대시보드 | 가능하면 동일 필터의 매출 | 동일 |
SELECT
COUNT(*) AS row_count,
SUM(<amount>) AS amount_sum
FROM raw_orders
WHERE <order_date> = DATE '<D>';
SELECT
COUNT(*) AS row_count,
SUM(<amount>) AS amount_sum
FROM stg_orders
WHERE <order_date> = DATE '<D>';
SELECT
COUNT(*) AS row_count,
SUM(<amount>) AS amount_sum
FROM fct_orders
WHERE <order_date> = DATE '<D>';
각 단계의 값을 같은 실행 시점에 별도 저장하세요. 특히 재처리 전에 존재했던 fct 상태가 필요합니다.
raw_orders → stg_orders
stg_orders → fct_orders
fct_orders → BI
가장 저렴하게 확인할 순서는 다음과 같습니다.
fct_orders에 재처리 배치가 두 번 들어갔는지 확인stg_orders → fct_orders 적재 쿼리의 append/overwrite 동작 확인fct_orders의 동일 주문 중복 확인stg_orders의 dedup 결과와 raw 대비 비교raw 행 수가 평소와 비슷하다는 사실만으로는 raw 단계가 정상이라고 결론낼 수 없습니다. 행 수뿐 아니라 금액 합계, 주문 키 중복, 해당 날짜의 실제 입력 범위를 함께 확인해야 합니다.
현재 확인된 사실로 확실히 배제되는 것은 거의 없습니다.
1.8배 높음배치 실행 이력이나 적재 메타데이터가 있다면:
SELECT
<business_date>,
<batch_id>,
<run_id>,
<load_mode>,
COUNT(*) AS rows_loaded,
SUM(<amount>) AS amount_loaded,
MIN(<loaded_at>) AS first_loaded_at,
MAX(<loaded_at>) AS last_loaded_at
FROM fct_orders
WHERE <business_date> = DATE '<D>'
GROUP BY <business_date>, <batch_id>, <run_id>, <load_mode>
ORDER BY last_loaded_at;
같은 날짜에 두 개의 적재 실행이 있고 둘 다 금액을 포함하면 append 가능성이 있습니다.
재처리 스크립트의 핵심 동작도 확인해야 합니다.
-- 위험한 형태
INSERT INTO fct_orders
SELECT ...
FROM stg_orders
WHERE <business_date> = DATE '<D>';
-- 날짜 파티션을 교체하는 형태인지 확인
DELETE FROM fct_orders
WHERE <business_date> = DATE '<D>';
INSERT INTO fct_orders
SELECT ...
FROM stg_orders
WHERE <business_date> = DATE '<D>';
파티션 교체, MERGE, overwrite 옵션 없이 단순 INSERT라면 재실행에 안전하지 않을 수 있습니다.
SELECT
<order_id>,
COUNT(*) AS cnt,
SUM(<amount>) AS amount_sum
FROM fct_orders
WHERE <business_date> = DATE '<D>'
GROUP BY <order_id>
HAVING COUNT(*) > 1
ORDER BY cnt DESC;
재처리 실행별로 중복이 나뉘어 있다면:
SELECT
<order_id>,
COUNT(DISTINCT <run_id>) AS run_count,
COUNT(*) AS row_count,
SUM(<amount>) AS amount_sum
FROM fct_orders
WHERE <business_date> = DATE '<D>'
GROUP BY <order_id>
HAVING COUNT(DISTINCT <run_id>) > 1;
각 조인을 한 번에 포함하지 말고 단계별로 측정합니다.
WITH base AS (
SELECT <order_id>, <amount>
FROM stg_orders
WHERE <business_date> = DATE '<D>'
),
joined AS (
SELECT
b.<order_id>,
b.<amount>,
l.<lookup_key>
FROM base b
LEFT JOIN <lookup_table> l
ON b.<join_key> = l.<join_key>
)
SELECT
COUNT(*) AS row_count,
COUNT(DISTINCT <order_id>) AS distinct_orders,
SUM(<amount>) AS amount_sum
FROM joined;
COUNT(*)와 SUM(<amount>)가 조인 전보다 증가하고 COUNT(DISTINCT <order_id>)는 그대로라면 해당 조인이 fan-out입니다.
lookup 키 자체도 확인합니다.
SELECT
<join_key>,
COUNT(*) AS lookup_rows
FROM <lookup_table>
GROUP BY <join_key>
HAVING COUNT(*) > 1;
SELECT
<order_id>,
COUNT(*) AS cnt,
SUM(<amount>) AS amount_sum
FROM stg_orders
WHERE <business_date> = DATE '<D>'
GROUP BY <order_id>
HAVING COUNT(*) > 1;
raw와 stg의 금액을 비교합니다.
SELECT
'raw' AS layer,
COUNT(*) AS row_count,
COUNT(DISTINCT <order_id>) AS distinct_orders,
SUM(<amount>) AS amount_sum
FROM raw_orders
WHERE <business_date> = DATE '<D>'
UNION ALL
SELECT
'stg',
COUNT(*),
COUNT(DISTINCT <order_id>),
SUM(<amount>)
FROM stg_orders
WHERE <business_date> = DATE '<D>';
SELECT
<order_id>,
COUNT(*) AS raw_rows,
MIN(<event_time>) AS first_event_time,
MAX(<ingested_at>) AS last_ingested_at,
SUM(<amount>) AS amount_sum
FROM raw_orders
WHERE <business_date> = DATE '<D>'
GROUP BY <order_id>
HAVING COUNT(*) > 1;
동일 주문의 재전송 이벤트인지, 실제 추가 주문 이벤트인지 이벤트 식별자와 상태 컬럼까지 비교해야 합니다.
대시보드 쿼리를 그대로 떼어내어 fact 단독 집계와 비교합니다.
SELECT SUM(<amount>)
FROM fct_orders
WHERE <business_date> = DATE '<D>';
SELECT SUM(f.<amount>)
FROM fct_orders f
JOIN <bi_dimension_or_lookup> d
ON f.<join_key> = d.<join_key>
WHERE f.<business_date> = DATE '<D>';
두 번째 값만 커지면 BI 조인의 fan-out입니다.
다시 재처리하기 전에 다음을 별도 스냅샷 테이블이나 파일로 보존하세요.
CREATE TABLE investigation_fct_orders_<D> AS
SELECT *
FROM fct_orders
WHERE <business_date> = DATE '<D>';
가능하면 다음도 저장합니다.
fct_orders의 행 수, 주문 수, 금액 합계stg_orders 스냅샷현재 상태를 보존하지 않고 overwrite나 delete 후 재처리하면, “재처리 전부터 잘못됐는지”와 “재처리 때문에 잘못됐는지”를 구분할 수 없게 됩니다.
현재 정보만으로 이 파이프라인이 멱등적이라고 말할 수 없습니다. 다음 중 하나가 보장되어야 멱등적입니다.
MERGE가 안정적인 business key로 기존 행을 갱신재처리 스크립트가 단순 INSERT INTO fct_orders SELECT ...라면 비멱등적이며, 조사를 위해 다시 실행할수록 매출이 더 증가할 수 있습니다. 따라서 먼저 fct_orders와 실행 메타데이터를 스냅샷한 뒤, append인지 overwrite인지 확인하는 것이 첫 번째 점검입니다.
| 대용량 텍스트 변환기 프롬프트 | |
| AI 데이터 추출 도구 기획 프롬프트 | |
| AI2sql 자연어 SQL 생성 프롬프트 | |
| 백엔드 아키텍트 역할을 맡기는 프롬프트 | |
| Base R 프로그래밍 프롬프트 |