Blog
trinoicebergparquetperformance

Trino + Iceberg 는 파티션 문제를 어떻게 해결하는가

Hive/Impala 에서 파티션 컬럼 누락 시 풀스캔이 발생하는 이유와, Trino + Iceberg 의 hidden partitioning·메타데이터 기반 프루닝이 이 문제를 구조적으로 제거하는 방법을 정리합니다.

Data Dynamics2026年5月30日25 min read
This post is not yet translated. The original Korean version is shown below.

Trino 에서 Parquet 포맷의 Iceberg 테이블을 만들고, 같은 데이터를 Impala 와 Hive 에서도 조회할 수 있게 구성하는 것은 흔한 아키텍처입니다. 그런데 Impala 나 Hive 에서 쿼리를 날릴 때 WHERE 절에 파티션 컬럼을 빠뜨리면 수 TB 풀스캔이 발생하고 클러스터가 멈추는 경험을 한 적 있으신가요? 이 글은 Hive/Impala 에서 이 문제가 왜 구조적으로 피할 수 없는지, 그리고 Trino + Iceberg 가 어떻게 이 문제를 근본적으로 제거하는지를 다룹니다.

1. 문제: 파티션 컬럼을 빠뜨리면 클러스터가 멈춘다

Hive 와 Impala 에서 파티션 테이블을 만드는 전형적인 패턴입니다.

CREATE TABLE events (
  event_id    BIGINT,
  user_id     BIGINT,
  event_type  STRING,
  event_time  TIMESTAMP,
  city        STRING,
  payload     STRING
)
PARTITIONED BY (dt STRING)
STORED AS PARQUET;

이 테이블에 1년치 데이터가 쌓여 있다고 가정합니다. 약 500GB, 365개 파티션.

-- 정상: 파티션 프루닝 동작, 하루치 약 1.4GB 만 스캔
SELECT * FROM events WHERE dt = '2026-04-12' AND user_id = 12345;
 
-- 문제: dt 파티션 컬럼 누락 → 365개 파티션 전체 스캔 (500GB)
SELECT * FROM events WHERE user_id = 12345;

두 번째 쿼리는 user_id 조건만 있고 dt 가 없습니다. Hive/Impala 는 어떤 파티션에 user_id = 12345 데이터가 있는지 알 방법이 없으므로, 모든 파티션의 모든 파일을 열어서 확인해야 합니다.

2. 왜 Hive/Impala 는 이 문제를 피할 수 없는가

디렉터리 기반 파티셔닝

Hive/Impala 의 파티션은 물리 디렉터리입니다.

/warehouse/events/dt=2026-04-01/
/warehouse/events/dt=2026-04-02/
/warehouse/events/dt=2026-04-03/
...
/warehouse/events/dt=2026-04-12/
  ├── 000000_0.parquet
  ├── 000001_0.parquet
  └── 000002_0.parquet

Hive Metastore(HMS)는 파티션 디렉터리 목록을 관리하지만, 각 파일 안에 어떤 데이터가 들어있는지(min/max 통계)는 모릅니다. 쿼리 플래너가 파티션을 줄이려면 WHERE 절에 파티션 컬럼(dt)이 명시적으로 있어야 합니다.

Strict Mode 로 막을 수는 있지만

Hive 는 hive.mapred.mode=strict, Impala 는 쿼리 옵션으로 파티션 컬럼이 없는 쿼리를 거부할 수 있습니다. 하지만 이것은 "실수를 차단" 하는 것이지, 파티션 컬럼 없이도 효율적으로 조회하는 것은 불가능합니다.

3. 파티션 개수 제한: Hive/Impala vs Trino+Iceberg

파티션을 잘게 나누면 쿼리 성능이 좋아지지만, Hive/Impala 에서는 파티션 수 자체가 병목이 됩니다.

Hive/Impala 의 파티션 제한

항목HiveImpala
파티션 컬럼 수제한 없음 (실무상 3~4개 권장)동일
최대 파티션 수HMS 성능 한계로 수만~수십만 개에서 심각한 저하Catalogd 메모리에 파티션 메타를 전부 올리므로 수십만 개 넘으면 OOM·갱신 지연
동적 파티션 생성 제한hive.exec.max.dynamic.partitions 기본값 1000MAX_DYNAMIC_PARTITIONS 쿼리 옵션으로 제한
플래닝 시 파티션 로드HMS 에 RPC → 파티션 수가 많으면 쿼리 플래닝만 수 분HDFS NameNode + Catalogd 동기화 비용 추가

파티션 컬럼을 2~3개 조합하면 카디널리티가 폭발합니다. 예를 들어 dt × region × device_type = 365 × 50 × 10 = 182,500 파티션/년. Hive/Impala 에서는 이런 설계를 할 수 없습니다.

Trino + Iceberg 의 파티션 제한

항목Trino + Iceberg
파티션 컬럼 수Iceberg spec 상 제한 없음
최대 파티션 수사실상 무제한. 파티션이 HMS 레코드가 아니라 manifest 내부 메타데이터
동적 파티션 생성 제한없음
플래닝 시 파티션 로드Manifest 파일만 읽으면 됨. HMS RPC 불필요

Iceberg 는 파티션 정보를 HMS 에 등록하지 않고 manifest 파일에 저장합니다. 따라서 파티션 수가 늘어나도 HMS 에 RPC 를 날리지 않으며, 100만 개 파티션이 있어도 manifest 프루닝으로 필요한 파일만 빠르게 필터링합니다.

비교 요약

항목Hive/ImpalaTrino + Iceberg
파티션 =HMS 레코드 + HDFS 디렉터리Manifest 내부 메타데이터
파티션 수 실무 한계수만~수십만 (이후 성능 저하)사실상 무제한
파티션 추가 비용HMS ALTER TABLE ADD PARTITION (RPC)데이터 쓰기 시 자동 반영 (manifest commit)
파티션 수 × 플래닝 시간선형 증가거의 일정
동적 파티션 생성 제한기본 1000개/쿼리없음

실무 시사점: Hive/Impala 에서는 시간 파티션을 일 단위가 한계이지만, Trino + Iceberg 에서는 hours(event_time) 파티션도 부담 없고, days(event_time), bucket(32, user_id) 같은 복합 파티션도 자유롭습니다.

4. Iceberg 가 다른 점: 메타데이터 기반 아키텍처

Iceberg 는 테이블 메타데이터를 3계층 구조로 관리합니다.

Metadata file (JSON)
  └── Snapshot
        └── Manifest list (Avro)
              └── Manifest file (Avro)
                    └── 개별 데이터 파일 정보
                          ├── 파일 경로
                          ├── 파티션 값
                          ├── row count
                          ├── 컬럼별 min / max
                          ├── null count
                          └── 파일 크기

핵심은 manifest file 입니다. 각 데이터 파일의 파티션 값과 컬럼별 min/max 통계가 manifest 안에 들어있으므로, 데이터 파일을 열지 않고도 "이 파일이 쿼리에 필요한지" 를 판단할 수 있습니다.

Hive/Impala 와의 근본적인 차이:

항목Hive/ImpalaIceberg
메타데이터 위치HMS (외부 RPC)Manifest 파일 (스토리지 직접 읽기)
파일 수준 통계없음 (파티션 단위만)min/max, null count, row count 등
파일 프루닝불가능Manifest 기반으로 가능

5. Hidden Partitioning — 사용자가 파티션을 몰라도 되는 구조

이것이 Iceberg 의 가장 큰 차별점입니다.

Hive 방식

-- 테이블 생성 시 파티션 컬럼을 스키마에 명시
CREATE TABLE events (...) PARTITIONED BY (dt STRING);
 
-- INSERT 시 파티션 값을 명시적으로 지정
INSERT INTO events PARTITION (dt = '2026-04-12') VALUES (...);
 
-- SELECT 시에도 파티션 컬럼을 WHERE 에 명시해야 프루닝
SELECT * FROM events WHERE dt = '2026-04-12';

사용자가 dt 라는 파티션 컬럼의 존재를 알고, INSERT/SELECT 모두에서 명시해야 합니다.

Iceberg 방식: Partition Transform

-- 파티션 컬럼이 스키마에 노출되지 않음
CREATE TABLE iceberg_catalog.db.events (
  event_id    BIGINT,
  user_id     BIGINT,
  event_type  VARCHAR,
  event_time  TIMESTAMP(6) WITH TIME ZONE,
  city        VARCHAR,
  payload     VARCHAR
)
WITH (
  format = 'PARQUET',
  partitioning = ARRAY['days(event_time)']
);

event_time 컬럼에서 자동으로 일 단위 파티션이 생성됩니다. 별도의 dt 파티션 컬럼이 스키마에 없습니다.

-- 사용자는 그냥 event_time 으로 조건을 걸면 됨
SELECT * FROM events
WHERE event_time >= TIMESTAMP '2026-04-12 00:00:00 UTC'
  AND event_time <  TIMESTAMP '2026-04-13 00:00:00 UTC';

Trino 가 days(event_time) partition transform 을 알고 있으므로, 자동으로 해당 일자의 파티션만 스캔합니다. 파티션 컬럼이 스키마에 없으니 빠뜨릴 일 자체가 없습니다.

지원되는 Partition Transform:

Transform설명예시
years(col)연 단위years(event_time)
months(col)월 단위months(event_time)
days(col)일 단위days(event_time)
hours(col)시간 단위hours(event_time)
bucket(N, col)해시 버킷 N개bucket(32, user_id)
truncate(W, col)문자열/숫자를 W 폭으로 잘라서 파티션truncate(10, city)

6. Trino + Iceberg 에서 파티션 컬럼이 꼭 필요한가?

결론: 필수는 아니다. 하지만 없으면 달라지는 것이 있다

Iceberg 테이블은 파티션 없이도 생성·조회가 가능합니다.

-- 파티션 없는 Iceberg 테이블
CREATE TABLE iceberg_catalog.db.events_no_partition (
  event_id    BIGINT,
  user_id     BIGINT,
  event_time  TIMESTAMP(6) WITH TIME ZONE,
  city        VARCHAR
)
WITH (format = 'PARQUET');

이 테이블에 WHERE event_time = TIMESTAMP '2026-04-12 10:00:00 UTC' 로 조회하면:

  1. Manifest 에 파티션 값이 없음 → 모든 manifest entry 를 확인
  2. 하지만 Parquet footer 의 column statistics(min/max)로 row group 단위 스킵은 가능
  3. 결과: 파일은 전부 열지만, row group 단위로 일부 스킵 → Hive 풀스캔보다는 낫지만, 파티션이 있을 때보다 훨씬 느림
구성파일 프루닝Row Group 프루닝전체 스캔 범위
파티션 없음불가Parquet min/max 로 일부 스킵거의 전체
파티션 있음 (hidden)Manifest 기반으로 대부분 스킵추가로 row group 스킵해당 파티션 파일만

파티션 컬럼이 아닌 컬럼을 WHERE 에 넣으면?

파티션은 days(event_time) 으로 설정했지만, 쿼리는 WHERE user_id = 12345 인 경우:

  1. Manifest 프루닝: user_id 는 파티션 transform 과 무관 → 파일을 줄일 수 없음 → 모든 데이터 파일이 후보
  2. Parquet min/max 프루닝: 각 파일의 user_id 컬럼 min/max 확인 → 범위 밖이면 스킵
  3. 실제 읽기: min/max 범위 안에 드는 파일만 열어서 row group 단위로 한 번 더 필터

결과: 파티션 프루닝 효과는 0, Parquet 통계에만 의존. 데이터가 정렬되어 있지 않으면 사실상 풀스캔에 가까움.

대응법:

-- 방법 1: user_id 에 대한 bucket 파티션 추가
ALTER TABLE events SET PROPERTIES
  partitioning = ARRAY['days(event_time)', 'bucket(64, user_id)'];
 
-- 방법 2: 정렬 키 설정으로 min/max 통계 효과 극대화
ALTER TABLE events SET PROPERTIES
  sorted_by = ARRAY['user_id'];
 
ALTER TABLE events EXECUTE optimize;

존재하지 않는 컬럼을 WHERE 에 넣으면?

SELECT * FROM events WHERE non_existent_column = 'value';
-- 결과: COLUMN_NOT_FOUND 에러로 즉시 쿼리 실패

Iceberg 스키마에 없는 컬럼이므로 플래닝 단계에서 거부됩니다. 이 동작은 Hive/Impala 도 동일합니다.

단, Schema Evolution 으로 나중에 추가된 컬럼의 경우, 추가 이전의 데이터 파일에는 해당 컬럼이 없으므로 NULL 로 처리됩니다. 쿼리 자체는 정상 실행됩니다.

7. Trino 의 파일 프루닝 메커니즘

Trino + Iceberg 는 3단계 깔때기로 불필요한 데이터를 걸러냅니다.

[전체 데이터 파일]


  ① Manifest 기반 프루닝
  (파티션 값 + 파일 수준 min/max)
       │  불필요한 파일 제거

  ② Parquet Row Group 프루닝
  (row group 별 column statistics)
       │  불필요한 row group 스킵

  ③ Dynamic Filtering
  (JOIN 의 build side 결과를 probe side 에 전파)
       │  추가로 파일/row group 스킵

  [실제로 읽는 데이터]

각 단계가 순차적으로 데이터를 줄이므로, 최종적으로 읽는 데이터량이 극적으로 감소합니다.

8. 실전 비교: 같은 데이터, 같은 쿼리, 다른 결과

시나리오 설정

  • 테이블: 1년치 이벤트 로그, 약 500GB
  • 파티션: 일 단위 (Hive: dt 컬럼, Iceberg: days(event_time))
  • Parquet 포맷

쿼리 1: 파티션 컬럼 명시

-- Hive/Impala
SELECT * FROM events WHERE dt = '2026-04-12' AND user_id = 12345;
 
-- Trino + Iceberg
SELECT * FROM events
WHERE event_time >= TIMESTAMP '2026-04-12 00:00:00 UTC'
  AND event_time <  TIMESTAMP '2026-04-13 00:00:00 UTC'
  AND user_id = 12345;
엔진스캔 범위비고
Hive/Impala~1.4GB (1일분)파티션 프루닝 정상 동작
Trino + Iceberg~1.4GB (1일분)Hidden partition 프루닝 동작

모두 빠릅니다.

쿼리 2: 파티션 컬럼 누락 — 시간 범위만

-- Hive/Impala: dt 를 안 쓰고 event_time 으로만 조건
SELECT * FROM events WHERE event_time >= '2026-04-12' AND event_time < '2026-04-13';
 
-- Trino + Iceberg: 동일한 조건
SELECT * FROM events
WHERE event_time >= TIMESTAMP '2026-04-12 00:00:00 UTC'
  AND event_time <  TIMESTAMP '2026-04-13 00:00:00 UTC';
엔진스캔 범위비고
Hive/Impala~500GB (전체)event_time 은 파티션 컬럼이 아니므로 풀스캔
Trino + Iceberg~1.4GB (1일분)days(event_time) transform 으로 자동 프루닝

350배 차이. Hive/Impala 에서는 풀스캔, Trino + Iceberg 에서는 하루치만 읽습니다.

쿼리 3: 파티션과 무관한 컬럼

SELECT * FROM events WHERE user_id = 12345;
엔진스캔 범위비고
Hive/Impala~500GB (전체)풀스캔
Trino + Iceberg (파티션: days(event_time) 만)~500GB (전체)user_id 는 파티션과 무관, Parquet min/max 에만 의존
Trino + Iceberg (파티션: days(event_time) + bucket(64, user_id))~7.8GB (1/64)bucket 파티션 프루닝

파티션 설계가 쿼리 패턴과 맞아야 효과가 있습니다.

쿼리 4: 파티션 없는 Iceberg 테이블

SELECT * FROM events_no_partition
WHERE event_time >= TIMESTAMP '2026-04-12 00:00:00 UTC'
  AND event_time <  TIMESTAMP '2026-04-13 00:00:00 UTC';
엔진스캔 범위비고
Trino + Iceberg (파티션 없음)거의 전체Parquet min/max 로 일부 row group 스킵, 파일 프루닝 불가
Trino + Iceberg (파티션 있음)~1.4GB파티션 프루닝으로 대부분 스킵

파티션 없는 Iceberg 도 Hive 풀스캔보다는 약간 낫지만, 파티션이 있을 때와는 10~50배 성능 차이가 납니다.

9. Iceberg 테이블 생성과 파티션 설정

기본 생성

CREATE TABLE iceberg_catalog.db.events (
  event_id    BIGINT,
  user_id     BIGINT,
  event_type  VARCHAR,
  event_time  TIMESTAMP(6) WITH TIME ZONE,
  city        VARCHAR,
  payload     VARCHAR
)
WITH (
  format = 'PARQUET',
  partitioning = ARRAY['days(event_time)']
);

복합 파티션

CREATE TABLE iceberg_catalog.db.events (
  event_id    BIGINT,
  user_id     BIGINT,
  event_type  VARCHAR,
  event_time  TIMESTAMP(6) WITH TIME ZONE,
  city        VARCHAR
)
WITH (
  format = 'PARQUET',
  partitioning = ARRAY['days(event_time)', 'bucket(32, user_id)']
);

Partition Evolution — 기존 데이터 재작성 없이 파티션 전략 변경

Iceberg 고유 기능입니다. Hive/Impala 에서는 불가능합니다.

-- 기존: 일 단위 파티션
-- 변경: 시간 단위 파티션으로 전환
ALTER TABLE events SET PROPERTIES
  partitioning = ARRAY['hours(event_time)'];

이후 새로 쓰이는 데이터만 시간 단위 파티션을 적용받고, 기존 데이터는 일 단위 파티션 그대로 유지됩니다. Iceberg 의 manifest 가 파티션 spec 버전을 추적하므로 혼재된 상태에서도 프루닝이 정상 동작합니다.

10. Trino + Iceberg 에서도 성능이 나빠지는 경우

Trino + Iceberg 가 만능은 아닙니다. 다음 상황에서는 성능이 크게 저하됩니다.

10.1 Small File Problem

Iceberg 는 파티션 수 제한이 없는 대신, INSERT 가 잦으면 파티션당 수백~수천 개의 작은 파일이 쌓입니다. 각 파일마다 manifest entry 가 존재하고, Parquet footer 를 파일마다 열어야 하므로 파일 수 × I/O 오버헤드가 발생합니다.

증상: 데이터 양은 적은데 쿼리 플래닝이 수십 초 걸림.

-- 작은 파일 병합 (컴팩션)
ALTER TABLE events EXECUTE optimize;
 
-- 또는 WHERE 조건으로 특정 파티션만 컴팩션
ALTER TABLE events EXECUTE optimize
  WHERE event_time >= TIMESTAMP '2026-04-01 00:00:00 UTC'
    AND event_time <  TIMESTAMP '2026-05-01 00:00:00 UTC';

10.2 Manifest 비대화

파일 수가 수십만 개를 넘으면 manifest 자체를 읽는 시간이 늘어납니다.

-- 파일 수 확인
SELECT count(*) AS file_count,
       sum(file_size_in_bytes) / (1024*1024*1024) AS total_gb
FROM "events$files";

10.3 Predicate Pushdown 이 안 되는 WHERE 조건

WHERE 절에 컬럼을 함수로 감싸면 predicate pushdown 이 깨집니다.

패턴Pushdown설명
WHERE event_time = TIMESTAMP '...'O직접 비교
WHERE CAST(event_time AS DATE) = DATE '...'X함수 적용 → 풀스캔
WHERE year(event_time) = 2026X함수 래핑 → 풀스캔
WHERE LOWER(city) = 'seoul'X문자열 함수 → 풀스캔
WHERE city LIKE '%eoul'X앞쪽 와일드카드 → min/max 활용 불가
WHERE city IN ('Seoul', 'Busan')OIN 리스트 → pushdown 가능
WHERE event_time BETWEEN ... AND ...O범위 비교 → pushdown + partition 프루닝

핵심 규칙: 컬럼은 항상 왼쪽에 단독으로, 변환은 오른쪽(상수 쪽)에서 처리.

-- BAD: pushdown 안 됨
WHERE CAST(event_time AS DATE) = DATE '2026-04-12'
 
-- GOOD: pushdown 됨
WHERE event_time >= TIMESTAMP '2026-04-12 00:00:00 UTC'
  AND event_time <  TIMESTAMP '2026-04-13 00:00:00 UTC'

10.4 정렬되지 않은 데이터

Parquet row group 의 min/max 통계는 데이터가 정렬되어 있을 때 효과적입니다.

  • 정렬된 경우: row group 1 은 user_id 1~1000, row group 2 는 1001~2000WHERE user_id = 500 이면 row group 1만 읽음
  • 비정렬 경우: 모든 row group 의 user_id 범위가 1~100000 으로 겹침 → 전부 읽어야 함
-- 정렬 키 설정
ALTER TABLE events SET PROPERTIES
  sorted_by = ARRAY['user_id'];
 
-- 기존 데이터 재정렬
ALTER TABLE events EXECUTE optimize;

10.5 JOIN 시 Dynamic Filtering 미작동

Dynamic Filtering 이 작동하지 않는 조건:

  • Build side 결과가 너무 클 때 (수십만 건 이상) → dynamic filter 생성 포기
  • LEFT JOIN / FULL OUTER JOIN → probe side 를 줄일 수 없음
  • JOIN 키가 파티션 컬럼과 무관할 때

10.6 과도한 스냅샷 누적

Iceberg 는 매 쓰기마다 새 스냅샷을 생성합니다. 시간이 지나면 수천 개의 스냅샷이 쌓이고, metadata file 크기 증가로 플래닝이 느려집니다.

-- 오래된 스냅샷 만료
ALTER TABLE events EXECUTE expire_snapshots(retention_threshold => '7d');
 
-- 고아 파일 제거
ALTER TABLE events EXECUTE remove_orphan_files(retention_threshold => '7d');

10.7 Worker 메모리 부족

파티션/파일 수와 무관하게, 대량 데이터를 집계·조인하면 Worker 메모리가 초과됩니다. Trino 는 디스크로 spill 가능하지만 성능이 크게 저하됩니다.

대응:

  • query.max-memory-per-node 조정
  • SELECT * 지양, 필요한 컬럼만 지정
  • 대량 집계는 단계별로 분리

11. 성능 확보를 위한 추가 팁

항목설정/명령효과
통계 수집ANALYZE eventsTrino 플래너가 더 나은 실행 계획 생성
Row group 크기write.parquet.row-group-size-bytes적절한 크기로 row group 프루닝 효과 조정
정렬 키sorted_by = ARRAY['col']min/max 통계 범위를 좁혀서 프루닝 효과 극대화
컴팩션ALTER TABLE ... EXECUTE optimize작은 파일 병합, 정렬 적용
스냅샷 관리EXECUTE expire_snapshotsmetadata 크기 감소
고아 파일 제거EXECUTE remove_orphan_files불필요한 스토리지 회수

12. 성능 저하 진단 체크리스트

문제가 발생했을 때 아래 순서로 확인하세요.

순서확인 항목확인 방법
1WHERE 절에 함수가 감싸져 있지 않은가쿼리 리뷰
2파일/파티션 프루닝이 동작하는가EXPLAIN (TYPE DISTRIBUTED)
3작은 파일이 과다하지 않은가SELECT count(*), sum(file_size_in_bytes) FROM "table$files"
4데이터가 정렬되어 있는가sorted_by 속성 확인
5스냅샷이 과도하게 쌓여 있지 않은가SELECT count(*) FROM "table$snapshots"
6Dynamic Filtering 이 동작하는가EXPLAIN 에서 dynamicFilter 존재 여부
7Worker 메모리가 충분한가Trino Web UI → Query Detail → Stage 별 메모리

13. 정리

항목Hive/ImpalaTrino + Iceberg
파티션 구조디렉터리 기반, HMS 관리Manifest 기반, HMS 무관
파티션 컬럼 누락 시풀스캔Hidden partitioning 으로 자동 프루닝
파티션 수 한계수만~수십만사실상 무제한
파일 수준 프루닝불가능Manifest 의 min/max 통계로 가능
파티션 전략 변경테이블 재생성 필요Partition Evolution 으로 무중단 변경
동적 파티션 생성 제한있음 (기본 1000)없음

Hive/Impala 의 파티션 문제는 "사용자의 실수" 가 아니라 디렉터리 기반 파티셔닝의 구조적 한계입니다. Iceberg 는 메타데이터 계층을 테이블 포맷 안으로 가져옴으로써 이 한계를 근본적으로 제거했고, Trino 는 이 메타데이터를 최대한 활용하여 불필요한 I/O 를 줄입니다.

다만 Trino + Iceberg 도 만능은 아닙니다. Small file problem, predicate pushdown 이 깨지는 SQL 패턴, 비정렬 데이터, 스냅샷 누적 등은 여전히 성능을 저하시킵니다. EXPLAIN 으로 실행 계획을 확인하고, 정기적인 컴팩션과 스냅샷 관리를 습관화하는 것이 핵심입니다.


이 글은 Trino 443 + Iceberg spec v2 기준으로 작성되었습니다. 실제 환경에서의 파티션 설계나 마이그레이션에 대해 도움이 필요하시면 언제든 문의해 주세요.

— Data Dynamics 엔지니어링 팀