본문으로 건너뛰기
이력으로 돌아가기

Iceberg 스키마 에볼루션 구현

기존 데이터를 버리지 않고 컬럼을 추가하는 스키마 변경 흐름

기존 데이터를 유지한 컬럼 추가 흐름과 호환성 검증 규칙을 구현했습니다. 일부 카탈로그 읽기 경로는 추가 확인 중입니다.

  • Java 21
  • Spring Batch
  • Apache Iceberg
  • Nessie
  • MySQL

CSV와 이벤트 입력이 같은 Iceberg 테이블로 모이는 파이프라인에서 컬럼 하나를 추가하려면 기존 데이터를 지우고 다시 적재해야 했습니다. 스키마를 바꾸는 기능만 만드는 것이 아니라 입력, 카탈로그, 물리 테이블과 조회 엔진이 끝까지 같은 스키마 상태를 보게 만드는 작업이 필요했습니다.

문제의 시작

사용자는 CSV 파일이나 연속해서 들어오는 이벤트를 데이터 소스에 쌓고, 그 데이터를 분석과 모델 학습에 사용합니다. 데이터 소스는 한 번 만들고 끝나는 자원이 아니라 새 파일과 레코드가 계속 누적되는 장기 자산입니다. 그 위의 카탈로그는 각 컬럼의 이름과 타입을 정의하고, Iceberg 테이블과 조회 엔진이 같은 스키마를 보게 하는 기준 역할을 했습니다.

기존에는 데이터 소스를 생성한 뒤 사용자 컬럼을 추가하는 API가 없었습니다. 새 컬럼이 포함된 CSV를 올리면 입력 컬럼 수와 카탈로그 컬럼 수가 다르다는 이유로 적재가 거부됐고, 컬럼을 늘리는 유일한 우회는 데이터 소스를 삭제한 뒤 다시 만드는 것이었습니다.

이 제약은 단순한 설정 불편이 아니었습니다. 몇 달 동안 쌓은 데이터에 분석용 필드 하나가 빠졌거나 앞으로 들어올 데이터에 새 지표가 생겨도, 사용자는 과거 데이터와 스냅샷을 포기하거나 별도 데이터 소스를 만들어 흐름을 쪼개야 했습니다. 원본 파일을 모두 보관하지 않았거나 연속 입력으로 받은 데이터라면 삭제 후 재생성은 복구 가능한 선택조차 아니었습니다.

Iceberg에는 데이터를 다시 쓰지 않고 컬럼을 추가하는 기능이 이미 있었지만, 이를 사용자 흐름에 연결하는 것만으로는 충분하지 않았습니다. 입력 적재, 관리 데이터베이스의 카탈로그, Iceberg 물리 스키마와 조회 엔진이 서로 다른 연결과 캐시 경로를 사용했기 때문입니다. 한 지점에서 컬럼 추가가 성공해도 CSV 재적재나 사용자 조회에서 새 컬럼이 보이지 않을 수 있었습니다. 삭제와 타입 변경까지 자동화하면 과거 데이터 해석을 깨뜨릴 수 있어, 처음부터 모든 변경을 허용하는 접근도 제외했습니다.

따라서 검증 질문을 전체 경로에 걸쳐 세웠습니다. 기존 데이터를 보존한 채 새 컬럼이 추가되는가, 과거 행은 새 필드를 NULL로 읽고 이후 입력은 값을 저장하는가, 같은 요청을 다시 보내도 중복 컬럼이 생기지 않는가, 쓰기와 조회가 같은 최신 스키마를 보는가, CSV 타입 추론이 모호한 값을 보수적으로 처리하는가를 확인했습니다. 자동 변경 범위는 안전한 컬럼 추가로 제한하고 삭제와 타입 변경은 명시적으로 막았습니다.

변경 전에는 재생성이 유일한 길

스키마 변경과 데이터 수명을 묶어 둔 것이 문제의 핵심이었습니다.

컬럼 추가 요구는 데이터 소스 재생성과 기존 데이터 손실로 이어지고 CSV 컬럼 수가 다르면 적재가 거부됩니다.

스키마 변경이 반영되는 전체 경로

입력 파일은 적재 엔진을 거쳐 Iceberg 테이블에 쓰이고, 카탈로그의 메타데이터는 별도 조회 엔진을 통해 콘솔에 노출됩니다. 관리 API는 사용자에게 보이는 스키마 규격과 메타데이터를 관리하지만 실제 스키마 커밋은 테이블 관리 서비스에 요청합니다.

따라서 변경 API는 ‘물리 스키마가 바뀌었다’에서 끝나지 않고 조회 캐시 갱신과 사용자 메타데이터 저장까지 순서를 보장해야 했습니다.

적재와 조회가 만나는 스키마 경로

쓰기와 조회가 서로 다른 접근 경로를 사용하므로 양쪽의 일치 여부를 함께 검증합니다.

사용자 파일은 적재 작업을 통해 Iceberg에 쓰이고 관리 API는 테이블 관리 서비스에 변경을 요청합니다. 버전 카탈로그는 조회 엔진과 콘솔로 이어집니다.

안전한 컬럼 추가만 허용하는 검증

자동 변경은 컬럼 추가로 제한했습니다. 중복 이름, 지원하지 않는 타입, 이미 진행 중인 변경, 권한과 대상 상태를 먼저 확인하고 하나라도 위반하면 물리 커밋 전에 멈춥니다.

Iceberg 변경을 먼저 커밋하고 조회 경로를 새로고침한 뒤 사용자 메타데이터를 마지막에 저장했습니다. 메타데이터만 먼저 성공해 실제 테이블과 어긋나는 상태를 피하기 위한 순서입니다.

물리 스키마를 먼저 확정하는 커밋 순서

사용자 메타데이터는 실제 스키마와 조회 경로가 준비된 뒤 확정합니다.

변경 API가 호환성을 검사하고 Iceberg 커밋과 조회 엔진 새로고침을 마친 뒤 사용자 메타데이터를 저장합니다.

CSV 컬럼명과 Iceberg 컬럼 ID의 역할 분리

CSV 열은 파일에 Iceberg 컬럼 ID(field ID)가 없으므로 테이블 필드와 이름으로 연결합니다. 반면 Iceberg 내부의 진화한 컬럼은 이름이나 위치가 아니라 Iceberg 컬럼 ID로 추적해 과거 스냅샷과 새 스냅샷이 같은 필드를 안정적으로 가리키게 했습니다.

과거 CSV에 새 필드가 없으면 NULL로 채우고, 입력에 테이블보다 많은 알 수 없는 필드가 있을 때만 거부했습니다. CSV 컬럼명 연결과 Iceberg 내부 식별을 같은 개념으로 섞지 않은 것이 중요했습니다.

입력 이름과 Iceberg 컬럼 ID의 역할 분리

외부 입력은 이름으로, Iceberg 내부 진화는 Iceberg 컬럼 ID로 추적합니다.

CSV 헤더는 이름으로 테이블 필드에 연결하고 Iceberg 내부에서는 Iceberg 컬럼 ID로 새 스냅샷을 추적합니다. 과거 누락 필드는 함께 조회할 때 NULL로 표시됩니다.

CSV 타입을 안전하게 추론하기

컬럼마다 타입을 손으로 지정하는 비용을 줄이기 위해 파일 전체 대신 앞부분과 최대 행 수를 제한해 샘플링했습니다. 마지막에 잘린 행은 버리고 각 값을 읽을 때마다 컬럼에 가능한 타입의 범위를 좁혔습니다.

판단이 모호하면 문자열로 처리하고 순수 숫자를 자동으로 시간으로 보지 않았습니다. 시간 타입은 API 타입 정의와 서비스 간 전달 규칙, 실제 물리 타입까지 이어지는 매핑을 표로 고정했습니다.

제한된 샘플에서 가능한 타입을 좁히는 과정

전부 읽지 않아도 재현 가능한 범위와 보수적인 기본값으로 타입을 제안합니다.

CSV 앞부분에서 최대 행 수만 읽고 잘린 행을 버린 뒤 컬럼별 타입 후보를 소거해 구체 타입 또는 문자열을 선택합니다.

쓰기는 성공했는데 조회에 안 보인 이유

알파 E2E에서 컬럼 커밋은 성공했지만 조회 엔진에는 새 필드가 보이지 않는 결함을 찾았습니다. 같은 카탈로그 서버를 쓰더라도 쓰기와 읽기가 서로 다른 프로토콜과 캐시 경로를 지나고 있었습니다.

반복 새로고침은 오래된 스키마를 다시 읽을 뿐이었습니다. 임시 우회와 근본적인 연결 방식 통일을 분리하고, 모든 읽기 경로를 완료 조건에 포함했습니다.

같은 카탈로그를 다른 연결 방식으로 본 불일치

서버가 같아도 프로토콜과 캐시가 다르면 같은 스키마를 본다고 가정할 수 없습니다.

스키마 쓰기는 최신 카탈로그에 반영되지만 별도 조회 연결이 오래된 스키마를 반환합니다. 연결 방식을 통일해 조회 엔진에 새 컬럼을 전달합니다.

검증

알파 E2E 11개 시나리오에서 6개 결함을 발견해 수정했습니다. 타입 추론은 13개 경계 케이스로 숫자, 시간, 불리언, 빈 값과 혼합 값을 확인했습니다.

컬럼 추가 전후의 과거·신규 스냅샷을 함께 읽고, 요청 재실행이 같은 결과를 내는지, 과거 CSV의 누락 값이 NULL인지 검증했습니다. 배열 타입이 적재 중 문자열로 덮이는 문제도 경로별 타입 매핑을 추적해 드러냈습니다.

선언 타입이 적재 중 바뀌는 실패

스키마 API만 테스트하지 않고 실제 적재가 타입을 다시 쓰는지도 확인했습니다.

카탈로그와 Iceberg에는 배열로 선언됐지만 CSV 적재 타입 매핑이 문자열로 대체해 스키마를 덮어쓰는 실패 흐름입니다.

배운 점

스키마 에볼루션은 쓰기 API 하나가 아니라 입력, 저장, 카탈로그, 캐시와 조회를 잇는 호환성 계약입니다. 변경의 성공은 모든 소비자가 과거와 현재 데이터를 함께 읽을 때 확정됩니다.

자동화 범위를 컬럼 추가로 제한하고 삭제와 타입 변경은 수동 마이그레이션으로 남긴 덕분에 안전한 경계를 먼저 만들 수 있었습니다.

© 2026 주병주

관찰하고, 기록하고, 더 단단하게 만듭니다.