1820 words
9 minutes
[Iceberg]Open Table format 개요
01. Overview
- 오픈 테이블 포맷(Open Table Format) 동작 방식 간단히 정리
- S3, GCS와 같은 저렴한 객체 스토리지 위에서 어떻게 RDBMS 수준의 데이터 변경(UPDATE/DELETE)과 ACID 트랜잭션이 가능한지
02. 객체 스토리지(S3)의 불변성 문제
- 전통적인 RDBMS는 블록 스토리지 기반이므로 특정 행(Row)만 찾아가서 덮어쓰는(UPDATE) 작업이 자연스러움.
- S3의 한계 (Immutability): 객체 스토리지는 태생적으로 ‘불변(Immutable)’
- 역설적 상황: 1GB 크기의 Parquet 파일에 저장된 수백만 명의 고객 정보 중, 단 1명의 주소가 변경되었다고 가정. S3에서는 그 한 줄만 수정할 수 없음. 1GB 파일을 통째로 읽어 주소를 수정한 뒤, 새로운 1GB 파일로 전체를 다시 써야 함.
- 데이터가 수백 TB 쌓이는 환경에서 다수의 사용자가 동시에 데이터를 수정하거나 삭제하면, 데이터 정합성이 깨지고 파이프라인이 붕괴
03. Metadat 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, 스키마 진화 기능을 제공하는 메타데이터 관리 규격 (예: Iceberg, Delta Lake).
- ACID 트랜잭션 : 원자성, 일관성, 격리성, 지속성을 보장하여 다수 사용자의 동시 데이터 접근 시 데이터 무결성을 유지하는 데이터베이스 속성.
- Time Travel : 메타데이터 스냅샷 기록을 활용하여, 과거의 특정 시점(Timestamp) 기준으로 테이블 상태를 조회하거나 복구할 수 있는 기능.
- Copy-on-Write (CoW) : 데이터 수정 시 원본 파일을 복사하여 변경 사항을 적용한 새로운 파일을 생성하는 물리적 업데이트 전략.
- Merge-on-Read (MoR) : 데이터 수정 시 변경분만 별도 로그 파일에 기록하고, 읽기 시점에 원본과 병합하여 보여주는 물리적 업데이트 전략.
- 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/spark/de-opentable-opentableformat/