gold 숫자가 이상할 때 source_hash, quality, lineage로 원인 좁히기
데이터 플랫폼에서 중요한 질문은 "gold 숫자를 만들었는가"에서 끝나지 않는다.
운영 중에는 오히려 이런 질문이 더 자주 나온다.
이 defect_rate가 왜 이렇게 높지?
이 gold row는 어느 source에서 왔지?
row가 처리 중 사라진 건가, 정상 필터링인가?
schema가 바뀐 상태로 계산된 건가?
quality check는 통과했나?
manufacturing-data-platform-mini의 B4 slice는 이 질문을 작게 다룬다. 새 Spark/Iceberg 엔진을 붙이지 않고, 이미 남겨둔 JSON catalog/lineage/quality evidence를 read-only operator report로 읽는다.
이 report는 이상치를 자동 탐지하지 않는다. 숫자를 해석할 provenance/quality 맥락을 제공해서 사람이 원인 후보를 좁히게 하는 도구다.
Scenario
분석가가 business_date=2026-06-29의 defect_rate가 이상하다고 말한다.
운영자는 raw CSV를 바로 열기 전에, 먼저 metadata와 evidence로 원인을 좁히고 싶다.
확인 순서는 이렇다.
1. gold row grain 확인
2. 해당 business_date의 successful run 확인
3. run_id / source_hash / schema_hash 확인
4. quality checks에서 fail/warn 확인
5. lineage path로 gold -> silver -> bronze -> source 역추적
6. row count / conservation / schema drift를 보고 원인 후보 좁히기
Decision Pressure
단순 CSV 변환 스크립트는 보통 output만 남긴다.
input.csv -> gold.csv
하지만 운영 질문은 output만으로 답하기 어렵다.
이 gold.csv는 어떤 입력에서 왔나?
동일 source를 재실행한 건가, 다른 source로 새로 처리한 건가?
source row 5개가 silver 3개가 된 이유는 정상 필터링/중복 제거인가?
집계가 units/defects를 보존했나?
schema drift가 있었나?
그래서 이 프로젝트는 transform output과 함께 run evidence를 남긴다.
source_hash
schema_hash
quality checks
stats
layer parent links
catalog state
이 문제는 data lineage 도구들이 다루는 전형적인 운영 질문의 축소판이다. 예를 들어 OpenLineage는 dataset/job/run metadata를 추적해 문제의 원인과 변경 영향 이해를 돕는 표준을 제공하고, Databricks Unity Catalog lineage는 downstream 결과가 이상할 때 upstream source를 추적하는 root-cause investigation을 lineage의 사용 사례로 설명한다.
이 프로젝트는 그런 시스템을 구현한 것이 아니라, 같은 문제를 path-level JSON evidence로 작게 연습한다.
Options
| Option | 장점 | 문제 | 판단 |
|---|---|---|---|
| raw CSV 직접 열기 | 가장 직접적 | 매번 수동, run/source identity를 놓치기 쉬움 | 보조 수단 |
| gold 파일만 보기 | 빠름 | 원인 추적 불가 | 부족 |
| full lineage system | 강력함 | v0에 과함, OpenLineage backend 미구현 | backlog |
| read-only operator report | 작고 검증 가능, 기존 evidence 재사용 | path-level까지만 가능 | 선택 |
Decision
이 slice에서는 read-only operator report를 선택했다.
명령은 작다.
PYTHONPATH=src python -m manufacturing_data_platform.pipeline.operator_report \
--output-dir /tmp/manufacturing-mini-operator-report-cli \
--business-date 2026-06-29
이 report는 JSON catalog state를 읽어서 아래를 보여준다.
gold grain
run_id
source_hash
schema_hash
quality summary
row counts
lineage trace: gold -> silver -> bronze -> source
claim boundary
또한 report 자체가 자기 한계를 같이 출력한다. 블로그에서만 정직한 것이 아니라, 산출물도 claim boundary를 포함한다.
Evidence
구현 evidence:
src/manufacturing_data_platform/pipeline/operator_report.py
tests/test_operator_report.py
검증 로그:
2026-07-10 — Operator evidence report slice
pytest: 35 passed
lakehouse JSON CLI: passed
operator evidence report CLI: passed
실제 출력 일부:
{
"gold_grain": {
"dataset_id": "manufacturing_daily_metrics",
"row_grain": [
"business_date",
"plant_id",
"line_id",
"product_code"
],
"metrics": [
"units_produced",
"defect_count",
"defect_rate",
"avg_cycle_time_ms",
"closing_status"
]
},
"run": {
"run_id": "2026-06-29-20260710T033849Z-73005763",
"source_hash": "b3ffc4acdd909db1b5a87db7155f76589dfdd4cedb4b00cedf18506f12948604",
"schema_hash": "4414a80b70b7b386a13ee705a33aa9c99bd29cb3a717bba9d2c5f0bc892d3126",
"quality_passed": true,
"reuse_count": 0
},
"quality_summary": {
"total_checks": 8,
"pass_count": 8,
"warn_count": 0,
"fail_count": 0,
"failed_checks": [],
"warning_checks": [],
"rca_focus_checks": [
{"name": "row_count_source_to_silver", "status": "pass"},
{"name": "unit_conservation_silver_to_gold", "status": "pass"},
{"name": "freshness_business_date", "status": "pass"},
{"name": "schema_drift", "status": "pass"}
]
},
"lineage_trace": [
{"name": "gold", "parents": [".../silver/manufacturing_events.csv"]},
{"name": "silver", "parents": [".../bronze/manufacturing_events.csv"]},
{"name": "bronze", "parents": ["data/raw/manufacturing_events.csv"]},
{
"name": "source",
"path": "data/raw/manufacturing_events.csv",
"hash": "b3ffc4acdd909db1b5a87db7155f76589dfdd4cedb4b00cedf18506f12948604",
"row_count": 5
}
],
"claim_boundary": {
"supports": [
"table/path-level lineage",
"operator-inspectable run evidence",
"source/schema identity trace",
"quality check summary"
],
"does_not_support": [
"column-level lineage",
"OpenLineage backend integration",
"interactive lineage UI",
"production incident workflow"
]
}
}
이 출력을 시나리오에 대입하면:
-
quality_summary.fail_count=0,warn_count=0-> 유실, 보존 불일치, schema drift warning이 없었다. 즉 이상한defect_rate는 파이프라인 버그보다 실제 입력 데이터에서 온 값일 가능성이 크다. -
lineage_trace의source.hash와row_count=5-> 어느 파일의 5개 row에서 왔는지 raw를 열기 전에 고정할 수 있다. -
run.reuse_count=0-> 이전 successful run을 재사용한 skip이 아니라 새로 처리된 run이다.
report 하나로 "숫자가 이상하다"를 "파이프라인 check는 정상이고, 출처는 이 source_hash의 5-row file"까지 좁힌다.
Limitations
이건 production lineage system이 아니다.
명확한 한계:
이상치 자동 탐지 아님
자동 RCA 아님
latest successful run evidence 조회용 — failure-state forensics는 backlog
column-level lineage 아님
OpenLineage backend 통합 아님
interactive lineage UI 아님
production incident workflow 아님
real Mongo runtime 검증 아님
Airflow runtime trigger 검증 아님
Spark/Iceberg 구현 아님
이 slice의 목적은 작다.
기존 run/catalog/quality/lineage evidence를 operator가 읽을 수 있는 형태로 묶는다.
정리
output만 남기면 "왜 이 숫자지?"에 답하기 어렵다. output과 함께 source_hash, quality result, lineage evidence를 남기면 raw 파일을 열기 전에 "어디서 왔고, 파이프라인 check는 정상이었는지"까지 원인 후보를 좁힐 수 있다.
코드: github.com/junhyun-dev/manufacturing-data-platform-mini
Interview Line
면접에서는 이렇게 30초로 줄일 수 있다.
I added a read-only operator report for a synthetic manufacturing data platform.
Given a business_date, it reads the JSON catalog state and shows the gold grain,
run_id, source_hash, schema_hash, quality summary, row counts, and a path-level
lineage trace from gold to silver to bronze to source. The goal is to help an
operator narrow a suspicious number to its run/source/quality context. I kept
the claim boundary explicit: this is operator-inspectable path-level lineage
evidence, not anomaly detection, column-level lineage, or an OpenLineage backend.
Top comments (0)