LabHub
Get started
배우기 러닝패스 코스

Lakehouse Table Format — Understanding Apache Iceberg Through Its Metadata

Two ways to change rows in files you can't modify — copy-on-write and merge-on-read

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

Parquet 파일은 고칠 수 없으므로 행 하나를 바꾸려면 그 파일을 새로 쓰거나(copy-on-write), '어느 파일의 몇 번째 행은 지워졌다' 는 삭제 파일을 따로 써 두고 읽을 때 걸러야(merge-on-read) 한다. 앞은 쓰기가 비싸고 읽기가 싸며, 뒤는 그 반대다. 표 속성 세 개가 이것을 정한다.

왜 문제가 되나 — 백만 행 파일의 한 행

주문 한 건이 환불로 바뀌었다. 그 주문이 든 Parquet 파일에는 다른 주문이 백만 건 들어 있다. 파일은 한 번 쓰이면 바뀌지 않는다(스펙의 요구 조건이 '제자리 쓰기 없음' 이다). 그러면 한 건을 고치려고 백만 건을 다시 써야 할까?

형식 판 1 에서는 그랬다. 판 2가 이 문제를 풀려고 삭제 파일을 더했다. 불변 데이터 파일을 다시 쓰지 않고도 그 안의 개별 행을 지우거나 바꿀 수 있게 된 것이다.

어떻게 동작하나 — 삭제의 세 가지 모양

행 단위 삭제에는 두 갈래가 있다.

종류 무엇으로 지우나 누가 주로 쓰나
위치 삭제 파일 (판 2) (데이터 파일 경로, 행 위치) Spark 의 merge-on-read
deletion vector (판 3 이상) 데이터 파일마다 비트맵 하나 판 3 표의 위치 삭제
동등 삭제 파일 열 값 (예: order_id = 'O123') 스트리밍 upsert(Flink 등)

위치 삭제 파일file_pathpos(0 부터 센 행 번호) 두 열짜리 파일이고, file_path 다음 pos 순으로 정렬해 쓴다. 읽는 쪽은 데이터 파일을 읽으면서 그 파일의 지운 위치를 건너뛴다. deletion vector는 같은 정보를 Puffin 파일 안의 로어링 비트맵으로 더 작게 담고, 데이터 파일마다 최대 하나만 둔다. 판 3 에서는 위치 삭제 파일이 폐기 예정이 되었다.

동등 삭제 파일은 위치를 모르고 값만 안다. 스트리밍 upsert 는 옛 행이 어느 파일 몇 번째에 있는지 찾을 여유가 없으니 '이 키의 옛 행은 지워라' 만 적는다. 대신 읽는 쪽이 모든 관련 데이터 파일의 행과 값을 맞춰 봐야 해서 가장 비싸다.

어떤 삭제가 어떤 데이터에 걸리는지는 시퀀스 번호가 정한다. 스캔 계획 규칙에 따르면 위치 삭제는 시퀀스 번호가 같거나 작은 데이터 파일에, 동등 삭제는 엄격히 작은 데이터 파일에만 걸린다. 그래서 동등 삭제를 쓴 뒤 같은 키로 다시 넣은 새 행은 지워지지 않는다.

쓰기 모드 — 표가 기억한다

Spark 가 DELETE·UPDATE·MERGE 를 어느 쪽으로 할지는 표 속성 write.delete.mode·write.update.mode·write.merge.mode 가 정하고, 셋 다 기본은 copy-on-write 다. merge-on-read 는 판 2 이상에서만 된다.

MERGE INTO는 WHEN MATCHED(지우기·고치기)와 WHEN NOT MATCHED(넣기)를 한 커밋으로 한다. 원본 한 행에 변경 두 개가 걸리면 어느 쪽을 적용할지 알 수 없어 실패하므로 변경 묶음은 키마다 하나로 정리해 둔다.

현장에서 만나는 모습

CDC 적재 표가 점점 느려진다. 몇 분마다 MERGE 하는 merge-on-read 표는 삭제 파일이 계속 쌓이고 읽을 때마다 그것을 맞춘다. 주기적인 압축(rewrite_data_files 의 삭제 비율·개수 기준, rewrite_position_delete_files)으로 삭제를 데이터에 녹여 넣어야 한다.

다른 엔진이 지운 행을 돌려준다. 삭제 파일을 이해하지 못하는 엔진은 merge-on-read 표에서 지운 행까지 읽는다. 여러 엔진이 같은 표를 읽는다면 각 엔진이 어떤 삭제 형식을 지원하는지 먼저 확인한다. 실습 환경의 DuckDB 1.5.5 와 pyiceberg 0.12 는 판 2 위치 삭제를 반영해 읽는다.

하루 한 번 크게 고치는 표. 야간 배치가 어제 파티션을 통째로 고치고 낮에는 대시보드가 수백 번 읽는다면 copy-on-write 가 낫다. 쓰기 비용은 한 번이고 읽기 이득은 수백 번이다.

실무에서 진짜 중요한 것

다음 실습에서 할 것

같은 3월 주문으로 copy-on-write 표와 merge-on-read 표를 만들고, 같은 DELETE 를 돌려 한쪽은 데이터 파일을 다시 쓰고 다른 쪽은 위치 삭제 파일만 더하는 것을 커밋 요약으로 확인한다. 변경 묶음으로 두 표에 똑같이 MERGE 한 뒤, 위치 삭제 파일 하나를 pyarrow 로 직접 열어 어느 파일의 몇 번째 행을 가리키는지 보고, 두 MERGE 가 쓴 바이트를 견주고, DuckDB 와 pyiceberg 가 삭제를 반영해 읽는지 확인한다.