메인 콘텐츠로 건너뛰기
ClickHouse에서 Iceberg 데이터를 다룰 때는 Iceberg 테이블 함수을 사용하는 것이 좋습니다. 현재 Iceberg 테이블 함수은 충분한 기능을 제공하며, Iceberg 테이블에 대해 부분적인 읽기 전용 인터페이스를 제공합니다.Iceberg Table Engine도 사용할 수 있지만 몇 가지 제한이 있을 수 있습니다. ClickHouse는 외부에서 스키마가 변경되는 테이블을 지원하도록 처음부터 설계되지 않았기 때문에, 이로 인해 Iceberg Table Engine의 기능이 영향을 받을 수 있습니다. 따라서 일반 테이블에서는 동작하는 일부 기능을 사용할 수 없거나 제대로 동작하지 않을 수 있으며, 특히 이전 분석기를 사용할 때 이런 문제가 더 두드러질 수 있습니다.최상의 호환성을 위해 Iceberg Table Engine 지원이 계속 개선되는 동안에는 Iceberg 테이블 함수을 사용하는 것이 좋습니다.
이 엔진은 Amazon S3, Azure, HDFS 및 로컬에 저장된 기존 Apache Iceberg 테이블에 대한 읽기 전용 통합을 제공합니다.

테이블 생성

Iceberg 테이블은 저장소에 이미 존재해야 하며, 이 명령은 새 테이블을 생성하는 DDL 매개변수를 지원하지 않습니다.
CREATE TABLE iceberg_table_s3
    ENGINE = IcebergS3(url,  [, NOSIGN | access_key_id, secret_access_key, [session_token]], format, [,compression], [,extra_credentials])

CREATE TABLE iceberg_table_azure
    ENGINE = IcebergAzure(connection_string|storage_account_url, container_name, blobpath, [account_name, account_key, format, compression])

CREATE TABLE iceberg_table_hdfs
    ENGINE = IcebergHDFS(path_to_table, [,format] [,compression_method])

CREATE TABLE iceberg_table_local
    ENGINE = IcebergLocal(path_to_table, [,format] [,compression_method])

엔진 인수

인수 설명은 각각 S3, AzureBlobStorage, HDFS, File 엔진의 인수 설명과 동일합니다. format은 Iceberg 테이블의 데이터 파일 포맷을 나타냅니다. IcebergS3에서는 선택 사항인 extra_credentials 매개변수를 사용해 ClickHouse Cloud에서 역할 기반 접근을 위한 role_arn을 전달할 수 있습니다. 구성 단계는 보안 S3를 참조하십시오. 엔진 매개변수는 이름이 지정된 컬렉션을 사용해 지정할 수 있습니다.

예시

CREATE TABLE iceberg_table ENGINE=IcebergS3('http://test.s3.amazonaws.com/clickhouse-bucket/test_table', 'test', 'test')
이름이 지정된 컬렉션 사용:
<clickhouse>
    <named_collections>
        <iceberg_conf>
            <url>http://test.s3.amazonaws.com/clickhouse-bucket/</url>
            <access_key_id>test</access_key_id>
            <secret_access_key>test</secret_access_key>
        </iceberg_conf>
    </named_collections>
</clickhouse>
CREATE TABLE iceberg_table ENGINE=IcebergS3(iceberg_conf, filename = 'test_table')

별칭

테이블 엔진 Iceberg는 이제 IcebergS3의 별칭입니다.

데이터 타입

다음 표는 스키마 추론 시(읽기용) Iceberg 데이터 타입이 ClickHouse 데이터 타입에 어떻게 매핑되는지 보여줍니다.

기본 타입

Iceberg 타입ClickHouse 타입비고
booleanBool
intInt32
long, bigintInt64
floatFloat32
doubleFloat64
dateDate32
timeInt64자정부터 경과한 마이크로초
timestampDateTime64(6)마이크로초, 시간대 없음
timestamptzDateTime64(6, 'UTC')마이크로초, UTC 시간대
timestamp_nsDateTime64(9)나노초, 시간대 없음 (Iceberg v3부터만 지원)
timestamptz_nsDateTime64(9, 'UTC')나노초, UTC 시간대 (Iceberg v3부터만 지원)
string, binaryString
uuidUUID
fixed(N)FixedString(N)
decimal(P, S)Decimal(P, S)

복합 타입

Iceberg 유형ClickHouse 유형
listArray
mapMap
structTuple

스키마 진화

ClickHouse는 시간이 지나며 스키마가 변경된 Iceberg 테이블도 읽을 수 있습니다. 여기에는 컬럼이 추가, 삭제 또는 재정렬된 테이블과 required에서 널 허용으로 변경된 컬럼이 있는 테이블이 포함됩니다. 또한 다음과 같은 타입 캐스트를 지원합니다:
  • int -> long
  • float -> double
  • decimal(P, S) -> decimal(P’, S) where P’ > P.
현재는 중첩 구조나 배열 및 맵 내부 요소의 타입은 변경할 수 없습니다. 동적 스키마 추론으로 생성된 테이블에서 생성 후 스키마가 변경된 경우 이를 읽으려면, 테이블을 생성할 때 allow_dynamic_metadata_for_data_lakes = true를 설정하십시오.

파티션 프루닝

ClickHouse는 Iceberg 테이블에 대한 SELECT 쿼리에서 파티션 프루닝을 지원하며, 관련 없는 데이터 파일을 건너뛰어 쿼리 성능을 최적화하는 데 도움이 됩니다. 파티션 프루닝을 활성화하려면 use_iceberg_partition_pruning = 1로 설정합니다. Iceberg 파티션 프루닝에 대한 자세한 내용은 https://iceberg.apache.org/spec/#partitioning 를 참조하십시오.

시점 조회

ClickHouse는 Iceberg 테이블의 시점 조회를 지원하므로, 특정 타임스탬프 또는 스냅샷 ID를 기준으로 과거 데이터를 쿼리할 수 있습니다.

삭제된 행이 포함된 테이블 처리

ClickHouse는 다음 삭제 방식을 사용하는 Iceberg 테이블을 읽을 수 있습니다: 다음 삭제 방식은 지원되지 않습니다:

기본 사용법

 SELECT * FROM example_table ORDER BY 1 
 SETTINGS iceberg_timestamp_ms = 1714636800000
 SELECT * FROM example_table ORDER BY 1 
 SETTINGS iceberg_snapshot_id = 3547395809148285433
참고: 동일한 쿼리에서는 iceberg_timestamp_ms 매개변수와 iceberg_snapshot_id 매개변수를 동시에 지정할 수 없습니다.

중요한 고려 사항

  • 스냅샷은 일반적으로 다음과 같은 경우 생성됩니다:
    • 새 데이터가 테이블에 기록될 때
    • 어떤 형태로든 데이터 compaction이 수행될 때
  • 스키마 변경은 일반적으로 스냅샷을 생성하지 않습니다 - 이로 인해 스키마 진화를 거친 테이블에 시점 조회를 사용할 때 중요한 동작상의 차이가 발생합니다.

예시 시나리오

CH는 아직 Iceberg 테이블 쓰기를 지원하지 않으므로, 모든 시나리오는 Spark로 작성했습니다.

시나리오 1: 새 스냅샷 없이 스키마가 변경되는 경우

다음 작업 시퀀스를 살펴보겠습니다:
 -- 두 개의 컬럼으로 테이블 생성
  CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example (
  order_number int, 
  product_code string
  ) 
  USING iceberg 
  OPTIONS ('format-version'='2')

-- 테이블에 데이터 삽입
  INSERT INTO spark_catalog.db.time_travel_example VALUES 
    (1, 'Mars')

  ts1 = now() // 의사 코드(pseudo code) 예시

-- 새 컬럼 추가를 위해 테이블 변경
  ALTER TABLE spark_catalog.db.time_travel_example ADD COLUMN (price double)
 
  ts2 = now()

-- 테이블에 데이터 삽입
  INSERT INTO spark_catalog.db.time_travel_example VALUES (2, 'Venus', 100)

   ts3 = now()

-- 각 타임스탬프 시점에서 테이블 쿼리
  SELECT * FROM spark_catalog.db.time_travel_example TIMESTAMP AS OF ts1;

+------------+------------+
|order_number|product_code|
+------------+------------+
|           1|        Mars|
+------------+------------+
  SELECT * FROM spark_catalog.db.time_travel_example TIMESTAMP AS OF ts2;

+------------+------------+
|order_number|product_code|
+------------+------------+
|           1|        Mars|
+------------+------------+

  SELECT * FROM spark_catalog.db.time_travel_example TIMESTAMP AS OF ts3;

+------------+------------+-----+
|order_number|product_code|price|
+------------+------------+-----+
|           1|        Mars| NULL|
|           2|       Venus|100.0|
+------------+------------+-----+
서로 다른 타임스탬프에서의 쿼리 결과:
  • ts1 & ts2: 원래의 두 개 컬럼만 표시됩니다
  • ts3: 세 개 컬럼이 모두 표시되며, 첫 번째 행의 price는 NULL입니다

시나리오 2: 과거 스키마와 현재 스키마의 차이

현재 시점의 시점 조회 쿼리는 현재 테이블과 다른 스키마를 표시할 수 있습니다:
-- 테이블 생성
  CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example_2 (
  order_number int, 
  product_code string
  ) 
  USING iceberg 
  OPTIONS ('format-version'='2')

-- 테이블에 초기 데이터 삽입
  INSERT INTO spark_catalog.db.time_travel_example_2 VALUES (2, 'Venus');

-- 새 컬럼 추가를 위한 테이블 변경
  ALTER TABLE spark_catalog.db.time_travel_example_2 ADD COLUMN (price double);

  ts = now();

-- timestamp 구문을 사용하여 현재 시점 테이블 쿼리

  SELECT * FROM spark_catalog.db.time_travel_example_2 TIMESTAMP AS OF ts;

    +------------+------------+
    |order_number|product_code|
    +------------+------------+
    |           2|       Venus|
    +------------+------------+

-- 현재 시점 테이블 쿼리
  SELECT * FROM spark_catalog.db.time_travel_example_2;
    +------------+------------+-----+
    |order_number|product_code|price|
    +------------+------------+-----+
    |           2|       Venus| NULL|
    +------------+------------+-----+
이는 ALTER TABLE이 새 스냅샷을 생성하지 않기 때문입니다. 현재 테이블에서는 Spark가 스냅샷이 아니라 최신 메타데이터 파일에서 schema_id 값을 가져옵니다.

시나리오 3: 과거 스키마와 현재 스키마의 차이

두 번째는 시점 조회를 수행할 때 테이블에 데이터가 아직 기록되지 않았던 시점의 상태는 가져올 수 없다는 점입니다:
-- 테이블 생성
  CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example_3 (
  order_number int, 
  product_code string
  ) 
  USING iceberg 
  OPTIONS ('format-version'='2');

  ts = now();

-- 특정 타임스탬프 시점의 테이블 쿼리
  SELECT * FROM spark_catalog.db.time_travel_example_3 TIMESTAMP AS OF ts; -- 오류 발생: ts보다 오래된 스냅샷을 찾을 수 없습니다.
ClickHouse에서는 동작 방식이 Spark와 동일합니다. Spark Select 쿼리를 ClickHouse Select 쿼리로 바꿔서 생각하면 같은 방식으로 동작합니다.

메타데이터 파일 결정

ClickHouse에서 Iceberg 테이블 엔진을 사용할 때 시스템은 Iceberg 테이블 구조를 설명하는 올바른 metadata.json 파일을 찾아야 합니다. 이 결정 과정은 다음과 같이 진행됩니다:
  1. 직접 경로 지정:
  • iceberg_metadata_file_path를 설정하면 시스템은 이 경로를 Iceberg 테이블 디렉터리 경로와 결합해 그대로 사용합니다.
  • 이 설정이 제공되면 다른 모든 확인 관련 설정은 무시됩니다.
  1. 테이블 UUID 일치:
  • iceberg_metadata_table_uuid가 지정되면 시스템은 다음과 같이 동작합니다:
    • metadata 디렉터리의 .metadata.json 파일만 확인합니다
    • 지정한 UUID와 일치하는 table-uuid 필드가 포함된 파일만 필터링합니다(대소문자 구분 없음)
  1. 기본 검색:
  • 위 두 설정이 모두 제공되지 않으면 metadata 디렉터리의 모든 .metadata.json 파일이 후보가 됩니다

최신 파일 선택

위 규칙에 따라 후보 파일을 식별한 후, 시스템은 그중 가장 최신 파일을 결정합니다:
  • iceberg_recent_metadata_file_by_last_updated_ms_field가 활성화된 경우:
    • last-updated-ms 값이 가장 큰 파일이 선택됩니다
  • 그렇지 않은 경우:
    • 버전 번호가 가장 높은 파일이 선택됩니다
    • (버전은 V.metadata.json 또는 V-uuid.metadata.json 형식의 파일 이름에서 V로 표시됩니다)
참고: 언급된 모든 설정은(명시적으로 달리 지정되지 않는 한) 엔진 수준 설정이며, 아래와 같이 테이블 생성 시 지정해야 합니다:
CREATE TABLE example_table ENGINE = Iceberg(
    's3://bucket/path/to/iceberg_table'
) SETTINGS iceberg_metadata_table_uuid = '6f6f6407-c6a5-465f-a808-ea8900e35a38';
참고: 일반적으로 Iceberg 카탈로그가 메타데이터 해석을 담당하지만, ClickHouse의 Iceberg 테이블 엔진은 S3에 저장된 파일을 Iceberg 테이블로 직접 해석합니다. 따라서 이러한 해석 규칙을 이해하는 것이 중요합니다.

데이터 캐시

Iceberg 테이블 엔진과 테이블 함수는 S3, AzureBlobStorage, HDFS 저장소와 마찬가지로 데이터 캐시를 지원합니다. 자세한 내용은 여기를 참조하십시오.

메타데이터 캐시

Iceberg 테이블 엔진과 테이블 함수는 manifest 파일, manifest 목록, 메타데이터 JSON 정보를 저장하는 메타데이터 캐시를 지원합니다. 이 캐시는 메모리에 저장됩니다. 이 기능은 use_iceberg_metadata_files_cache 설정으로 제어되며, 기본적으로 활성화되어 있습니다.

비동기 메타데이터 프리페치

비동기 메타데이터 프리페치는 iceberg_metadata_async_prefetch_period_ms를 설정하여 Iceberg 테이블 생성 시 활성화할 수 있습니다. 이 값을 0(기본값)으로 설정하거나 메타데이터 캐싱이 활성화되지 않은 경우, 비동기 프리페치는 비활성화됩니다. 이 기능을 활성화하려면 0이 아닌 밀리초 단위의 값을 지정해야 합니다. 이 값은 프리페치 사이클 사이의 간격을 나타냅니다. 활성화되면 server는 원격 catalog를 조회하고 새 메타데이터 버전을 감지하는 백그라운드 작업을 주기적으로 실행합니다. 그런 다음 이를 파싱하고 스냅샷을 재귀적으로 순회하면서 활성 manifest 목록 파일과 manifest 파일을 가져옵니다. 메타데이터 캐시에 이미 있는 파일은 다시 다운로드되지 않습니다. 각 프리페치 사이클이 끝나면 최신 메타데이터 스냅샷을 메타데이터 캐시에서 사용할 수 있습니다.
CREATE TABLE example_table ENGINE = Iceberg(
    's3://bucket/path/to/iceberg_table'
) SETTINGS
    iceberg_metadata_async_prefetch_period_ms = 60000;
읽기 작업에서 비동기 메타데이터 프리페치를 최대한 활용하려면 iceberg_metadata_staleness_ms 매개변수를 쿼리 또는 세션 매개변수로 지정해야 합니다. 기본값(0 - 지정되지 않음)에서는 각 쿼리마다 서버가 원격 catalog에서 최신 메타데이터를 가져옵니다. 메타데이터 staleness 허용 범위를 지정하면 서버는 원격 catalog를 호출하지 않고 캐시된 메타데이터 스냅샷 버전을 사용할 수 있습니다. 캐시에 메타데이터 버전이 있고, 지정된 staleness 윈도우 내에 다운로드된 경우 해당 버전을 사용해 쿼리를 처리합니다. 그렇지 않으면 원격 catalog에서 최신 버전을 가져옵니다.
SELECT count() FROM icebench_table WHERE ...
SETTINGS iceberg_metadata_staleness_ms=120000
참고: 비동기 메타데이터 프리페치는 ICEBERG_SCEDULE_POOL에서 실행되며, 이 풀은 활성 Iceberg 테이블의 백그라운드 작업을 처리하는 서버 측 스레드 풀입니다. 이 스레드 풀의 크기는 iceberg_background_schedule_pool_size 서버 구성 매개변수로 제어됩니다(기본값은 10). 참고: 비동기 프리페치가 활성화된 경우, 현재는 메타데이터 캐시 크기가 모든 활성 테이블의 최신 메타데이터 스냅샷 전체를 저장하기에 충분하다고 가정합니다.

관련 항목

마지막 수정일 2026년 6월 10일