[Iceberg] Apache Iceberg 등장

Hive Table Format과 비교하여 Iceberg 의 특징(Snapshot, Hidden Partition) / 스냅샷 정리방법 / Tag

Posted by Wonyong Jang on October 01, 2024 · 13 mins read

1. Apache Iceberg의 등장

기존의 Apache Hive에서 대용량 데이터를 다룰 때 규모, 성능, 사용성에 대한 문제가 존재했고 이를 해결하기 위해 등장하게 되었다.
Netflix에서 개발되었으며 페타바이트 규모의 테이블을 위해 설계된 개방형 테이블 형식으로 설계 되어 모든 파일 포맷을 관리 구성 및 추적하는 방법을 결정하여 snapshot 방식으로 파일을 관리한다.

간단히 요약하자면 Iceberg는 오픈 소스 테이블 포맷을 의미하며 구조상 대용량 데이터를 쉽게 다룰 수 있는 이점이 있는데, Iceberg의 구조와 특징에 대해 자세히 알아보자.

Iceberg는 Hadoop 및 Apache Spark와 같은 분산 처리 시스템에서 사용되며, 대규모 데이터셋에 대한 효율적인 저장, 버전관리, 스키마 변화 관리 및 쿼리 처리를 제공한다.

그 외에도 Uber에서 개발한 Hudi, Databricks에서 지원하는 delta lake 가 존재하며, 이 글에서는 Iceberg에 대해서 다룰 예정이다.


2. Iceberg 구조

Iceberg는 크게 data layer, metadata layer, Iceberg catalog로 계층적인 구조로 이루어져 있다.

스크린샷 2024-10-01 오후 12 04 25

2-1) Iceberg catalog

catalog는 특정 데이터 소스에 접근을 가능하게 만들어주는 설정으로, Iceberg catalog는 현시점의 테이블 metadata를 찾을 수 있게 도와 준다.
그리고 쿼리가 실행되면 쿼리가 찾는 metadata file을 찾기 위해 사용된다.

2-2) metadata layer

metadata file

테이블 형상은 metadata file로 관리되며, 테이블 내용에 변경이 생기면 새로운 metadata file이 만들어지고 기존의 것을 대체 한다.

metadata file은 스키마, 파티션, 스냅샷에 대한 정보를 가지고 있다.

참고로, Hive는 MetaStore에서 메타데이터를 관리하여 RDB 스토리지 부하 문제가 발생할 수 있다.

즉, Iceberg는 메타정보 및 데이터를 파일로 관리하기 때문에 hive 테이블 형식의 문제점을 해결한 키 포인트이다.

Iceberg는 스냅샷 기능을 통해 특정 시점의 테이블 형상을 파일로 기록해주는데, 이는 특정 시점에 대한 rollback이 가능하게 해준다.

manifest list

스냅샷(특정 시점의 테이블 형상을 기록한 파일)은 하나의 manifest list를 참조하며, manifest list는 하나 이상의 manifest file에 대한 메타 정보를 저장하고 있다.

쉽게 생각하면, manifest list를 통해 스냅샷과 연관된 manifest file 위치를 찾아내는 것이다.

-- 테이블 스냅샷 조회(시점별 스냅샷에 대한 manifest list 확인 가능)
-- Spark SQL 에서 조회
SELECT * FROM mydb.iceberg_table.snapshots; 

-- Trino(Presto) 에서 아래와 같이 확인 가능 
SELECT * FROM "mydb"."iceberg_table$snapshots";
-- Trino 에서 조회 가능
-- file 하나 하나 담긴 row count, file size, 컬럼별 size, 특정 컬럼의 null row count 까지 파악할 수 있다.
SELECT * FROM "mydb"."iceberg_table$files";

위와 같은 쿼리로 스냅샷 히스토리 조회가 가능하며, 이를 통해 해당 테이블의 트랜잭션이 언제 발생하였는지, 얼마나 많은 파일의 업데이트가 이뤄졌는지 쉽게 파악할 수 있다.

manifest file

결국, 스냅샷은 하나 이상의 manifest file로 이루어지게 된다.

manifest file은 data file에 대한 모든 정보(data file 위치, 파티션 정보)와 통계 정보(null, nan 갯수)를 가지고 있다.

2-3) data layer

실제 데이터 파일을 저장하는 곳으로, 테이블에 정의된 파일 포맷(orc, parquet)형식으로 데이터 파일을 저장해 준다.
manifest file의 메타 정보를 이용하여 필요한 데이터 파일에 접근할 수 있게 된다.

정리해보면 Iceberg는 metadata -> manifest 를 거쳐서 원하는 데이터를 쉽게 찾을 수 있다.

select 쿼리가 발생하면 아래와 같은 프로세스로 데이터를 조회한다.

  • catalog에 접근하여 현재 metadata pointer를 체크한다.
  • pointer가 가르키는 metadata file을 읽는다. (스키마, 파티션, 스냅샷 정보)
  • manifest list의 파티션 정보로 필터링 후 필터링된 manifest를 거쳐서 데이터 파일을 가져온다.

3. Hive Table의 문제점

Hive는 데이터를 관리할 때 MetaStore(RDB) + 데이터(HDFS 안에 있는 실제 데이터 파일)로 나뉘어서 관리 된다.
DB에서 파일이 어디에 있는지 어떤 데이터를 추출해야 하는지 스키마나 파티션 정보들을 관리하고 실제 데이터는 HDFS나 S3 등에 적재 되어 있다.

Hive Table을 사용할 때 아래와 같은 문제점이 발생할 수 있다.
자세한 내용은 Line Data Platform 영상링크 확인해보자.

ACID 완벽하게 지원되지 않는 문제

Hive Table은 기본적으로 HDFS를 사용하므로 update를 지원하지 않기 때문에 트랜잭션 처리에 문제가 있다.

트랜잭션이 일부 지원하지만 완벽하게 지원되지 않는다.

병목현상

Hive는 MetaStore라는 메타데이터 저장소를 RDB를 활용하여 구축하고 실제 데이터는 HDFS 또는 s3에 저장하고 관리하기 때문에 데이터가 쌓일 수록 RDB의 성능 문제가 발생할 수 있다.

스키마 확장성 미지원

메타데이터를 직접 관리하지 않기 때문에 기본적으로 스키마를 확장할 수 없다.
파티션이나 스키마를 다시 지정하고 싶다면 테이블을 지우고 다시 생성해야 한다.

물론 파일 포맷에 따라 다르며, orc 또는 parquet 의 경우 컬럼 추가는 스키마 제거 없이 추가 가능하다.


4. Iceberg의 장점 및 Hive와 비교

4-1) 트랜잭션 지원

Iceberg는 ACID(Atomicity, Consistency, Isolation, Durability) 트랜잭션을 지원한다.

기존에 Hive는 ORC 파일 포맷을 이용할 때만 ACID 트랜잭션을 지원했지만 Iceberg에서는 분산환경에서도 ACID 트랜잭션을 지원하며 데이터 일관성과 무결성을 보장하여 여러 사용자가 동시에 데이터를 안전하게 수정할 수 있도록 한다.

4-2) Hidden Partition

파티션은 해당 데이터에만 접근하여 쿼리 속도를 향상시키게 만들어준다.

Hive의 경우 파티션을 컬럼처럼 분명히 명시해줘야 했다.
즉, Hive에서는 매번 새로운 데이터가 추가될 때마다 파티션 디렉토리를 명시적으로 관리 해야 하며, 조회 할때 또한 파티션 키를 잘못 사용했을 경우 성능 저하가 발생할 수 있다.

-- event_date를 파티션 열로 지정한 경우
SELECT *
FROM events
WHERE event_date = '2024-10-01';

Iceberg에서는 사용자가 직접 파티션 열을 지정하지 않아도 Iceberg가 내부적으로 파티션을 생성하고 관리한다.
이는 Iceberg의 Hidden partition 기능 덕분이며, 데이터 타입에 따라 자동으로 적절한 파티셔닝을 수행하며, 쿼리 시 사용자는 이를 인식할 필요가 없다.

4-3) Time Travel & Rollback

Iceberg는 데이터의 각 스냅샷을 관리한다. 그렇기 때문에 과거 시점의 데이터를 조회할 수도 있고 데이터를 롤백할 수 있도록 제공한다.

Time Travel은 Iceberg가 스냅샷을 관리하기 때문에 특정 시점의 데이터 상태를 조회할 수 있도록 제공하는 기능이다.
이를 통해 데이터 변경 이력을 추적하고 분석할 수 있다.

아래는 스냅샷을 조회하는 예시이다.

-- catalog 이름 검색
SHOW catalogs;

-- 스냅샷 모두 조회 
SELECT * FROM my_catalog.my_database.my_table.snapshots

-- 특정 스냅샷 시점의 데이터 조회
SELECT * FROM mydb.iceberg_table.snapshot_at('2023-09-01T00:00:00');
-- Trino 에서 조회
-- 1234 는 snapshot id

SELECT * FROM mydb.iceberg_table 
for version as of 1234;

롤백 방식은 여러방식을 제공하며 아래 예시를 살펴보자.

-- RESTORE_TABLE
-- 특정 스냅샷으로 테이블의 상태를 복원하되, 기존 스냅샷 히스토리를 유지한다.  
CALL my_catalog.system.RESTORE_TABLE('my_table', 'snapshot_id');

-- ROLLBACK_TO_SNAPSHOT
-- 특정 스냅샷으로 테이블의 상태를 롤백한다.  
-- 현재 테이블의 상태를 완전히 이전 스냅샷으로 되돌린다.   
CALL my_catalog.system.my_table.ROLLBACK_TO_SNAPSHOT('snapshot_id');

-- ROLLBACK_TO_TIMESTAMP   
-- 명시적으로 스냅샷 ID를 지정할 필요 없이 특정 타임스탬프에 해당하는 상태로 테이블을 롤백한다.   
-- 이때 가장 최근의 스냅샷을 참조하여 해당 시점의 상태로 복원한다.   
CALL my_catalog.system.my_table.ROLLBACK_TO_TIMESTAMP(TIMESTAMP 'YYYY-MM-DD HH:MM:SS');

-- SET_CURRENT_SNAPSHOT   
-- 테이블의 현재 스냅샷을 변경하여 특정 스냅샷을 현재 스냅샷으로 설정한다.  
-- 이전 스냅샷으로 롤백하는 대신, 특정 스냅샷을 현재로 설정할 수 있다.   
CALL my_catalog.system.my_table.SET_CURRENT_SNAPSHOT('snapshot_id');

-- CHERRYPICK_SNAPSHOT
-- 특정 스냅샷의 변경 사항만 선택적으로 적용하여 현재 테이블에 반영한다.   
-- 전체 롤백과는 달리 선택적인 변경을 수행 
CALL my_catalog.system.my_table.CHERRYPICK_SNAPSHOT('snapshot_id');

4-4) Upsert 와 Delete

스크린샷 2024-10-01 오후 10 12 45

upsert 는 아래와 같이 merge 쿼리를 제공하며 전통적인 sql에서 사용하는 구문과 유사하다.

-- 대상 테이블(target): 병합 작업이 수행될 테이블
-- 소스 테이블(source): 병합할 데이터를 제공할 테이블   
-- 조건(on): 대상 테이블과 소스 테이블 간의 매칭 조건을 정의   
-- 일치하는 경우(when matched): 매칭 조건이 참일 때 수행할 작업을 정의   
-- 일치하지 않는 경우(when not matched): 매칭 조건이 거짓일 때 수행할 작업을 정의   

MERGE INTO my_database.iceberg_table AS target
USING (
        SELECT 1 AS productid, 101 AS categoryid
      ) AS source   
ON target.productid = source.productid
WHEN MATCHED THEN update set * 
WHEN NOT MATCHED THEN insert *   

링크 에서 더 많은 쿼리 예시를 살펴보자.


5. Iceberg Maintenance

Apache Iceberg를 사용할 때, 데이터 변경이 발생할 때마다 스냅샷이 생성되어 s3와 같은 스토리지에 저장된다.
이 스냅샷들이 누적되면서 저장 공간을 많이 차지할 수 있기 때문에, 실무에서는 주기적으로 스냅샷을 정리하는 것이 중요하다.

이를 위해 Iceberg는 스냅샷과 데이터 파일 관리를 위한 몇 가지 방법을 제공한다.

5-1) Expire Snapshots(스냅샷 만료)

Iceberg는 오래된 스냅샷을 삭제하는 메커니즘을 제공하며, 일정 기간 이전의 스냅샷을 만료시킴으로써 스토리지 비용을 줄일 수 있다.

아래와 같이 특정 날짜 이전의 모든 스냅샷을 삭제하여 테이블의 메타데이터를 정리하고 스토리지 사용량을 줄이는데 사용된다.

CALL <catalog_name>.<namespace>.expire_snapshots('<table_name>', TIMESTAMP '<expiration_time>')

- catalog_name: Iceberg의 카탈로그의 이름   
- namespace: Iceberg 테이블이 포함된 데이터베이스 이름  
- table_name: 스냅샷을 만료시킬 Iceberg 테이블의 이름   
- expiration_time: 삭제할 스냅샷의 기준 날짜이며, 이 날짜 이전에 생성된 모든 스냅샷이 삭제된다.   

아래 예시는 2023년 1월 1일 이전에 생성된 모든 스냅샷을 삭제한다.

CALL my_catalog.my_database.expire_snapshots('my_table', TIMESTAMP '2023-01-01 00:00:00')

추가적으로 스냅샷 관리 시, 만료 기간이 지났음에도 가장 최근 스냅샷 만큼 유지하는 옵션이 있으며, 아래 예시를 보자.

CALL my_catalog.my_database.expire_snapshots('my_table', TIMESTAMP '2023-01-01 00:00:00', retain_last=3)

- retain_last: 2023년 1월 1일 이전의 생성된 모든 스냅샷을 삭제하되, 가장 최근 3개의 스냅샷은 삭제하지 않고 유지   

5-2) Remove Orphan Files(고아 파일 제거)

Iceberg의 메타데이터와 연결되지 않은 고아 파일(Orphan Files)이 있을 수 있다. 이러한 파일을 정리하지 않으면 스토리지가 낭비될 수 있기 때문에, 주기적으로 고아 파일을 삭제하는 것이 좋다.

스냅샷을 만료시킨 후, 해당 스냅샷이 참조하던 파일들이 남아 있을 수 있다.

CALL <catalog_name>.<namespace>.remove_orphan_files('<table_name>', TIMESTAMP '<expiration_time>')

5-3) Table Compaction(테이블 압축)

데이터 파일을 정리하고, 작은 파일들을 합치는 작업(Compaction)을 주기적으로 수행하여 읽기 성능을 최적화하고, 스토리지 효율성을 높일 수 있다.

CALL catalog.schema.rewrite_data_files('table_name');

https://wikidocs.net/228567
https://iomete.com/resources/reference/iceberg-tables/maintenance
https://magpienote.tistory.com/255
https://iceberg.apache.org/docs/latest/spark-queries/
https://developers-haven.tistory.com/50