СУБД не нужна
Работаем с 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 в пути.
Эту и кучу других проблем вам удалось скипнуть, потому что только дураки учатся на своих ошибках.