+ Thorough stage checks, invariants, and evidence preservation.
- It omits queries for several symptom-shape candidates.
Works backward from a bad final number to the stage that changed it.
| Category | Development › Data & databases |
|---|---|
| Tags | AnalyzingReviewingDeveloper |
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.
Pipelines fail silently with wrong values rather than errors. This compares row counts and totals stage by stage to bisect where the value changed.
ChatGPT is the strongest and most reliable. Gemini is concrete but overconfident and invents a date, while [C] is absent.
+ Thorough stage checks, invariants, and evidence preservation.
- It omits queries for several symptom-shape candidates.
+ Concrete queries and decision criteria for likely causes.
- It invents a date and prematurely rules out possible causes.
| Criterion | ChatGPT | Gemini | Leader |
|---|---|---|---|
| Instruction following | 9 | 7 | ChatGPT +29% |
| Accuracy | 9 | 5 | ChatGPT +80% |
| Specificity | 9 | 8 | ChatGPT +13% |
| Structure | 9 | 8 | ChatGPT +13% |
| Right length | 7 | 6 | ChatGPT +17% |
Scored 1–10 by gpt-5.6-sol with model names hidden (2026-09-25). This is an AI review, not a measurement.
We gave three models the same input and copied their answers unedited. Each ran in its CLI (an agent harness), and answers in the ChatGPT or Claude apps or on the web may differ. Outputs are in Korean.
Pipeline: raw_orders(원본 수집 로그) → stg_orders(정제·중복 제거) → fct_orders(일별 배치 팩트 테이블) → BI 대시보드 집계 How it is wrong: 어제부터 fct_orders의 일별 매출 합계가 실제보다 약 1.8배 높게 찍힘. 어제 새벽 프로모션 이벤트 로그가 늦게 도착해서 재처리 스크립트를 한 번 더 돌렸음 What I checked: 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인지 확인하는 것이 첫 번째 점검입니다.
| Advanced Text Converter for Large Datasets | |
| AI-powered data extraction and organization tool | |
| AI2sql SQL Model — Query Generator | |
| Backend Architect | |
| base-R |