✅ 제목: Iceberg 기반 데이터 레이크하우스 아키텍처 도입기 2 - Apache Iceberg 활용하기
1. 새로운 아키텍처: Iceberg 기반 데이터 레이크하우스
Iceberg는 단순한 포맷 이상의 의미를 갖습니다. 기존 Hive 기반 테이블 포맷이 가진 구조적 한계, 메타데이터 일관성 문제, 파티션 경직성 등 다양한 문제를 해결하며 등장한 차세대 테이블 포맷입니다. 현재 여러 대규모 분석 시스템에서 도입되었고, 다양한 오픈소스 분산 처리 엔진들과의 호환성을 통해 분산 환경에서도 안정적인 트랜잭션과 고성능 쿼리를 보장합니다.
저희는 이러한 Iceberg의 구조적 특성과 요구사항 충족 가능성을 바탕으로, 새로운 데이터 아키텍처를 구상하게 되었습니다. 본격적인 아키텍처 설명에 앞서, Iceberg가 어떤 기술인지 간략히 소개하고자 합니다.
1-1. Apache Iceberg란?
Apache Iceberg는 Netflix에서 시작된 대규모 분석 데이터 테이블 포맷입니다. 기존 Hive Table Format의 한계를 극복하기 위해 개발되었으며, 현재는 Spark, Trino, Flink 등 주요 분산 처리 엔진과 호환됩니다.
Iceberg는 기존 Hadoop 기반 Data Warehouse에서 경험하던 파일 추적 어려움, 파티션 관리의 경직성, 쿼리 성능 저하 같은 문제를 해결하며, 데이터 레이크와 데이터 웨어하우스의 장점을 결합한 레이크하우스 아키텍처를 가능하게 해줍니다.
Iceberg의 주요 특징
-
스키마 진화 (Schema Evolution)
테이블 수준에서 컬럼 추가, 삭제, 이름 변경, 파티션 변경 등을 유연하게 반영할 수 있습니다. 이를 통해 구조 변경에 빠르게 대응할 수 있고, 분석 흐름 전반의 유지보수 부담을 줄일 수 있습니다. -
스냅샷 기반 버저닝 (Time Travel)
데이터 변경이 발생할 때마다 기존 데이터를 덮어쓰는 대신 새로운 스냅샷을 생성해 변경 이력을 관리합니다. 이를 통해 특정 시점의 데이터로의 롤백, 특정 시점 기준 분석, 변경 이력 추적이 가능합니다. -
유연한 파티셔닝 (Hidden Partitioning)
테이블 생성 후에도 파티셔닝 전략을 유연하게 변경 가능하며, 쿼리 성능을 손상시키지 않습니다. -
메타데이터 인덱싱을 통한 효율적인 쿼리 성능
데이터 파일마다 파티션 정보, 컬럼 통계, null 비율 등의 메타데이터를 유지하고 있어 쿼리 시 전체 파일을 스캔하지 않고 필요한 데이터만 선택적으로 조회할 수 있습니다. 이를 통해 대규모 데이터 환경에서도 분석 성능을 안정적으로 유지할 수 있습니다. -
ACID 트랜잭션 지원
Iceberg는 파일 기반 테이블이지만, 트랜잭션 단위의 쓰기·읽기 일관성을 보장합니다. 여러 쓰기 작업이 동시에 발생해도 충돌을 자동으로 감지하고, 먼저 커밋된 작업만 반영되며, 나머지 작업은 자동 재시도 처리됩니다. 덕분에 배치 기반 적재 중에도 읽기 작업이 안전하게 수행되고, 분산 환경에서도 안정적인 운영이 가능합니다. -
클라우드 네이티브 설계
S3, GCS, MinIO 등 오브젝트 스토리지와 최적화된 구조를 가집니다.
1-2. Iceberg 기반 아키텍처 구성
새로운 데이터 파이프라인은 Iceberg를 중심으로 아래와 같은 단계로 구성되어 있습니다:
1. 원시 데이터 수집: 대용량 압축 파일을 MinIO에 그대로 저장하고, 수집 시점의 메타데이터(압축 유형, 수집 위치 등)도 함께 저장합니다.
2. 파일 구조 분석 및 메타 추출: 압축을 해제한 후, 내부 폴더 구조, 전체 경로, 파일 확장자, 메타데이터, 파일 blob 및 텍스트 내용을 분석하여 Parquet 형식으로 Iceberg 테이블에 저장합니다.
3. Spark 기반 병렬 분석: 여러 워커가 동시에 개별 파일들을 읽고 분석 모듈을 수행하여 결과를 저장합니다.
4. SQL 기반 조회 및 인덱싱: 분석가나 애플리케이션은 Spark SQL을 통해 Iceberg 테이블을 직접 조회하며, 필요한 경우 파티션 또는 컬럼 기반 인덱스를 설정할 수 있습니다.
5. 확장 분석 및 파이프라인 재활용: 저장된 데이터를 기반으로 신규 분석 파이프라인을 자유롭게 구성하고, 전체 파일에 대한 후속 정제 및 제품화 분석이 가능하도록 설계합니다.
1-3. 사용 기술 및 구현 방법
압축 파일을 해제해 개별 파일 단위로 저장하고, 이를 Iceberg 기반으로 분석 가능한 구조로 전환하기 위해 여러 기술적 고려사항을 반영했습니다.
대용량의 비정형 데이터를 구조화하기 위해서는 단순 저장을 넘어 파일에 대한 메타데이터 인덱싱, 조건 기반 필터링, 그리고 빠른 조회 성능이 보장되어야 했습니다.
이러한 요구를 충족하기 위해 Iceberg의 구조적 특성과 기능들을 어떻게 활용했는지를 다음 항목들에서 구체적으로 설명드리겠습니다.
1-3-1. 테이블 스키마 설계
초기에는 메타데이터와 Blob 데이터를 별도 테이블로 분리하여 저장하는 방식을 설계했습니다. 중복된 파일이 서로 다른 소스에서 수집될 수 있기 때문에, 메타데이터 테이블에는 각각의 수집 이력을 남기고, Blob 테이블에는 sha256 해시값을 기준으로 중복 없이 저장하는 구조였습니다. 이때 두 테이블 간의 연결은 sha256 해시를 키로 하여 이루어졌습니다.
하지만 분석 및 조회 효율성, 관리 복잡성 등을 고려해 메타데이터와 Blob을 하나의 테이블에 함께 저장하는 구조로 변경하였습니다. 이 단일 테이블 구조에서는 각 개별 파일이 어떤 압축 파일에서 유래했는지를 추적할 수 있도록 원본 압축파일 정보와 전체 경로, 상위 디렉토리 정보 등을 함께 저장하고, 텍스트 파일의 경우 토큰화하여 Array 형태로 저장합니다. 이 구조는 쿼리 하나로 분석 대상 파일을 메타데이터와 함께 바로 조회할 수 있는 장점을 제공합니다.
한편, 텍스트 파일 내용을 저장하는 방식에 대해서도 다양한 실험을 진행했습니다. 초기에는 텍스트 본문 전체를 단일 String 필드로 저장하고, Bloom Filter를 적용하면 저장 공간이 절약되고 쿼리 속도도 빨라질 것이라 기대했습니다. 하지만 테스트 결과, 단어 단위 검색 시에는 오히려 쿼리 속도가 느려졌으며, 특히 특정 키워드 검색 시 filter pushdown이 제대로 작동하지 않는 문제가 있었습니다. 반면 Array 형태로 토큰화하여 저장한 방식은 단어 기반 검색에서 훨씬 빠른 성능을 보여주었으며, Iceberg의 column-level stats와 잘 결합되어 효율적인 필터링이 가능했습니다. 이로 인해 텍스트 저장은 Array 형태를 유지하는 것이 최적이라는 결론에 도달했습니다.
1-3-2. 파티셔닝 전략
Iceberg에서 파티셔닝은 테이블 내 데이터를 논리적으로 분할해 저장하는 방식으로, 효율적인 쿼리 수행과 성능 최적화에 핵심적인 역할을 합니다. 쿼리가 전체 데이터를 스캔하는 것이 아니라, 조건에 맞는 파티션만 선택적으로 조회할 수 있기 때문에, 특히 대규모 테이블에서 쿼리 속도와 비용에 큰 영향을 미칩니다. 이러한 이유로 파티셔닝 전략은 실제 자주 사용되는 쿼리 패턴과 데이터 분포를 기반으로 신중하게 설계했습니다. 파티션 키의 선택은 Iceberg 쿼리 성능을 좌우하는 주요 요소이므로, 실사용 환경에 맞춘 최적 구성을 도출하는 것이 중요했습니다.
초기에는 메타데이터 테이블은 수집 시점(timestamp_store)을 기준으로, Blob 테이블은 sha256 해시값을 기준으로 16개의 버킷으로 나누어 파티셔닝했습니다. 이후 단일 테이블로 전환하면서는 파일의 수집 시점(timestamp_store)을 기준으로만 파티셔닝을 구성하였습니다. sha256 해시는 더 이상 파티셔닝에 사용되지 않았으며, 쿼리 단에서 조건 필터링으로 활용하였습니다.
이러한 파티셔닝은 대규모 데이터셋에서도 조건 기반 조회 성능을 안정적으로 유지하고, 특정 파일만 빠르게 찾을 수 있도록 해줍니다.
또한 복합 파티셔닝(timestamp_store + sha256 bucket)을 사용할 경우 Iceberg 내부적으로 다음과 같은 문제점이 발생할 수 있음을 고려해 제외하였습니다:
- 파티션 수 증가로 인한 메타데이터 오버헤드:
timestamp_store가 일 단위이고, sha256이 16버킷으로 분기되면 하루에도 최대 16개의 파티션이 생성됩니다. 수천 개 이상의 파티션이 생기면 Iceberg의 snapshot commit 및 manifest 병합 시 성능 저하가 발생할 수 있습니다. - 작은 파일 증가 및 압축 효율 저하:
같은 날짜여도 sha256 값에 따라 데이터가 분산되기 때문에 각 파티션에 적은 양의 데이터만 쌓여 많은 수의 작은 Parquet 파일이 생깁니다. 이는 압축 효율을 떨어뜨리고, 이후 compaction 및 read 작업에서도 오버헤드를 유발합니다.
1-3-3. 데이터 적재 방식 최적화
압축 파일 내부의 개별 파일들을 Iceberg에 적재하는 방식은 MERGE INTO와 INSERT가 있습니다.
MERGE INTO는 주어진 조건에 따라 기존 데이터를 갱신(UPDATE)하거나 새로운 데이터를 삽입(INSERT)하는 SQL 명령어입니다. 일반적으로 적재 대상 데이터를 뷰(view)나 임시 테이블 형태로 준비한 후, 기준 키를 기준으로 대상 테이블과 조인하여 매칭되는 경우 UPDATE, 매칭되지 않으면 INSERT를 수행합니다. 데이터 웨어하우스 환경에서는 중복 방지와 조건부 갱신을 위해 자주 사용되지만, 비교 대상 전체 스캔, 조건 평가, 쓰기 연산이 동시에 수행되기 때문에 연산 비용이 크고 대규모 데이터셋에서는 성능 저하가 발생할 수 있습니다.
반면 INSERT는 단순히 데이터를 추가하는 연산으로, 빠른 처리 속도와 낮은 시스템 부하가 장점입니다.
| 항목 | MERGE INTO | INSERT |
|---|---|---|
| 연산 방식 | 조건에 따라 UPDATE 또는 INSERT | 단순 INSERT (append only) |
| 성능 | 전체 스캔 및 비교로 비용 높음 | 빠름, 경량 연산 |
| 중복 방지 | 가능 (조건 정의 필요) | 불가능 (중복 허용) |
| 구현 복잡도 | 높음 (뷰, 조건 필요) | 단순함 |
| 스키마 진화 | 지원 | 지원 |
초기 구조에서는 중복 제어를 위해 Iceberg의 MERGE INTO 구문을 활용했습니다. 하지만 이 방식은 비교 조건 평가와 파일 병합 연산을 포함하기 때문에 데이터가 증가할수록 성능 저하가 발생했습니다.
아래 명령어를 통해 파일 정렬 및 재작성 작업을 수행하면 동일한 양의 데이터를 적재할 때 10분이 걸리던 작업이 약 30초로 단축되는 효과를 얻을 수 있었습니다:
spark.sql(f"CALL <catalog_name>.system.rewrite_data_files(table => '{table_name}', options => map('rewrite-all', 'true'), strategy => 'sort', sort_order => 'sha256 ASC')")
하지만 이 역시 데이터가 쌓이면 다시 느려지는 현상이 반복되었으며, 주기적으로 rewrite 작업을 수행해야 한다는 점에서 실질적인 장기 해법이 되지 못했습니다.
이에 따라 적재 방식을 MERGE INTO에서 INSERT로 전환하고, 특정 기간의 배치 작업 전 기존 데이터를 삭제한 뒤 새로 INSERT 하는 방식으로 변경했습니다. 이 방식은 구조를 단순하게 유지하면서도, 원하는 데이터 적재 일관성을 확보할 수 있다는 장점이 있습니다.
이러한 구조 변경은 데이터 적재 속도를 안정화시키고, 분석 시나리오에서의 쿼리 활용도 또한 높이는 데에 기여하였습니다.
2. 운영 중에는 어떤 이슈가 있었고, 어떻게 해결했나?
2-1. 주요 에러 사례
Iceberg 기반 분석 파이프라인을 구축하고 운영하면서, 대규모 데이터를 다루는 과정에서 예상치 못한 몇 가지 이슈가 발생했습니다. 특히 대량의 메타데이터를 병렬로 처리하는 작업 중 Spark executor에서 OOM(Out Of Memory) 또는 task 직렬화 크기 초과와 같은 문제가 반복적으로 나타났습니다.
예를 들어, 아래와 같은 오류 메시지가 발생했습니다:
Error during batch 5 metadata insertion: Job aborted due to stage failure:
Serialized task 81:0 was 1316422320 bytes, which exceeds max allowed: spark.rpc.message.maxSize (536870912 bytes).
Consider increasing spark.rpc.message.maxSize or using broadcast variables for large values.
java.lang.OutOfMemoryError
The executor with id 1 exited with exit code 52(JVM OOM)
이는 단일 Spark task가 처리하는 데이터량이 너무 커져 직렬화 크기 제한(512MB)을 초과했거나, executor가 메모리 부족으로 종료된 사례였습니다.
2-2. 대응 방안
이 문제를 해결하기 위해 다음과 같은 전략들을 도입했습니다:
Batch 크기 축소
기존 5,000건 단위로 처리하던 메타데이터 batch를 1,000건으로 줄여, task 단위의 데이터 볼륨을 제한했습니다.
배치 리스트 초기화 방식 변경
기존에 .clear()로 재사용하던 리스트를 new ArrayList<>()로 재생성해 GC 부담을 줄였습니다.
KryoSerializer 도입
Spark 기본 직렬화기(JavaSerializer) 대신 Kryo를 사용하여 메모리 사용량을 줄이고 직렬화 속도를 개선했습니다.
Task 수 조절을 위한 Repartition
데이터셋을 repartition(56)하여 Spark executor 수(2 core × 7 instance)에 맞춰 병렬 처리를 분산시켰습니다.
기본 파티션 수(8)로는 병목이 발생했기 때문에, 2×7×4 = 56을 기준으로 설정했습니다.
spark.rpc.message.maxSize 증가
Spark 설정에서 spark.rpc.message.maxSize를 최대값인 2047MB로 상향 조정하여, 직렬화 가능한 payload 크기를 늘렸습니다.
Broadcast 방식 예외 처리 도입
Dataset 적재 실패 시, 데이터를 SparkContext의 broadcast 변수로 전달하여 executor 간 네트워크 전송을 최소화했습니다. 단, broadcast 데이터가 50MB를 넘는 경우는 제외했습니다.
Broadcast<List<IcebergMetadata>> broadcastBatch =
sparkSession.sparkContext().broadcast(batch, ClassTag$.MODULE$.apply(List.class));
dataset = sparkSession.createDataset(
broadcastBatch.value(),
Encoders.bean(IcebergMetadata.class)
).toDF();
2-3. 기타 관련 설정
위 문제들과 직접 연관되어 있거나, 유사한 환경에서 고려하면 좋은 Spark 설정들은 다음과 같습니다. 대부분 기본값을 그대로 사용했지만, 유사한 문제를 겪는 사용자에게 도움이 될 수 있어 함께 소개합니다:
- spark.memory.fraction = 0.6
전체 JVM heap 중 60%를 execution/storage 용도로 사용 - spark.memory.storageFraction = 0.5
위 60% 중 절반(=전체 heap의 30%)을 storage용으로 할당 - spark.memory.offHeap.enabled = true
JVM 외부 메모리(off-heap)를 사용하도록 설정 - spark.memory.offHeap.size = 8g
off-heap 메모리 용량을 8GB로 설정 - spark.shuffle.spill = true
메모리 부족 시 중간 데이터를 디스크로 spill - spark.shuffle.spill.compress = true
spill되는 데이터를 압축하여 I/O 효율 개선 - spark.executor.memory = 32g
executor heap 메모리 - spark.executor.memoryOverhead = 8g
JVM 외 메모리 영역으로, off-heap 및 shuffle 처리에 사용됨
2-4. 결과
이러한 조치들을 통해 대규모 병렬 작업 중 발생하던 메모리 이슈를 안정적으로 제어할 수 있었고, 데이터 적재 파이프라인의 실패율도 현저히 감소했습니다. 또한 적재 성능의 일관성도 확보되어, 배치 파이프라인의 신뢰성이 크게 향상되었습니다.
3. Iceberg 도입 이후, 어떤 효과를 얻었나?
Iceberg 기반 아키텍처를 도입함으로써 SQL 기반 쿼리를 통해 필요한 데이터를 빠르게 선별하고 분석할 수 있는 구조가 마련되었습니다. 사용자는 특정 경로나 파일 형식, 메타데이터 기준 등으로 데이터를 필터링하고, 해당 조건에 맞는 파일을 직접 조회할 수 있게 되어 분석 효율이 크게 향상되었습니다. 특히 분석 대상이 되는 개별 파일과 관련 메타데이터가 구조적으로 정리되어 있기 때문에, 복잡한 디렉토리 탐색 없이 쿼리만으로 원하는 데이터를 바로 가져올 수 있게 되었습니다.
또한 기존 데이터 레이크의 장점인 저장 비용 절감과 원본 데이터 보존이라는 특성도 유지되었습니다. 원본 압축 파일은 MinIO에 보존되어 있어 Iceberg 테이블 구축 중 문제가 발생하거나 처리 로직에 오류가 생기더라도, 언제든지 원본을 통해 데이터를 다시 추출할 수 있는 복원성과 안정성을 확보할 수 있었습니다.
분석 과정에서 추출된 데이터는 Iceberg 테이블로 저장되므로, 다른 파이프라인이나 분석 워크플로우에서 재사용하기 쉬운 형태가 되었고, 이는 데이터 재사용성과 운영 효율성을 크게 높였습니다. 예를 들어, 한 번 파싱된 파일은 재처리 없이 다양한 downstream 작업에 활용할 수 있고, 동일한 테이블을 기반으로 여러 분석 파이프라인이 병렬적으로 작동할 수 있습니다.
Iceberg의 메타데이터 기반 구조 덕분에 분석가, 기획자 등 다양한 역할의 사용자들이 필요한 데이터에 더 직관적으로 접근할 수 있게 되었으며, SQL 인터페이스를 통해 비개발자도 유의미한 분석을 수행할 수 있는 기반을 제공하였습니다. 기존에는 개발자의 손을 빌려야 했던 특정 데이터 추출이나 조건 기반 필터링 작업을 self-service 형태로 전환함으로써, 타팀과의 협업 효율도 크게 향상되었습니다.
이러한 구조적 유연성과 성능 개선은 새로운 제품 기능 개발이나 분석 파이프라인을 빠르게 설계하고 확장하는 데에도 매우 효과적이었습니다. 과거에는 새로운 분석 요구가 생길 때마다 데이터 구조를 재설계하거나 파이프라인을 별도로 만들어야 했지만, 이제는 Iceberg 테이블에 쌓인 구조화된 데이터를 재활용하여 빠르게 새로운 분석 환경을 구성할 수 있게 되었습니다.
4. 이번 경험이 남긴 인사이트와 앞으로의 계획은?
이번 Iceberg 기반 구조 설계를 통해, 대용량 비정형 데이터를 분산 환경에서도 효율적으로 관리하고 분석 가능한 형태로 구성하는 방법에 대한 실질적인 인사이트를 얻을 수 있었습니다. 특히, 데이터의 원시성과 구조화 간 균형을 유지하면서도, 확장성과 쿼리 성능을 함께 확보하는 것이 중요하다는 점을 체감했습니다.
- 데이터 구조와 처리 흐름을 분리함으로써, 파이프라인의 유지보수성과 확장 가능성을 높일 수 있었으며
- 압축 파일 내 개별 파일의 메타데이터 인덱싱, 스냅샷 기반 버저닝, 파티셔닝 전략의 중요성 등 실질적인 성능에 직결되는 요소들을 튜닝하며 Iceberg의 강점을 확인할 수 있었습니다.
향후에는 다음과 같은 방향으로 확장할 계획입니다:
- 성능 최적화 반복: 파티셔닝 재설계, 정기적인 compact 작업, 스냅샷 정리 등 운영 효율 개선을 위한 주기적 리팩토링
- Airflow 기반 파이프라인 정교화: 현재 구성된 작업을 더욱 모듈 단위로 쪼개고, DAG의 유연성과 재사용성을 강화하여 복잡한 워크플로우도 안정적으로 운영할 수 있는 기반 확보
- 모듈 단위 분석 태스크 분리: 파싱, 전처리, 인덱싱 등 세부 분석 단계를 개별 태스크로 분리하고 관리함으로써, 장애 격리와 재실행 편의성을 확보하고 향후 확장성에 유리한 구조로 개선
- Iceberg 기반 제2의 파이프라인 설계: 위 테이블을 기반으로 각각의 분석 모듈을 붙여 후속 파이프라인을 설계하는 등, Iceberg의 테이블 추상화를 적극 활용한 유연한 파이프라인 구성 전략을 도입할 예정입니다.
Iceberg는 단순한 저장 포맷을 넘어, 분석 전략과 데이터 운영 방식을 함께 설계할 수 있는 강력한 기반이 됩니다. 이번 설계를 계기로, 구조적 유연성과 기술적 확장성을 동시에 확보하는 아키텍처 구축의 중요성을 다시금 확인할 수 있었습니다. 앞으로도 다양한 실전 경험을 바탕으로, 더 정교하고 견고한 분석 인프라를 만들어나갈 계획입니다.
<Iceberg 기반 데이터 레이크하우스 아키텍처 도입기 1편> 보러가기
🧑💻 칼럼 작성자: S2W KE팀
👉 AI 기술 문의하기: https://s2w.inc/ko/contact
*S2W의 생성형 AI 플랫폼 SAIP에 대해 더 알고 싶다면, 아래에서 자세한 내용을 확인해 주세요.