Перейти к основному содержимому
Перейти к основному содержимому

Движок таблицы Iceberg

Примечание

Мы рекомендуем использовать табличную функцию Iceberg для работы с данными Iceberg в ClickHouse. В настоящее время табличная функция Iceberg предоставляет достаточную функциональность, реализуя частичный интерфейс только для чтения к таблицам Iceberg.

Движок таблицы Iceberg доступен, но может иметь ограничения. Изначально ClickHouse не был спроектирован для поддержки таблиц со схемами, которые изменяются извне, что может влиять на работу движка таблицы Iceberg. В результате некоторые возможности, доступные для обычных таблиц, могут быть недоступны или работать некорректно, особенно при использовании старого анализатора запросов.

Для оптимальной совместимости мы рекомендуем использовать табличную функцию Iceberg, пока мы продолжаем улучшать поддержку движка таблицы Iceberg.

Этот движок обеспечивает доступ только для чтения к существующим таблицам Apache Iceberg в Amazon S3, Azure, HDFS, а также к локально хранимым таблицам.

Создание таблицы

Обратите внимание, что таблица 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, чтобы передать role_arn для доступа на основе ролей в ClickHouse Cloud. См. Secure 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, схема которых со временем изменялась. Это включает таблицы, в которых столбцы добавлялись, удалялись или менялся их порядок, а также столбцы, изменённые из обязательных в допускающие значение NULL. Дополнительно поддерживаются следующие преобразования типов:

  • int -> long
  • float -> double
  • decimal(P, S) -> decimal(P', S), где P' > P.

Сейчас невозможно изменять вложенные структуры или типы элементов внутри массивов и отображений.

Чтобы прочитать таблицу, схема которой изменилась после её создания, с использованием динамического вывода схемы, установите allow_dynamic_metadata_for_data_lakes = true при создании таблицы.

Отсечение партиций

ClickHouse поддерживает отсечение партиций в запросах SELECT к таблицам Iceberg, что помогает оптимизировать производительность запросов за счёт пропуска нерелевантных файлов данных. Чтобы включить отсечение партиций, установите use_iceberg_partition_pruning = 1. Дополнительную информацию об отсечении партиций в Iceberg см. в спецификации: https://iceberg.apache.org/spec/#partitioning

Time travel

В ClickHouse поддерживается механизм time travel для таблиц Iceberg, который позволяет выполнять запросы к историческим данным по заданной временной метке или идентификатору снимка (snapshot).

Обработка таблиц с удалёнными строками

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 в одном запросе одновременно.

Важные замечания

  • Снапшоты обычно создаются, когда:

    • В таблицу записываются новые данные
    • Выполняется операция компакции данных
  • Изменения схемы, как правило, не создают снапшоты — это приводит к важным особенностям поведения при использовании механизма time travel для таблиц, которые претерпели эволюцию схемы.

Примеры сценариев

Все сценарии приведены на Spark, потому что ClickHouse (CH) пока не поддерживает запись в таблицы Iceberg.

Сценарий 1: Изменения схемы без новых снапшотов

Рассмотрим следующую последовательность операций:

 -- Create a table with two columns
  CREATE TABLE IF NOT EXISTS spark_catalog.db.time_travel_example (
  order_number int, 
  product_code string
  ) 
  USING iceberg 
  OPTIONS ('format-version'='2')

-- Insert data into the table
  INSERT INTO spark_catalog.db.time_travel_example VALUES 
    (1, 'Mars')

  ts1 = now() // A piece of pseudo code

-- Alter table to add a new column
  ALTER TABLE spark_catalog.db.time_travel_example ADD COLUMN (price double)
 
  ts2 = now()

-- Insert data into the table
  INSERT INTO spark_catalog.db.time_travel_example VALUES (2, 'Venus', 100)

   ts3 = now()

-- Query the table at each timestamp
  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: Отличия между исторической и текущей схемами

Запрос time travel, выполненный в текущий момент времени, может показать схему, отличающуюся от текущей схемы таблицы:

-- Create a table
  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 initial data into the table
  INSERT INTO spark_catalog.db.time_travel_example_2 VALUES (2, 'Venus');

-- Alter table to add a new column
  ALTER TABLE spark_catalog.db.time_travel_example_2 ADD COLUMN (price double);

  ts = now();

-- Query the table at a current moment but using timestamp syntax

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

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

-- Query the table at a current moment
  SELECT * FROM spark_catalog.db.time_travel_example_2;
    +------------+------------+-----+
    |order_number|product_code|price|
    +------------+------------+-----+
    |           2|       Venus| NULL|
    +------------+------------+-----+

Это происходит потому, что ALTER TABLE не создает новый снимок, а для текущей таблицы Spark берет значение schema_id из последнего файла метаданных, а не из снимка.

Сценарий 3: Отличия между исторической и текущей схемами

Второй момент заключается в том, что при выполнении операции time travel вы не можете получить состояние таблицы до того, как в неё были записаны какие-либо данные:

-- Create a table
  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();

-- Query the table at a specific timestamp
  SELECT * FROM spark_catalog.db.time_travel_example_3 TIMESTAMP AS OF ts; -- Finises with error: Cannot find a snapshot older than ts.

В ClickHouse поведение аналогично Spark. Вы можете мысленно заменить запросы Select в Spark на запросы Select в ClickHouse — и всё будет работать так же.

Определение файла метаданных

При использовании движка таблиц Iceberg в ClickHouse системе необходимо найти корректный файл metadata.json, который описывает структуру таблицы Iceberg. Ниже описано, как работает этот процесс определения:

  1. Явное указание пути:
  • Если вы задаёте iceberg_metadata_file_path, система будет использовать именно этот путь, комбинируя его с путём к директории таблицы Iceberg.
  • При наличии этого параметра все остальные параметры определения игнорируются.
  1. Сопоставление UUID таблицы:
  • Если указан iceberg_metadata_table_uuid, система будет:
    • Рассматривать только файлы .metadata.json в директории metadata
    • Отфильтровывать файлы, содержащие поле table-uuid, совпадающее с указанным UUID (без учёта регистра)
  1. Поиск по умолчанию:
  • Если ни один из вышеперечисленных параметров не задан, все файлы .metadata.json в директории metadata рассматриваются как кандидаты

Выбор самого нового файла

После определения файлов-кандидатов по указанным правилам система решает, какой из них является самым новым:

  • Если включён iceberg_recent_metadata_file_by_last_updated_ms_field:

    • Выбирается файл с максимальным значением поля last-updated-ms
  • В противном случае:

    • Выбирается файл с наибольшим номером версии
    • (Версия представлена как V в именах файлов формата V.metadata.json или V-uuid.metadata.json)

Примечание: Все упомянутые параметры (если явно не указано иное) являются настройками на уровне движка и должны указываться при создании таблицы, как показано ниже:

CREATE TABLE example_table ENGINE = Iceberg(
    's3://bucket/path/to/iceberg_table'
) SETTINGS iceberg_metadata_table_uuid = '6f6f6407-c6a5-465f-a808-ea8900e35a38';

Примечание: Хотя каталоги Iceberg обычно отвечают за разрешение метаданных, табличный движок Iceberg в ClickHouse напрямую интерпретирует файлы, хранящиеся в S3, как таблицы Iceberg, поэтому важно понимать правила их разрешения.

Кэш данных

Табличный движок Iceberg и одноимённая табличная функция поддерживают кэширование данных так же, как и хранилища S3, AzureBlobStorage, HDFS. См. здесь.

Кэш метаданных

Движок таблиц и табличная функция Iceberg поддерживают кэш метаданных, в котором хранится информация о manifest-файлах, списке manifest и JSON-файле метаданных. Кэш хранится в памяти. Эта функция управляется настройкой use_iceberg_metadata_files_cache, которая по умолчанию включена.

Асинхронная предзагрузка метаданных

Асинхронную предзагрузку метаданных можно включить при создании таблицы Iceberg, задав параметр iceberg_metadata_async_prefetch_period_ms. Если задано значение 0 (по умолчанию) или если кэширование метаданных не включено, асинхронная предзагрузка отключена. Чтобы включить эту возможность, следует указать ненулевое значение в миллисекундах. Оно задаёт интервал между циклами предзагрузки.

Если параметр включён, сервер будет выполнять периодическую фоновую операцию: получать список удалённого каталога и обнаруживать новую версию метаданных. Затем он разберёт её и рекурсивно обойдёт снимок, загружая активные файлы списка манифестов и файлы манифестов. Файлы, уже доступные в кэше метаданных, не будут загружаться повторно. В конце каждого цикла предзагрузки последний снимок метаданных будет доступен в кэше метаданных.

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. Если указать допустимую степень устаревания метаданных, сервер сможет использовать кэшированную версию снимка метаданных без обращения к удалённому catalog. Если версия метаданных есть в кэше и была загружена в пределах заданного окна устаревания, она будет использоваться для обработки запроса. В противном случае последняя версия будет получена из удалённого catalog.

SELECT count() FROM icebench_table WHERE ...
SETTINGS iceberg_metadata_staleness_ms=120000

Примечание: Асинхронная предзагрузка метаданных выполняется в ICEBERG_SCEDULE_POOL — это серверный пул потоков для фоновых операций с активными таблицами Iceberg. Размер этого пула потоков задаётся параметром настройки сервера iceberg_background_schedule_pool_size (по умолчанию — 10).

Примечание: В настоящее время предполагается, что размер кэша метаданных достаточен для полного хранения последнего снимка метаданных всех активных таблиц, если включена асинхронная предзагрузка.

См. также