LLM WikiAccess-protected knowledge portal
← 스터디 홈
6편 · 약 24분

Schema evolution과 compatibility: add/drop/rename, reader/writer contract

스키마 변경은 DDL이 아니라 계약 변경이다

Lakehouse table format에서 ALTER TABLE ADD COLUMN은 보통 빠르다. Iceberg, Delta Lake, Hudi 모두 데이터 파일을 전부 다시 쓰지 않고도 일정 범위의 schema evolution을 지원한다. 그래서 처음에는 “컬럼 하나 추가하는 정도는 metadata만 바꾸면 되겠네”라고 느끼기 쉽다.

하지만 운영 관점에서 더 중요한 질문은 다르다.

새 schema를 쓰는 writer, 오래된 schema로 읽는 reader, 과거 파일, 현재 파일, streaming job이 동시에 존재할 때도 안전한가?

스키마 변경은 테이블 자체의 모양만 바꾸지 않는다. ingestion job, CDC pipeline, batch transform, BI dashboard, feature pipeline, ad-hoc query, data quality rule까지 영향을 준다. 특히 lakehouse는 여러 engine이 같은 테이블을 읽고 쓰는 구조가 흔하다. Spark가 쓴 테이블을 Trino가 읽고, Flink가 streaming sink로 쓰고, ClickHouse나 외부 테이블이 일부 데이터를 조회할 수 있다. 이때 schema evolution은 “지원 여부”보다 호환성 계약으로 이해해야 한다.

Writer contract
new files / new schema
Table metadata
schema version / field ID
Reader contract
old + new files
Old data files
previous schema
New data files
current schema
Downstream consumers
queries / streams / BI
Schema evolution에서 동시에 맞춰야 하는 계약

이 장에서는 add, drop, rename, type widening 같은 변경을 “문법”이 아니라 운영 계약으로 다룬다. 핵심은 다음 세 가지다.

  1. 테이블 포맷이 안전하게 매핑할 수 있는 변경인지 확인한다.
  2. reader와 writer가 어떤 버전·기능을 이해하는지 확인한다.
  3. 소비자가 의미적으로 깨지는 변경인지 별도로 검토한다.

1. Backward, forward, full compatibility를 운영 언어로 바꾸기

Schema Registry 문서에서 자주 쓰는 용어는 lakehouse에도 그대로 도움이 된다. 표현은 조금 추상적이지만, 운영 상황으로 바꾸면 어렵지 않다.

호환성운영 의미예시 질문
Backward compatible새 reader가 과거 writer가 만든 데이터를 읽을 수 있음새 Spark job이 과거 Parquet 파일도 읽는가?
Forward compatible오래된 reader가 새 writer가 만든 데이터를 읽을 수 있음아직 배포되지 않은 BI/ETL이 새 파일을 읽어도 실패하지 않는가?
Full compatible양방향 모두 가능순차 배포, rollback, replay가 모두 안전한가?
Transitive직전 버전뿐 아니라 모든 과거 버전과 호환1년 전 snapshot이나 Kafka replay까지 읽을 수 있는가?

Lakehouse table에서 backward compatibility는 특히 중요하다. 테이블은 보통 오래된 파일과 최신 파일을 함께 갖고 있다. 새 schema로 읽을 때 예전 파일에 컬럼이 없으면 NULL 또는 default처럼 해석할 수 있어야 한다. 반대로 forward compatibility는 점진 배포와 rollback에서 중요하다. 새 writer가 만든 파일을 오래된 reader가 읽을 때, 모르는 컬럼을 무시할 수 있는지, rename/drop을 이해할 수 있는지가 문제가 된다.

여기서 주의할 점은 “쿼리가 에러 없이 돈다”와 “비즈니스 의미가 유지된다”가 다르다는 것이다. 예를 들어 customer_idaccount_id로 rename했는데, 테이블 포맷이 column ID로 안전하게 매핑해도, downstream SQL이 여전히 customer_id를 참조하면 실패한다. 또는 status 값의 의미를 바꾸면 schema는 같아도 dashboard 해석이 깨진다.

따라서 호환성은 두 층으로 나누어 봐야 한다.

  • Physical/schema compatibility: 파일과 metadata를 reader가 해석할 수 있는가?
  • Semantic compatibility: 소비자가 기대한 컬럼 이름, 의미, nullability, 단위, cardinality가 유지되는가?

2. Add column: 가장 안전하지만 required column은 조심한다

새 컬럼 추가는 가장 흔하고 대체로 안전한 변경이다. 기존 파일에는 새 컬럼이 없으므로 reader는 과거 파일에서 해당 값을 NULL로 본다. Iceberg는 field ID와 schema history를 이용해 새 컬럼을 기존 파일에 강제로 맞추지 않고 읽을 수 있다. Delta Lake도 schema evolution 또는 DDL을 통해 새 컬럼을 추가할 수 있고, Hudi도 root 또는 nested struct 끝에 nullable field를 추가하는 backward-compatible evolution을 지원한다.

문제는 “추가”가 항상 안전하다는 식으로 운영하면 생긴다.

추가 방식안전도운영 포인트
nullable column 추가높음과거 파일은 NULL, downstream은 null 처리 필요
default가 있는 complex field 추가조건부 안전writer/reader가 default 해석을 일관되게 하는지 확인
non-nullable column 추가위험과거 파일에 값이 없으므로 read/write 실패 또는 의미 불일치 가능
중간 위치에 컬럼 추가포맷별 차이field ID 기반이면 안전하지만 position 기반 reader는 위험

실무에서는 새 컬럼을 바로 required로 만들기보다 다음 순서가 안전하다.

  1. nullable로 추가한다.
  2. writer를 배포해 새 컬럼을 채우기 시작한다.
  3. data quality check로 null 비율과 backfill 범위를 확인한다.
  4. 필요한 경우 과거 데이터 backfill을 수행한다.
  5. 모든 consumer가 준비된 뒤 not-null expectation이나 validation rule을 강화한다.

이 순서를 지키면 schema evolution과 배포 순서를 분리할 수 있다. 반대로 “DDL에서 NOT NULL로 추가하고 writer도 동시에 바꾸자”는 방식은 rollback과 replay에 약하다.


3. Drop column: metadata에서는 사라져도 파일과 소비자는 남아 있다

컬럼 삭제는 겉보기보다 위험하다. 테이블 metadata에서 컬럼을 제거하는 것과, object storage에 이미 쓰인 Parquet 파일에서 해당 물리 데이터를 제거하는 것은 다른 일이다. 많은 table format은 drop을 metadata-only로 처리할 수 있다. 이 경우 새 schema에서는 컬럼이 보이지 않지만, 오래된 파일 안에는 데이터가 남아 있을 수 있다. 실제 물리 삭제는 rewrite, vacuum, cleaner, retention 정책과 연결된다.

운영 리스크는 세 가지다.

  1. Downstream query 실패: 삭제된 컬럼을 참조하는 dashboard, model, notebook이 깨진다.
  2. Data governance 착각: metadata에서 안 보인다고 민감 정보가 즉시 물리 삭제된 것은 아니다.
  3. Rollback 혼란: 삭제 후 다시 같은 이름으로 컬럼을 만들 때 예전 field와 새 field가 같은 의미인지 모호해진다.

Iceberg는 field ID를 재사용하지 않는 방식으로 silent corruption을 피한다. 삭제된 컬럼의 ID는 active schema에서 제거되고, 같은 이름을 나중에 다시 만들어도 새 ID가 붙는다. Delta Lake는 column mapping을 활성화하면 rename/drop을 metadata-only로 처리할 수 있지만, 이 기능은 reader/writer protocol 요구사항을 올린다. Hudi는 schema-on-write에서 삭제 같은 backward-incompatible 변경을 일반적으로 안전한 기본 경로로 보지 않고, 더 유연한 schema-on-read는 실험적 성격과 irreversible한 운영 제약이 있다.

민감 정보 삭제라면 “DROP COLUMN을 실행했다”로 끝내면 안 된다. 최소한 다음을 확인해야 한다.

  • time travel, snapshot, Delta log, Hudi timeline retention 기간
  • vacuum/cleaner/snapshot expiration 후 실제 파일 제거 여부
  • backup과 replica에 남은 데이터
  • downstream export, BI extract, feature store copy
  • 감사 로그에 남길 승인과 삭제 증적

즉 drop column은 schema change이면서 data lifecycle change다.


4. Rename column: field ID가 없으면 사실상 drop + add다

Rename은 사람이 보기에는 단순하다. cust_idcustomer_id로 바꾸면 더 명확해 보인다. 하지만 데이터 파일 관점에서는 rename을 안전하게 인식하려면 “이름은 바뀌었지만 같은 field”라는 식별자가 필요하다.

Iceberg는 column을 이름이나 위치가 아니라 field ID로 추적한다. 그래서 rename은 metadata에서 이름만 바꾸는 작업이 될 수 있다. 기존 파일의 field ID는 그대로이므로 새 이름으로 읽을 수 있다. 이 점이 Iceberg schema evolution의 큰 장점이다.

Delta Lake도 column mapping을 활성화하면 rename/drop을 metadata-only로 처리할 수 있다. 단, column mapping은 table protocol을 올리고 일부 downstream 동작, streaming, change data feed, partition directory 방식과 관련된 제약을 확인해야 한다. 이미 여러 engine이 읽는 운영 테이블이라면 “rename 가능”보다 “모든 reader가 column mapping table을 지원하는가”가 먼저다.

Hudi는 기본 schema-on-write 경로에서 rename을 streaming 환경에 적합한 변경으로 보지 않는다. Hudi 문서는 streaming data에는 incompatible schema change를 적용할 자연스러운 boundary가 없다고 설명한다. schema-on-read를 켜면 rename 같은 변경을 더 유연하게 다룰 수 있지만, 해당 기능은 실험적이며 한 번 활성화하면 되돌릴 수 없다는 제약이 있다.

실무에서 rename은 다음 중 하나로 처리하는 편이 안전하다.

전략장점단점
진짜 renameschema가 깔끔함downstream SQL과 engine 호환성 리스크
새 컬럼 추가 후 dual-write점진 이전 가능일정 기간 중복 컬럼과 sync 검증 필요
view/semantic layer에서 alias 제공소비자 충격 완화원본 테이블 schema는 복잡하게 남을 수 있음
breaking change로 새 테이블 생성계약 명확migration 비용과 dual-run 필요

운영 테이블에서는 rename을 “리팩터링”처럼 가볍게 처리하지 않는 것이 좋다. 컬럼 이름은 API field처럼 소비자 계약의 일부다.


5. Type change와 nullability: widening과 narrowing을 구분한다

타입 변경은 add/drop/rename보다 더 미묘하다. intlong으로 넓히는 것은 대체로 안전한 widening이다. 반대로 longint로 줄이거나, stringint로 바꾸거나, nullable을 non-nullable로 강화하는 것은 기존 데이터와 consumer를 깨뜨릴 수 있다.

변경일반적 판단이유
intlong비교적 안전값 표현 범위가 넓어짐
floatdouble비교적 안전정밀도와 표현 범위가 넓어짐
decimal precision 증가조건부 안전scale 유지 여부와 engine 지원 확인 필요
longint위험overflow 가능
nullable → non-nullable위험과거 파일과 late data에 null 가능
non-nullable → nullable대체로 안전하지만 의미 변화consumer가 null을 처리해야 함

Iceberg는 안전한 type promotion을 schema evolution 범위로 다룬다. Hudi도 write 경로에서 type promotion matrix를 제공하지만 demotion은 지원하지 않는다. Delta Lake는 type widening 기능과 schema enforcement/evolution 옵션을 구분해서 봐야 한다. 즉 “engine이 자동으로 schema를 merge한다”와 “모든 타입 변경이 안전하다”는 같은 말이 아니다.

운영에서는 타입 변경보다 새 컬럼을 추가하는 방식이 더 안전할 때가 많다. 예를 들어 amount_centsdecimal로 바꾸고 싶다면, 기존 컬럼을 직접 바꾸기보다 amount_decimal을 추가하고 dual-write/backfill/consumer migration 후 기존 컬럼을 deprecate하는 방식이 rollback에 강하다.


6. 포맷별 schema evolution 감각

Apache Iceberg

Iceberg의 핵심은 field ID와 metadata 기반 schema history다. 컬럼을 이름이나 physical position으로만 추적하지 않기 때문에 add, drop, rename, reorder, safe type promotion을 metadata-only에 가깝게 처리할 수 있다. 또한 snapshot마다 당시 schema를 추적하므로 과거 파일과 새 파일이 공존하는 상황을 더 명시적으로 다룬다.

운영자가 봐야 할 포인트는 다음이다.

  • field ID가 보존되는 engine 경로를 쓰고 있는가?
  • 외부 engine이 Iceberg metadata를 제대로 이해하는가, 아니면 단순 Parquet path처럼 읽고 있는가?
  • rename/drop 이후 old snapshot과 time travel query가 필요한가?
  • schema evolution과 partition evolution이 동시에 일어날 때 query plan이 예상대로 pruning하는가?

Iceberg 테이블을 그냥 Parquet 파일 묶음처럼 우회해서 읽으면, Iceberg가 제공하는 schema evolution 안전장치를 잃을 수 있다. Iceberg는 table metadata를 통해 읽어야 한다.

Delta Lake

Delta Lake는 transaction log와 schema enforcement/evolution을 함께 제공한다. 일반적인 append나 merge에서 새 컬럼을 허용하려면 mergeSchema 또는 auto merge 같은 옵션을 명시적으로 사용하는 경우가 많다. 이 점은 실수로 schema가 오염되는 것을 막는 장점이기도 하다.

Rename/drop은 column mapping과 연결해서 봐야 한다. Column mapping은 logical column과 physical column mapping을 분리해 metadata-only rename/drop을 가능하게 하지만, reader/writer protocol 요구사항을 올리고 호환성 제약을 만든다. 따라서 운영 테이블에서는 다음 질문이 필요하다.

  • 모든 runtime과 connector가 필요한 Delta protocol을 지원하는가?
  • streaming read/write, change data feed, external reader에 제한이 없는가?
  • partitioned table에서 physical directory naming 변화가 운영 도구와 충돌하지 않는가?
  • column mapping을 켜기 전으로 되돌릴 수 있는가?

Delta에서는 schema evolution을 편하게 켜는 것과 schema enforcement를 유지하는 것 사이의 균형이 중요하다. 모든 job에 auto merge를 무심코 켜면 의도하지 않은 컬럼이 production table에 붙을 수 있다.

Apache Hudi

Hudi는 schema-on-write와 schema-on-read를 구분한다. 기본적으로 권장되는 것은 schema-on-write에서 backward-compatible 변경을 처리하는 것이다. 예를 들어 nullable field 추가, type promotion, nullable nested field 추가 같은 변경은 write 시점에 안정적으로 다루기 좋다.

반면 rename, delete, move 같은 더 유연한 변경은 schema-on-read의 영역이다. 이 기능은 더 많은 변경을 read 시점에 해석할 수 있게 하지만 실험적이고, 활성화 후 비활성화할 수 없다는 제약이 있다. Hudi를 streaming ingestion과 함께 쓴다면 “중간에 모든 producer/consumer가 멈추는 boundary”가 없다는 점도 중요하다.

Hudi 운영에서는 다음을 특히 확인한다.

  • write schema와 table schema가 어긋났을 때 null 채움 설정이 필요한가?
  • COW와 MOR에서 같은 schema change가 동일하게 안전한가?
  • reader engine이 schema reconciliation을 동일하게 수행하는가?
  • schema-on-read를 켜는 결정이 장기 table contract에 맞는가?

7. Reader/writer contract로 변경을 설계하기

스키마 변경을 안전하게 하려면 DDL 순서가 아니라 배포 순서로 생각해야 한다. 가장 실무적인 모델은 writer contract와 reader contract를 분리하는 것이다.

Add column 배포 예시

  1. 테이블에 nullable column을 추가한다.
  2. reader는 새 컬럼이 없어도 동작하도록 유지한다.
  3. writer가 새 컬럼을 채우도록 배포한다.
  4. null 비율, late event, backfill 상태를 관찰한다.
  5. 소비자가 새 컬럼을 사용하도록 점진 배포한다.
  6. 충분한 기간 후 validation rule을 강화한다.

Rename 배포 예시

  1. 실제 rename이 필요한지, alias/view로 해결 가능한지 검토한다.
  2. 안전한 rename을 지원하는 포맷 기능과 engine 버전을 확인한다.
  3. 중요한 consumer 목록을 만든다.
  4. 가능하면 새 이름 컬럼을 추가하고 dual-write한다.
  5. downstream을 새 이름으로 옮긴다.
  6. 구 이름 컬럼을 deprecated 상태로 두고 사용량을 관찰한다.
  7. 충분한 기간 후 drop을 별도 변경으로 처리한다.

Drop column 배포 예시

  1. query log, lineage, catalog에서 사용자를 찾는다.
  2. deprecation notice와 deadline을 둔다.
  3. 먼저 view/semantic layer에서 숨기거나 read path 사용을 제거한다.
  4. table metadata에서 drop한다.
  5. retention, time travel, backup, export에 남은 물리 데이터를 확인한다.
  6. 민감 정보라면 cleanup 증적을 남긴다.

이렇게 보면 schema evolution은 한 번의 명령이 아니라 여러 release에 걸친 migration이다. 특히 DBA나 data platform engineer는 “DDL 성공”이 아니라 “모든 reader/writer가 계약을 지켰다”를 완료 조건으로 잡아야 한다.


8. 운영 체크리스트

스키마 변경 PR 또는 작업 요청을 받으면 다음 질문을 순서대로 확인한다.

질문확인 이유
변경 유형이 add/drop/rename/type/nullability 중 무엇인가?위험도가 다름
과거 파일을 새 schema로 읽을 수 있는가?backward compatibility 확인
오래된 reader가 새 파일을 읽어도 실패하지 않는가?forward compatibility 확인
모든 engine이 필요한 table feature/protocol을 지원하는가?Spark만 보고 결정하면 위험
downstream SQL, BI, dbt model, notebook 사용처를 찾았는가?semantic compatibility 확인
streaming job과 replay가 있는가?transitive compatibility와 rollback 확인
민감 정보 삭제나 보존 정책과 연결되는가?physical cleanup 필요
rollback은 DDL rollback인가, 새 migration인가?metadata-only 변경도 되돌리기 어려울 수 있음
변경 후 어떤 지표를 볼 것인가?null ratio, job failure, schema drift, query error

자동화할 수 있는 부분도 있다.

  • catalog/lineage 기반 column usage 조회
  • CI에서 Schema Registry compatibility check 또는 table schema diff check
  • production table에 대한 unsafe auto merge 제한
  • schema change 승인 템플릿
  • reader/writer minimum version 정책
  • drop 전 deprecation period 강제

좋은 schema evolution 운영은 변경을 막는 것이 아니다. 변경이 반복되어도 소비자와 저장 데이터가 서로 속지 않도록, 계약과 절차를 명확히 만드는 것이다.


정리

Schema evolution은 lakehouse table format의 핵심 장점이다. 하지만 장점이 있다는 말은 아무 변경이나 안전하다는 뜻이 아니다.

  1. Add column은 가장 안전하지만 nullable/default/backfill 전략이 필요하다.
  2. Drop column은 metadata 변경과 physical deletion, downstream 제거를 분리해서 봐야 한다.
  3. Rename은 field ID나 column mapping 같은 안정적 식별자가 없으면 사실상 drop + add가 될 수 있다.
  4. Type change는 widening과 narrowing을 구분하고, 위험한 변경은 새 컬럼 migration이 더 안전할 수 있다.
  5. Iceberg, Delta Lake, Hudi는 모두 schema evolution을 지원하지만 field ID, column mapping, schema-on-write/read 같은 운영 모델이 다르다.

결국 스키마 변경의 완료 조건은 “명령이 성공했다”가 아니다. 새 writer, 오래된 reader, 과거 파일, 최신 파일, downstream consumer가 동시에 존재해도 데이터 계약이 깨지지 않는 상태가 완료 조건이다.

References

  • Apache Iceberg Docs, “Evolution”, https://iceberg.apache.org/docs/latest/evolution/
  • Apache Iceberg Docs, “Schemas”, https://iceberg.apache.org/docs/latest/schemas/
  • Delta Lake Docs, “Schema enforcement”, https://docs.delta.io/latest/delta-batch.html#schema-validation-1
  • Delta Lake Docs, “Delta column mapping”, https://docs.delta.io/latest/delta-column-mapping.html
  • Delta Lake Docs, “Type widening”, https://docs.delta.io/latest/delta-type-widening.html
  • Apache Hudi Docs, “Schema Evolution”, https://hudi.apache.org/docs/schema_evolution/
  • Confluent Documentation, “Schema Evolution and Compatibility”, https://docs.confluent.io/platform/current/schema-registry/fundamentals/schema-evolution.html