1990 words
10 minutes
[Iceberg]Open Table format 개요
01. Open Table Format이 해결하는 문제
01. Overview
- 오픈 테이블 포맷(Open Table Format)의 정의
- S3, GCS와 같은 객체 스토리지 위에서 어떻게 RDBMS 수준의 데이터 변경(UPDATE/DELETE)과 ACID 트랜잭션이 가능한지
Open Table Format은 파일시스템이나 객체 스토리지에 저장된 불변 데이터 파일 집합을 스냅샷 기반 메타데이터로 버전 관리하고, 원자적 커밋 규칙을 통해 여러 처리 엔진이 이를 하나의 트랜잭션 테이블로 일관되게 읽고 갱신할 수 있도록 하는 개방형 테이블 추상화 계층
02. 객체 스토리지(S3)의 불변성 문제
- 전통적인 RDBMS는 블록 스토리지 기반이므로 특정 행(Row)만 찾아가서 덮어쓰는(UPDATE) 작업이 자연스러움.
- S3의 한계 (Immutability): 객체 스토리지는 태생적으로 ‘불변(Immutable)’
- 역설적 상황: 1GB 크기의 Parquet 파일에 저장된 수백만 명의 고객 정보 중, 단 1명의 주소가 변경되었다고 가정. S3에서는 그 한 줄만 수정할 수 없음. 1GB 파일을 통째로 읽어 주소를 수정한 뒤, 새로운 1GB 파일로 전체를 다시 써야 함.
- 데이터가 수백 TB 쌓이는 환경에서 다수의 사용자가 동시에 데이터를 수정하거나 삭제하면, 데이터 정합성이 깨지고 파이프라인이 붕괴
03. Metadata Layer
- Apache Iceberg, Delta Lake, Apache Hudi는 이 불변성의 한계를 극복하기 위해 스토리지와 컴퓨팅 엔진 사이에 **‘메타데이터 레이어(Metadata Layer)‘**를 강제 삽입
- 핵심 아이디어는 “데이터 파일 자체를 수정하는 것이 아니라, 어떤 파일이 유효한지 가리키는 장부(메타데이터)를 건드리는 것”
데이터 변경 및 조회 프로세스
- 신규 데이터 쓰기: Spark가 S3에 변경된 데이터를 담은 ‘새로운 Parquet 파일’을 추가. (기존 원본 파일은 건드리지 않음)
- 트랜잭션 로그 기록: 쓰기 작업이 완료되면, 엔진이 새로운 메타데이터 파일(JSON/Avro 형태)을 생성.
- 포인터 전환 (Commit): 메타데이터에 *“구버전 파일 A는 무시하고, 신버전 파일 B를 유효한 테이블 데이터로 취급하라”*는 스냅샷(Snapshot) 정보를 기록.
- 데이터 조회 (Read): 사용자가 쿼리를 실행하면 엔진은 무조건 최신 메타데이터부터 읽음. 메타데이터가 지시하는 ‘현재 유효한 파일 목록’만 S3에서 스캔하므로 완벽한 트랜잭션 격리(Isolation)가 성립.
Delta Lake 데이터 병합(Merge) 예제
from delta.tables import DeltaTable
# 메타데이터 레이어를 통해 S3 위에서도 RDBMS의 UPSERT(MERGE) 연산 지원deltaTable = DeltaTable.forPath(spark, "s3://silver-data/users")
deltaTable.alias("target").merge( source_df.alias("source"), "target.user_id = source.user_id").whenMatchedUpdate(set = { "email": "source.email" } ) \ .whenNotMatchedInsertAll() \ .execute()04. 데이터 변경 물리 전략: CoW vs MoR
- 오픈 테이블 포맷이 데이터를 업데이트할 때 내부적으로 사용하는 두 가지 핵심 전략.
- 데이터 쓰기/읽기 패턴에 따라 아키텍처 레벨에서 선택
Copy-on-Write (CoW)
- 방식: 업데이트 발생 시 해당 레코드가 포함된 데이터 파일 전체를 새로 읽어 수정한 뒤, 새로운 파일로 덮어씀.
- 적용 환경: 읽기(Read) 성능이 극도로 중요하고, 데이터 변경이 가끔 큰 배치(Batch) 형태로 일어날 때 적합.
- 단점: 쓰기 증폭(Write Amplification)이 커서 쓰기 비용이 매우 높음.
Merge-on-Read (MoR)
- 방식: 변경된 레코드만 작은 로그 파일(Delta/Changelog)에 빠르게 기록.
- 나중에 쿼리로 데이터를 읽을 때, 베이스 파일과 로그 파일을 실시간으로 병합(Merge)하여 반환.
- 적용 환경: 쓰기/변경이 매우 빈번한 스트리밍이나 CDC(Change Data Capture) 환경에 적합.
- 단점: 읽을 때마다 병합 연산이 발생하므로 조회(Read) 성능이 다소 떨어짐.
05. 시스템 유지보수와 트레이드오프
메타데이터 조작 방식은 필연적으로 덤프파일 생성 및 파티션 단편화라는 관리 비용을 발생시킴.
- Compaction (최적화): 잦은 업데이트로 인해 S3에 수 KB짜리 작은 파일이 수만 개 생성되면(Small File Problem), 메타데이터 스캔 자체가 병목이 됨. 주기적으로 작은 파일들을 큰 파일로 뭉쳐주는(Compaction) 작업이 필수적임.
- Vacuuming (가비지 컬렉션): 과거 스냅샷을 유지하면 Time Travel(과거 시점 쿼리)이 가능하지만, 스토리지 비용이 폭발함. 더 이상 참조하지 않는 오래된 데이터 파일과 메타데이터를 주기적으로 물리 삭제(Vacuum)해야 함.
Concept
- Open Table Format : 기존 데이터 파일을 직접 수정하지 않고 새 파일과 새 스냅샷을 생성한 뒤 현재 메타데이터 포인터를 원자적으로 전환함으로써, 스토리지의 파일 집합을 하나의 테이블로 관리하는 테이블 추상화 계층.
- ACID 트랜잭션 : 원자성, 일관성, 격리성, 지속성을 보장하여 다수 사용자의 동시 데이터 접근 시 데이터 무결성을 유지하는 데이터베이스 속성.
- Time Travel : 메타데이터 스냅샷 기록을 활용하여, 과거의 특정 시점(Timestamp) 기준으로 테이블 상태를 조회하거나 복구할 수 있는 기능.
- Copy-on-Write (CoW) : 데이터 수정 시 원본 파일을 복사하여 변경 사항을 적용한 새로운 파일을 생성하는 물리적 업데이트 전략. 일부 수정도 페이지 전체를 다시씀. Write Amplification과 관련있음.
- Merge-on-Read (MoR) : 데이터 수정 시 변경분만 별도 로그 파일에 기록하고, 읽기 시점에 원본과 병합하여 보여주는 물리적 업데이트 전략. 원본 parquet을 건드리지 않고 delete file에 대한 일종의 flag를 남김. Read Amplification과 관련.
- Compaction (컴팩션) : 쿼리 성능 저하를 막기 위해, 스토리지에 흩어진 수많은 작은 파일(Small Files)들을 읽기 좋은 크기의 큰 파일로 병합하는 최적화 과정.
- Z-Ordering (Z-오더 커브) : 다차원 데이터를 1차원으로 매핑하여 비슷한 데이터를 물리적으로 가깝게 배치, 쿼리 시 스캔해야 하는 파일 수를 획기적으로 줄여주는 데이터 클러스터링 기법.
- Data Catalog (Hive Metastore, Nessie) : 클러스터 외부에 존재하며, 여러 컴퓨팅 엔진(Spark, Trino 등)이 동일한 오픈 테이블 포맷 메타데이터를 찾을 수 있도록 돕는 중앙 저장소.
Key_Takeaways
- 객체 스토리지는 데이터를 덮어쓸 수 없는 불변성을 가지며, 이로 인해 대규모 데이터 업데이트 시 심각한 비효율과 정합성 문제가 발생
- Iceberg, Delta Lake 같은 오픈 테이블 포맷은 실제 데이터 파일 대신 메타데이터(장부)를 조작하는 방식으로 이 한계를 극복하고 트랜잭션을 구현한다.
- 데이터 변경 전략은 읽기 성능을 우선하는 Copy-on-Write(CoW)와 쓰기 속도를 우선하는 Merge-on-Read(MoR)로 나뉘며 파이프라인 특성에 맞춰 선택해야 함
- 이 아키텍처를 유지하려면, 수많은 자잘한 파일을 뭉치고(Compaction) 불필요한 과거 스냅샷을 지우는(Vacuuming) 지속적인 관리 작업이 수반되야 함
[Iceberg]Open Table format 개요
https://yjinheon.netlify.app/posts/02de/00-storage/otf-01/