{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreibko5r2rwzh34wj63bqvzutdcyeuzbeyb5fi4iwupxldmdpy2ddga",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mqhkyu3ojva2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreid3uhri3f3komrizhhjl4p4mmxuwtf43ixffrvll42gh3ksrp4l4i"
    },
    "mimeType": "image/webp",
    "size": 32326
  },
  "path": "/junhyun-dev/schema-driftreul-faili-anira-warneuro-dun-iyu-540h",
  "publishedAt": "2026-07-12T15:48:59.000Z",
  "site": "https://dev.to",
  "tags": [
    "dataengineering",
    "python",
    "etl",
    "learning",
    "dbt data tests",
    "dbt severity config",
    "Great Expectations",
    "Apache Iceberg schema evolution"
  ],
  "textContent": "#  schema drift를 fail이 아니라 warn으로 둔 이유\n\n데이터 파이프라인에서 source schema가 바뀌는 순간은 애매하다.\n\n무조건 무시하면 운영자는 입력 구조가 바뀐 사실을 모른다. 반대로 모든 schema 변화를 실패로 처리하면, 정상적인 컬럼 추가까지 daily run을 막아버린다.\n\n`manufacturing-data-platform-mini`에서는 이 문제를 작게 다뤘다. synthetic manufacturing CSV의 실제 header를 기준으로 `schema_hash`를 만들고, previous successful run과 비교해 달라졌으면 `schema_drift` quality check를 `warn`으로 남긴다. 단, required column이 빠져 silver/gold contract를 만들 수 없는 경우는 현재 `ValueError`로 빠르게 실패한다.\n\n##  1. Scenario\n\n어느 날 source CSV에 새 컬럼이 추가된다.\n\n기존 header:\n\n\n\n    event_time,plant_id,line_id,work_order_id,machine_id,product_code,\n    operation,units_produced,defect_count,cycle_time_ms,business_date\n\n\n새 header:\n\n\n\n    event_time,plant_id,line_id,work_order_id,machine_id,product_code,\n    operation,units_produced,defect_count,cycle_time_ms,business_date,operator_id\n\n\n`operator_id`는 아직 silver/gold mart에서 쓰지 않는다. 하지만 source 구조가 바뀐 사실은 기록되어야 한다.\n\n##  2. Decision Pressure\n\nschema drift에서 중요한 질문은 단순히 \"바뀌었나?\"가 아니다.\n\n\n\n    바뀐 것을 운영자가 알 수 있는가?\n    정상적인 컬럼 추가 때문에 pipeline을 멈춰야 하는가?\n    downstream gold mart contract가 조용히 바뀌지는 않는가?\n    이전 successful run과 지금 run의 schema identity를 비교할 수 있는가?\n\n\n초기 구현에서는 한 가지 실제 버그가 있었다. `schema_hash`가 고정된 required column 목록에 너무 묶여 있어서, 추가 컬럼이 들어와도 hash가 바뀌지 않았다. 즉 `operator_id`가 추가되어도 drift가 보이지 않았다.\n\n이 문제를 고치기 위해 `read_rows`가 실제 CSV header를 반환하고, 그 실제 header 기준으로 `schema_hash`를 계산하도록 바꿨다.\n\n##  3. Options\n\noption | result | risk\n---|---|---\nignore drift | pipeline은 계속 돈다 | source 변화가 보이지 않음\nfail every drift | 변화에 강하게 반응 | 정상적인 컬럼 추가도 막음\nwarn and continue | 변화가 보이고 run도 계속됨 | warning을 inspect해야 함\nauto-evolve silver/gold | 새 컬럼을 바로 사용 가능 | downstream contract가 조용히 바뀔 수 있음\nfull schema registry | production에 가까움 | mini slice에는 무거움\n\n이 프로젝트의 선택은 `warn and continue`다.\n\n##  4. Decision\n\n현재 contract는 이렇다.\n\n\n\n    previous successful run이 없으면:\n      schema_drift = pass\n      baseline schema established\n\n    current schema_hash == previous successful schema_hash:\n      schema_drift = pass\n\n    current schema_hash != previous successful schema_hash:\n      schema_drift = warn\n      quality_passed는 true 유지\n      run/lineage record에 previous/current schema_hash 저장\n\n    required column missing:\n      ValueError fast fail\n\n\n중요한 구분:\n\n\n\n    schema_drift warn:\n      source shape이 바뀌었다. 하지만 현재 silver/gold contract는 만들 수 있다.\n\n    missing required column failure:\n      현재 mart contract를 만들 수 없다.\n\n\n이 선택은 \"schema registry를 만들었다\"는 뜻이 아니다. schema change를 invisible하게 두지 않고, quality/check result와 run metadata에 남긴다는 작은 contract다.\n\n비슷한 방향의 외부 기준도 있다.\n\n  * dbt data tests는 `unique`, `not_null`, `accepted_values` 같은 generic test로 data contract를 드러낸다.\n  * dbt severity config는 test 결과를 error 또는 warn으로 다룰 수 있게 한다.\n  * Great Expectations는 Expectation을 데이터에 대한 검증 가능한 assertion으로 본다.\n  * Apache Iceberg schema evolution은 schema 변화를 table metadata 차원에서 다루는 더 큰 방향이다.\n\n\n\n이 프로젝트는 그 도구들을 구현한 것이 아니라, 같은 운영 질문을 mini pipeline의 `schema_hash`와 `schema_drift` warning으로 작게 연습한다.\n\n##  5. Evidence\n\n관련 코드와 문서:\n\n\n\n    src/manufacturing_data_platform/pipeline/lakehouse.py\n    learn/reference-decisions/schema-drift.md\n    VERIFICATION_LOG.md\n    README.md\n\n\n핵심 테스트:\n\n\n\n    tests/test_lakehouse_pipeline.py\n      test_schema_drift_helper_states\n      test_schema_drift_warns_against_previous_successful_run\n      test_schema_stable_when_schema_unchanged_across_dates\n      test_schema_drift_warns_on_added_column\n\n\n특히 `test_schema_drift_warns_on_added_column`은 `operator_id` 컬럼이 추가됐을 때:\n\n\n\n    schema_drift.status == \"warn\"\n    previous schema hash != current schema hash\n    quality_passed is True\n\n\n를 확인한다.\n\n검증 로그에도 이 버그 수정이 남아 있다.\n\n\n\n    2026-06-30 pre-Codex self-audit:\n      schema_hash was computed from fixed REQUIRED_COLUMNS, not the actual CSV header.\n      Fix: read_rows now returns the actual header; schema_hash uses that actual header.\n\n\n그리고 최신 local verification에서도 전체 테스트와 CLI가 통과했다.\n\n\n\n    2026-07-10:\n      pytest: 35 passed\n      lakehouse JSON CLI: passed\n\n\n##  6. Limitations\n\n정직한 한계:\n\n\n\n    full schema registry는 없다.\n    column-level diff UI도 없다.\n    Iceberg/Delta schema evolution은 아직 구현되지 않았다.\n    schema_drift는 hash 중심이라 어떤 컬럼이 바뀌었는지 사람이 바로 보기엔 제한이 있다.\n    warning을 운영자가 inspect하지 않으면 놓칠 수 있다.\n    missing required column은 아직 structured quality report가 아니라 ValueError fast fail이다.\n\n\n그래서 이 글의 claim은 작게 둔다.\n\n\n\n    actual CSV header 기준 schema_hash를 만들고,\n    previous successful run과 비교해 schema drift를 warn으로 드러내는 mini contract를 구현했다.\n\n\n##  7. Why This Connects To Iceberg Later\n\nSlice1은 schema drift를 detect하고 warn으로 남긴다.\n\nIceberg로 가면 질문이 바뀐다.\n\n\n\n    새 컬럼을 Iceberg table에 add column으로 반영할 것인가?\n    어떤 변화는 허용하고 어떤 변화는 금지할 것인가?\n    과거 snapshot은 어떤 schema로 읽히는가?\n    downstream gold mart contract가 조용히 바뀌지 않게 어떻게 막을 것인가?\n\n\n즉 Slice1의 `schema_hash + warn`은 나중에 Iceberg schema evolution으로 이어지는 출발점이다. 하지만 현재 repo에서 Iceberg schema evolution은 design-only이며 구현 evidence가 아니다.\n\n##  8. 정리\n\nschema drift를 전부 failure로 처리하면 정상적인 source 확장을 막을 수 있다. 반대로 아무 기록 없이 통과시키면 downstream contract가 조용히 흔들릴 수 있다.\n\n이 slice에서는 실제 CSV header 기준 `schema_hash`를 남기고, previous successful run과 달라진 header를 `warn`으로 드러낸다. run은 계속되지만 운영자는 source 구조 변화를 inspect할 수 있다. required column이 빠져 silver/gold contract를 만들 수 없는 경우는 빠르게 실패시킨다.",
  "title": "schema drift를 fail이 아니라 warn으로 둔 이유"
}