СУБД не нужна

Работаем с Iceberg из Trino и DuckDB

Iceberg Nessie DuckDB Trino
Работаем с Iceberg из Trino и DuckDB

Как-то я привык, что если хочешь писать SQL, тебе нужна СУБД. А оказывается, давно вперёд шагнули технологии. Про iceberg слышали, про duckdb слышали, про мета каталоги слышали.. слышали ведь?

Надо закинуть всё в кастрюльку, потрястись под музыку и посмотреть, что получится.

Iceberg

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

Некоторую часть данных мы просто храним в S3 паркетами, но это долго.
Для объемных таблиц организовали гибридное хранение, когда данные по мере устаревания автоматически отправляются с SSD на HDD, а оттуда на S3. Но из-за репликации они дублируются в объектном хранилище, что кажется напрасным.

Поискав в инете, выяснил, что есть открытые форматы таблиц. Люди придумали не просто складывать паркеты, а докидывать к ним метаинформацию и всякие вспомогательные расчеты.

Nessie

Но это всё равно какие-то паркеты в облаке! Как с ними одновременно-то работать? И файлы с метаинфорацией плодятся с огромной скоростью, едва тронешь данные. Ещё непонятно, что там с доступами и ролями.
Вот так появились каталоги – сервисы с постгресом, которые решают эти проблемы.

Я потыкал несколько, и успехов за разумное время добился только с Nessie. Хоть это чудовище у меня и ломалось с каждым релизом, альтернативы оказались еще страшнее.

Trino и DuckDB

Ну и кто-то должен взять на себя грязную работу - исполнять мой SQL. Trino имеет кучу коннекторов к разным базам и форматам, может масштабироваться вдоль, и многие облака сделали у себя его managed версии. А вот suckdb меня привлек тем, что ему не нужен отдельный сервис, может запускаться любым процессом.
Более того, у него есть wasm вариант - исполняется прямо в браузере.

Cooking

Пришло время варить!
Чтобы сложить данные в айсберг, нужны эти самые данные, поэтому поднимаю Trino.

# ./docker-compose.yamlservices:  trino:    image: trinodb/trino    container_name: trino    restart: unless-stopped    networks:      iceberg-nessie-net:    ports:      - '8080:8080'    volumes:      - './catalog:/etc/trino/catalog'      - './config/log.properties:/etc/trino/log.properties'    env_file:      - .env networks:  iceberg-nessie-net:

И знакомлю его с имеющимся у меня ClickHouse

# ./catalog/clickhouse.propertiesconnector.name=clickhouseconnection-url=${ENV:CH_URL}connection-user=${ENV:CH_USER}connection-password=${ENV:CH_PASSWORD}clickhouse.map-string-as-varchar=true

Уже можно поселектить КХ через триню в дибивере. Но эти данные мне же надо перелить в S3.. в ведро 🤣
Я завёл бакет в яндексе и выписал сервисный аккаунт к нему со статичным ключом. Пришло время призвать из глубин чудовище.

# docker-compose.yamlservices:  catalog:    image: ghcr.io/projectnessie/nessie:0.105.5    container_name: nessie    restart: unless-stopped    ports:      - '19120:19120'    environment:      # общие      - nessie.server.default-branch=main      - nessie.server.authentication.enabled=false      - write.object-storage.enabled=true       - nessie.version.store.type=JDBC2      - nessie.version.store.persist.jdbc.datasource=postgresql      - quarkus.datasource.postgresql.jdbc.url=${DB_URL}      - quarkus.datasource.postgresql.username=${DB_USERNAME}      - quarkus.datasource.postgresql.password=${DB_PASSWORD}       # хранилища      - nessie.catalog.default-warehouse=yandex      - nessie.catalog.warehouses.yandex.location=s3://lakehouse       # s3 яндекс      - nessie.catalog.service.s3.buckets.lakehouse.endpoint=${S3_YANDEX_ENDPOINT}      - nessie.catalog.service.s3.buckets.lakehouse.region=${S3_YANDEX_REGION}      - nessie.catalog.service.s3.buckets.lakehouse.path-style-access=false       # обязательно передавать через секрет      - nessie.catalog.service.s3.buckets.lakehouse.access-key=urn:nessie-secret:quarkus:yandex-secrets      - yandex-secrets.name=${S3_YANDEX_ACCESS_KEY}      - yandex-secrets.secret=${S3_YANDEX_SECRET_KEY}     env_file:      - .env    networks:      iceberg-nessie-net:

В этом конфиге создаётся warehouse под названием yandex, в которое будут складываться паркеты. Обратите внимание, что название хранилища потом используется в пути настроек для него. Аналогично с названием бакета.
Ах да, мне всё-таки нужна СУБД 😅
Я указываю креды managed постгресса в том же яндексе. Несси сделает в нём буквально пару табличек в отдельной базе, ничего критичного.

Познакомим с новым зверьком триню.

# ./catalog/nessie.propertiesconnector.name=icebergfs.native-s3.enabled=trueiceberg.catalog.type=resticeberg.rest-catalog.uri=http://catalog:19120/icebergiceberg.rest-catalog.warehouse=yandexs3.endpoint=${ENV:S3_YANDEX_ENDPOINT}s3.region=${ENV:S3_YANDEX_REGION}s3.path-style-access=falses3.aws-access-key=${ENV:S3_YANDEX_ACCESS_KEY}s3.aws-secret-key=${ENV:S3_YANDEX_SECRET_KEY}

Наконец можно покидать данные в ведро 🤣

insert into nessie.some_schema.some_tableselect * from clickhouse.some_schema.some_tablewhere date(date) = date('2025-10-26')limit 100;

Чуете странное в конфигах? да, я знаю, вас так просто не провести.
Ключ сервисного акка для доступа к бакету у меня указан и в каталоге и в SQL движке. Каталогу он нужен, чтобы видеть файлы, а движку, чтобы работать с ними. Вроде логично.. но как-то тупо. Поэтому придумали научить каталог делиться кредами, и тут есть два варианта:

  • движок составляет запрос к s3, отправляет в каталог, тот его подписывает токеном, возвращает, и движок шлёт результат к объектном хранилищу. Это жидкий вариант, мы не будем его показывать
  • движок запрашивает временные креды у каталога, сохраняет у себя и использует, пока не протухнут, потом обновляет.

В настройках несси для трини надо внести корректировки

# ./catalog/nessie.properties # включаем получение кредов у каталогаiceberg.rest-catalog.vended-credentials-enabled=true# всякие access-key и secret-key можно убрать

Это было просто, а вот чудовище заставило меня повозиться по незнанке. Сперва я потестил выдачу временных прав по инструкции яндекс клауда через cli

aws --endpoint https://sts.yandexcloud.net/ sts assume-role \  --role-arn a1234567891234567890 \  --role-session-name project_id \  --duration-seconds 3600 \  --policy file://policy.json

И когда оно заработало, перенёс в несси.. ну, или наоборот 😅
Конечно же для продакшна надо всё переписать.

# docker-compose.yaml # отключаем механизм подписывания запросов: медленный и не поддерживается движками- nessie.catalog.service.s3.buckets.lakehouse.request-signing-enabled=false# настраиваем выдачу временных кредов- nessie.catalog.service.s3.buckets.lakehouse.sts-endpoint=https://sts.yandexcloud.net/- nessie.catalog.service.s3.buckets.lakehouse.client-iam.enabled=true- nessie.catalog.service.s3.buckets.lakehouse.client-iam.assume-role=a1234567891234567890- nessie.catalog.service.s3.buckets.lakehouse.client-iam.role-session-name=project_id- nessie.catalog.service.s3.buckets.lakehouse.client-iam.session-duration=3600- nessie.catalog.service.s3.buckets.lakehouse.client-iam.policy={"Version":"2012-10-17","Statement":{"Sid":"all","Effect":"Allow","Principal":"*","Action":"*","Resource":"arn:aws:s3:::lakehouse/*"}}

А что там с уткой? Зацените, как легко теперь подключить этот каталог в dickdb

ATTACH '' AS lakehouse (    TYPE iceberg,    AUTHORIZATION_TYPE 'none',    ENDPOINT 'http://localhost:19120/iceberg/');

По логике надо было бы указать имя warehouse, но тогда некоторые команды не работают из-за наличия yandex|main в пути.
Эту и кучу других проблем вам удалось скипнуть, потому что только дураки учатся на своих ошибках.

Полезные ссылки

  1. Apache Iceberg
  2. Project Nessie
  3. DuckDB
  4. Big data is dead
  5. Больше чем просто данные в S3. Iceberg как основа архитектуры Next-Gen КХД