Iceberg руками: стенд, где все части собраны вместе

Прикасаюсь к Iceberg… а он горячий

Про Iceberg сейчас много чего понаписано, и почти всё какой-то мутный сумбур. Либо обзор «формат будущего, открытость, лейкхаус» и прочая бла-бла-бла, после которого непонятно, что делать-то? Где нажать? Либо разбор какого-то одного механизма: снимки отдельно, каталог отдельно, права отдельно, Trino где-то сбоку. Каждый кусок по отдельности понятен, а как они складываются в работающую систему, где аналитик пишет SELECT и получает данные или отказ, остаётся за кадром. Каюсь, сам писал тут пару статей давеча, и пока писал, всё время зудела мысль — «нужен стенд», чтобы у людей была возможность потыркать и понажимать. В вопросах, которые мне присылают: спрашивают не «что такое снимок», а … да и не присылают толком вопросы )) Отсюда ощущение, что есть инфовакуум в этой теме. Скажите, если это так или нет, там внизу в комментах. Буду рад обратной связи.

Короч, Iceberg плохо объясняется словами. Понимание приходит, когда сам сделал DELETE, полез в бакет и увидел, что файлы данных никуда не делись, а если делись, то понятно, почему и куда. Поэтому я собрал стенд, где все части уже соединены, поднимаются одной командой и работают так, как работали бы в компании. Лежит он на GitHub: maxpiter/iceberg-lab. Я очень надеюсь, что модераторы не сочтут это рекламой и не отправят меня в очередной бан, тем самым не дадут пропасть моему воистину титаническому труду и стоическому терпению в подготовке материала.

Коротко

  • Стенд поднимается через docker compose up -d: Trino, каталог Lakekeeper, права в OpenFGA, проверка через OPA, вход через Keycloak, S3 на RustFS. Ноутбука с 8 ГБ хватает.

  • Для работы нужен только DBeaver: пользователь видит Trino и пишет обычный SQL. Остальное спрятано за ним, как в рабочей системе.

  • Почти у каждого компонента есть веб-консоль, и всё можно потыкать руками: бакет, каталог, права, историю запросов, учётки.

  • На GitHub лежит подробный пошаговый roadmap, шесть глав по порядку: подключение, первая таблица, права, снимки и схема, обслуживание, ограничения.

  • Каждое действие видно с трёх сторон, результат в SQL, файлы в бакете, запись в каталоге.

  • Права выдаются мышкой в UI каталога и сразу действуют в Trino.

  • Мусор в таблице копится на глазах и убирается четырьмя процедурами.

Что внутри

Девять постоянно работающих сервисов, около 2,5 ГБ памяти в покое. Ещё пять контейнеров — разовые задания первичной настройки (ключи, бакет, миграции баз, warehouse и пользователи каталога): они отрабатывают при запуске и завершаются.

Компонент

Роль

Trino

единственная точка входа, обычный SQL из DBeaver

nginx

даёт Trino HTTPS, без него вход через Keycloak не работает

Lakekeeper

REST-каталог Iceberg: где таблицы, где их файлы, кому что можно

OpenFGA

хранит права, которые раздаются в UI Lakekeeper

OPA

перед каждым действием Trino запрашивает у него разрешение

Keycloak

вход людей и технических учёток

RustFS

S3, где физически лежат Parquet и метаданные

PostgreSQL, 2 шт.

служебные базы каталога и OpenFGA. На стенде это два отдельных экземпляра, но обе базы можно держать и в одном Postgres

Отдельно про Lakekeeper. На мой взгляд, это центральный компонент стенда и одна из самых важных вещей в открытом лейкхаусе сейчас. Lakekeeper — REST-каталог на Rust, он хранит указатель на актуальную версию каждой таблицы, раздаёт права и выдаёт движкам временные ключи к хранилищу. Ставится одним бинарником рядом с Postgres и не требует отдельной команды на сопровождение. Подробно я разбирал его в статье про Lakekeeper и Polaris на Хабре.

Пользователей заведено два. engineer — администратор каталога, создаёт таблицы и раздаёт права. analyst после запуска не может ничего, права ему выдаются по ходу глав.

Из инструментов на вашей стороне нужен только DBeaver, бесплатной Community-версии достаточно. Всё, что делается с данными, делается в нём обычным SQL. Никаких ноутбуков, плейграундов, Spark-сессий и консольных клиентов ставить не придётся.

Смотреть же на результат можно со всех сторон. Почти у каждого компонента есть веб-консоль, и в каждую можно зайти и потыкать руками:

Консоль

Что там видно

Lakekeeper

дерево каталога, схема таблицы, снимки, предпросмотр строк, выдача прав

RustFS

бакет с файлами Parquet и метаданными каждой таблицы

Trino

история запросов: кто, что и сколько выполнял, план запросов и много чего ещё

Keycloak

пользователи и технические учётки, их сессии

Адреса и логины с паролями ко всем консолям сведены в одну таблицу в README. У OpenFGA и OPA своего интерфейса нет, их работу видно по правам в UI Lakekeeper и по отказам в Trino, ну ещё можно увидеть в виде работающих контейнеров, а особо дотошные могут подключиться и посмотреть, что там внутри.

Важное в репозитории — подробный пошаговый roadmap из шести глав. Каждая опирается на предыдущую, в каждой есть готовые запросы, ожидаемый результат, тексты ошибок, которые вы увидите, и объяснение, почему так. Начинается он с подключения DBeaver через Keycloak, здесь ошибаются чаще всего, и этому посвящена отдельная глава с разбором каждого параметра.

Набора данных в репозитории нет, точнее — есть. В Trino встроен отдельный каталог tpch, который генерирует заказы, клиентов и поставщиков на лету, этого хватает. Загрузчиков данных из сторонних систем тоже нет: у всех своя загрузка, кто-то на Spark, кто-то на SeaTunnel, кто-то на Kafka Connect. Для них в Keycloak заведена техническая учётка loader с правом записи, подключайте своё. Кстати, рекомендую пойти в официальную доку Trino и взахлёб наиграться с этим замечательным движком. Для особо упоротых упорных (как я) можно запустить у Trino несколько воркеров и смотреть в консоли, как оно работает с пушдаун-запросами из разных систем. Ощутить мощщь, так сказать, ФЕДЕРАЦИИ.

Как это собрано в кучу

Прежде чем трогать таблицы, полезно один раз проследить путь обычного запроса. Аналитик в DBeaver выполняет SELECT count(*) FROM lakekeeper.sales.orders, и вот что происходит:

1. DBeaver -> Trino            "кто ты?"  -> браузер, логин в Keycloak, токен
2. Trino   -> OPA              "можно ли analyst читать sales.orders?"
3. OPA     -> Lakekeeper       "есть ли у analyst read_data на таблицу?"
4. Lakekeeper -> OpenFGA       проверка с учётом наследования схема -> таблица
5. Trino   -> Lakekeeper       "где таблица?" -> адрес metadata.json и временный ключ к S3
6. Trino   -> RustFS           читает метаданные, потом нужные Parquet

Шаги 2–4 — это права, 5–6 — сам Iceberg. В Trino не хранится ничего постоянного, у него нет своих прав, данных и ключа к хранилищу, всё это он при каждом запросе получает от остальных компонентов. Где лежит таблица, записано в каталоге, разрешения лежат в OpenFGA, файлы в S3, а пользователь из всего этого работает только с Trino.

По этой схеме удобно искать проблемы. Если пришёл отказ в доступе, смотреть надо шаги 2–4. Медленный запрос обычно упирается в чтение файлов на шаге 6, а если пропала таблица, разбираться начинают с каталога.

Самая наглядная демонстрация шага 5 — вкладка PREVIEW в UI Lakekeeper. Предпросмотр строк делает DuckDB, который работает прямо в браузере. Каталог выдаёт браузеру тот же временный ключ, и Parquet читается из бакета напрямую, без сервера посередине. Пришлось повозиться с адресом хранилища, чтобы он одинаково открывался и из контейнеров, и из браузера, зато правка hosts не нужна.

Таблица, у которой три части

Первое, что я предлагаю сделать, — создать таблицу и пойти смотреть, где она лежит.

CREATE SCHEMA lakekeeper.sales;

CREATE TABLE lakekeeper.sales.orders
WITH (partitioning = ARRAY['year(orderdate)'])
AS SELECT * FROM tpch.tiny.orders;

В консоли RustFS появится папка вида orders-1d37d136... с подпапками data/ и metadata/. Папки для схемы sales нет, таблица лежит прямо в корне warehouse. Спецификация Iceberg раскладку файлов по папкам не задаёт, её выбирают каталог и движок. Lakekeeper начиная с версии 0.13 по умолчанию кладёт таблицы плоско, без папок для схем, а Trino добавляет к имени таблицы случайный хвост, чтобы пересозданная таблица с тем же именем не попала в старую папку. Кому привычнее иерархия «схема — таблица», у warehouse в Lakekeeper есть настройка storage-layout со значением full-hierarchy.

Даже с папками схема остаётся сущностью каталога. Файлы Iceberg находит по полным путям, записанным в метаданных. Папки нужны только оператору, который смотрит в сам бакет.

Таблица Iceberg — это три сущности вместе: файлы Parquet, файлы метаданных и одна запись в каталоге, которая указывает на актуальный metadata.json. Цепочка чтения прослеживается по файлам:

каталог -> metadata.json -> список манифестов -> манифесты -> Parquet

То же самое Trino показывает служебными таблицами с суффиксом $: $files, $partitions, $snapshots. В $partitions год 1992 записан как 22, на этом путаются почти все. Преобразование year хранит число лет от 1970, а в именах папок Trino пишет привычный год.

После INSERT в бакете появляются новые Parquet, новый metadata.json и новые манифесты, а старые файлы остаются как были. Каталог переключает указатель на новый metadata.json одной атомарной операцией. На этом построены снимки и откат из следующего раздела.

Права мышкой

engineer в UI Lakekeeper открывает таблицу, вкладка PERMISSIONS, выбирает analyst и ставит select. analyst в DBeaver жмёт F5, и таблица появляется, ничего перезапускать не нужно. Попытка вставки получает Access Denied: Cannot insert into table, и в журнале аудита Lakekeeper остаётся событие с настоящим пользователем в поле for-principal. Право на схему действует на все её таблицы, включая созданные потом, это проверяется за минуту.

В документации отдельно разобрано, откуда берутся пользователи. Учётки людей хранятся в Keycloak, права в Lakekeeper, и до первого входа пользователя записи о нём в каталоге нет. Скрипт первичной настройки стенда заводит пользователей заранее через API каталога (для удобства).

Владелец таблицы — не вы. Таблица, созданная через Trino, принадлежит технической учётке trino. Trino подключается к каталогу под этой учёткой, и владельцем записывается она, хотя CREATE TABLE выполнял человек. Аналитик, создавший витрину в DBeaver, сам раздать на неё права в UI не сможет, это делает админ.

Каталоги вне Lakekeeper открыты. Связка Trino с OPA проверяет через Lakekeeper только его каталог. Остальные каталоги Trino (в стенде это tpch) пропускаются по флагу, и если подключить рядом PostgreSQL с рабочими данными, он окажется доступен всем, кто вошёл в Trino. В главе про ограничения разобраны три способа это закрыть.

Снимки: машина времени из коробки

Если по-простому, снимок — это сохранённая версия таблицы. Каждый INSERT, DELETE или UPDATE создаёт новую версию таблицы, предыдущая при этом остаётся целой. Похоже на историю правок в Google Docs: документ один, но к любому прошлому состоянию можно вернуться или просто на него посмотреть.

С версиями можно делать следующее: прочитать таблицу такой, какой она была вчера или до конкретной операции, — обычным SELECT с припиской FOR VERSION AS OF или FOR TIMESTAMP AS OF, или откатить таблицу целиком на прошлую версию одной командой rollback_to_snapshot. Ничего для этого настраивать не нужно: ни триггеров, ни таблиц истории, ни бэкапов. Это свойство самого формата, оно работает с первой записи.

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

В документации на GitHub всё это расписано по шагам, с готовыми запросами, и каждое действие можно повторить руками. Вот как это выглядит на стенде. Удаляем заказы со статусом P и обновляем одну строку. Получаем три снимка: append, delete, overwrite. В снимке delete есть интересная деталь. Счётчик total-records по-прежнему 15 000, хотя 363 строки удалены. Файлы данных остались нетронутыми, рядом появился маленький файл удалений со списком строк, которые надо считать несуществующими. Старый снимок на месте, и FOR VERSION AS OF возвращает все 15 000. Для такого чтения хватает права select, так что аналитик тоже может заглядывать в прошлое таблицы.

Все обращения к каталогу идут от учётки trino, и автор изменений вроде бы теряется, но Trino записывает его в снимок:

SELECT committed_at, operation, summary['trino_user'] AS author
FROM lakekeeper.sales."orders_tt$snapshots";

В истории таблицы остаётся, кто из людей что менял, ну, по правде, uid сложно назвать людьми, но тем не менее. Для разборов «кто удалил полмиллиона строк в пятницу вечером» это весьма ценно.

ADD COLUMN и RENAME COLUMN выполняются мгновенно, файлы не переписываются, потому что колонки в Iceberg хранятся по внутренним номерам. Старый снимок после переименования читается со старым именем колонки, со схемой своего момента. С партициями ещё веселее. Переключаем разбивку с годов на месяцы, дописываем свежие данные, и в бакете рядом лежат orderdate_year=1998/ и orderdate_month=1998-07/. Таблицу никто не пересоздавал. В классической СУБД для смены разбивки таблицу на пару терабайт пришлось бы пересоздать и перелить данные.

Теперь про откат на практике. Сама команда короткая, но тут меня ждал сюрприз: Trino отказывал в rollback_to_snapshot всем, включая администратора каталога.

Разобраться помогает схема из начала статьи. Перед каждым действием Trino отправляет запрос в OPA, а решение OPA принимает по правилам — файлам на языке Rego. Готовый набор правил для связки с Trino поставляет сам Lakekeeper. Для процедур вида ALTER TABLE ... EXECUTE в нём жёсткий список разрешённых: optimize, optimize_manifests, expire_snapshots, remove_orphan_files и ещё пара служебных, и только для тех, у кого есть право записи в таблицу. Отката в списке нет, а всё, чего нет в списке, OPA запрещает.

Править официальные файлы — плохая идея, при обновлении Lakekeeper правка потеряется. Авторы это предусмотрели и оставили точку расширения: если положить рядом свой файл allow_managed.rego, его правила добавятся к официальным. В стенде он такой:

allow_managed if {
	input.action.operation == "ExecuteTableProcedure"
	input.action.resource.function.functionName == "ROLLBACK_TO_SNAPSHOT"
	table := input.action.resource.table.tableName
	is_metadata_table(table) == false
	catalog := input.action.resource.table.catalogName
	schema := input.action.resource.table.schemaName
	require_table_access_simple(catalog, schema, table, "write_data")
}

Это копия официального правила для процедур обслуживания, только для одной процедуры и с тем же условием. OPA через Lakekeeper проверяет в OpenFGA, есть ли у пользователя право записи. В итоге право на откат выдаётся в UI Lakekeeper той же галочкой modify, что и право на optimize, ничего отдельно настраивать не нужно. engineer откатывает таблицу, analyst с одним select получает Access Denied.

Почему авторы не включили откат в список, я не знаю. Может, забыли, а может, считают его слишком опасным. Если ставите эту связку у себя, проверьте заранее. Ещё одна деталь. Откат возвращает данные, но не схему, добавленные колонки останутся. Откатиться можно на любую версию, пока её не удалила процедура expire_snapshots, так что глубину истории вы задаёте сами, об этом следующий раздел.

Мусор и уборка

Самый частый случай выглядит так. События из Kafka пишутся в таблицу потоком, через Flink, Kafka Connect или SeaTunnel. Загрузчик копит сообщения и раз в минуту фиксирует их в таблице, это и есть коммит. Первую неделю всё летает. Через месяц простой запрос за вчерашний день выполняется полминуты, бакет весит в несколько раз больше, чем сами данные, а DELETE старых событий место не освобождает. Первая реакция — добавить воркеров Trino, и это не помогает.

Причина в том, как устроена запись в Iceberg. Файлы никогда не меняются на месте, каждый коммит только добавляет новые, а старые автоматически не удаляются. Посчитаем: коммит раз в минуту — это 1440 коммитов в сутки и больше 40 тысяч за месяц. Каждый создаёт новый снимок, свои файлы Parquet в каждой партиции, куда попали события, новый манифест и новый metadata.json. За минуту сообщений набегает немного, поэтому файлы получаются по сотне-другой килобайт. Копится всё это в папке таблицы в бакете:

  • в data/ — тысячи мелких файлов Parquet. Запрос за один день открывает каждый из них вместо пары крупных;

  • там же — старые файлы, которые уже не входят в текущую версию таблицы: удалённые строки, заменённые при UPDATE. Их держат старые снимки, ведь к ним можно откатиться, поэтому DELETE места не освобождает;

  • там же — сироты, файлы от упавших или перезапущенных загрузок, на которые не ссылается вообще ничего. Загрузчик успел записать Parquet, а коммит не прошёл;

  • в metadata/ — десятки тысяч манифестов и metadata.json, которые планировщик Trino перебирает перед каждым запросом.

Часть этого мусора можно не производить, если коммитить реже, об этом ниже.

Убирается всё это четырьмя процедурами, они есть и в Trino, и в Spark:

  • optimize сливает мелкие файлы в крупные и заодно применяет файлы удалений;

  • expire_snapshots удаляет старые снимки и вместе с ними файлы, на которые больше никто не ссылается. Место освобождает только он;

  • remove_orphan_files убирает сирот;

  • optimize_manifests сжимает манифесты, это ускоряет планирование запроса.

На стенде весь цикл проходится за десять минут, в документации он расписан по шагам с готовыми запросами. Двенадцать маленьких вставок дают около 80 файлов по 7 КБ и 13 снимков, после уборки остаются 7 файлов и один снимок. Самое поучительное видно в бакете сразу после optimize: файлов стало больше, 87, потому что старые 80 всё ещё держат снимки. Место освобождается только после expire_snapshots. Сирот на стенде изображаем вручную: загружаем в папку data/ таблицы любой файл через консоль RustFS, и remove_orphan_files его удаляет.

Что запускать в рабочей системе и когда

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

Процедура

Когда

На что смотреть

optimize

каждую ночь, примерно 01:00–04:00, после ночных загрузок и до утренних отчётов. Для потока с коммитом раз в минуту и разбивкой по часам — дополнительно каждый час по прошлому часу

только по партициям, куда уже не пишут, через WHERE; вся таблица каждую ночь не нужна

expire_snapshots

каждую ночь, сразу после optimize, в той же цепочке

срок хранения снимков = насколько далеко назад можно откатиться

remove_orphan_files

раз в неделю, в ночь на воскресенье

срок хранения заметно больше самой долгой загрузки, не меньше нескольких дней

optimize_manifests

раз в неделю, после remove_orphan_files

при ежедневном optimize нужна редко

Ночь выбрана потому, что optimize переписывает файлы и нагружает Trino и хранилище наравне с тяжёлым отчётом. Его ставят в окно, где нет ни основных загрузок, ни людей с дашбордами, и утром аналитики приходят к уже сжатой таблице. Если ночного окна нет (круглосуточная нагрузка, несколько часовых поясов), компакцию выносят на отдельный кластер Trino или отдельную группу ресурсов, чтобы она не отнимала память у пользовательских запросов.

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

В цепочке optimize идёт первым. Он создаёт новые крупные файлы, а старые мелкие пока остаются на месте, их вместе со снимками удаляет expire_snapshots, который запускается следом. Сирот убирают отдельно и реже, на скорость запросов они не влияют, только занимают место.

Сжимать имеет смысл только партиции, куда уже не пишут. Если таблица разбита по дням, а поток из Kafka пишет в сегодняшнюю партицию, ночью сжимают вчерашнюю, и компакция не конкурирует с загрузкой. Условие в WHERE должно совпадать с границами партиций:

ALTER TABLE events EXECUTE optimize
WHERE event_date >= current_date - INTERVAL '1' DAY
  AND event_date < current_date;

Сроки хранения — главное место для ошибки. Trino по умолчанию не даёт удалять снимки и сирот моложе семи дней, и для рабочей системы эту защиту лучше не трогать. На стенде я её снял, чтобы результат был виден сразу, и там это безопасно. В рабочей системе нулевой срок у remove_orphan_files легко ломает таблицу. Загрузчик, который работает прямо сейчас, уже записал свои Parquet в бакет, но коммит ещё не сделал, и процедура удалит эти файлы как сирот. Срок для снимков зависит от того, на сколько дней назад аналитику может понадобиться откатиться, неделя — разумная отправная точка.

Больше всего мусора дают слишком частые коммиты. При коммите раз в минуту снимков и мелких файлов в десять раз больше, чем при коммите раз в десять минут. Если отчётам не нужна минутная свежесть, проще поднять интервал в загрузчике, чем потом компактировать. Размер файлов после загрузки стоит проверить по факту через $files, потому что порог в загрузчике часто сравнивается с размером до сжатия, а Parquet сжимает в разы.

Компакцию делает только движок, в каталоге её нет. Обычно её запускает оркестратор, который ночью дёргает Trino по списку таблиц, куда шла запись. Для истечения снимков и удаления сирот в Lakekeeper 0.13 есть свои задачи по расписанию. Они включаются на warehouse или на отдельную таблицу, по умолчанию выключены, ход выполнения виден на вкладке TASKS в UI. Мне нравится такое разделение — снимки и сирот отдать каталогу, optimize оставить оркестратору. Процедурам нужно право записи в таблицу, поэтому запускать их стоит под отдельной технической учёткой.

Остаются старые metadata.json, их ни одна процедура не трогает. Ими управляют свойства таблицы write.metadata.delete-after-commit.enabled и write.metadata.previous-versions-max, Lakekeeper по умолчанию хранит последние 100 версий. Spark для этого не нужен, хватит Trino. По умолчанию он такие свойства не пропускает и отвечает Illegal keys in extra_properties, поэтому сначала их разрешают одной строкой в настройках каталога Trino:

iceberg.allowed-extra-properties=write.metadata.delete-after-commit.enabled,write.metadata.previous-versions-max

После этого свойства задаются обычным SQL, и при создании таблицы, и потом:

ALTER TABLE events SET PROPERTIES extra_properties = MAP(
  ARRAY['write.metadata.delete-after-commit.enabled', 'write.metadata.previous-versions-max'],
  ARRAY['true', '5']);

На стенде я проверил, после восьми вставок в папке metadata/ осталось шесть metadata.json, текущий и пять предыдущих, остальные удалены.

Что стенд не покажет

Это учебная конструкция, и некоторые вещи в ней упрощены специально.

У учётки trino в каталоге доступ ко всему, и проверка прав пользователей доверена ей. На ИБ-ревью это будет первый вопрос, передача токена пользователя в каталог в Trino пока не реализована. Права заканчиваются на уровне таблицы. Маски на колонки и фильтры строк — задача Trino, и с правом select человек, дошедший до каталога напрямую, прочитает таблицу без масок. Значит, каталог и S3 в рабочей системе закрываются от пользователей сетью. Группы из AD в открытую версию Lakekeeper с OpenFGA автоматически не синхронизируются. Каталог — единая точка отказа, и если упал Lakekeeper, Trino отказывает всем. Пароли в репозитории учебные, сертификат самоподписанный.

Последняя глава репозитория перечисляет все упрощения с пояснением, что делать в рабочей системе. По сути, это список вопросов, которые надо закрыть до того, как в лейкхаус придут первые реальные пользователи.

Когда всё это не нужно

Если вся аналитика помещается в пару таблиц обычной базы и источников один-два, лейкхаус с каталогом, OPA и Keycloak — лишняя сложность. Девять сервисов даже на учебном стенде хорошо показывают её масштаб. Конструкция окупается, когда данных и источников становится много, нагрузки разные и появляется смысл развязать хранение и обработку.

Как запустить

git clone https://github.com/maxpiter/iceberg-lab.git
cd iceberg-lab
docker compose up -d

Через несколько минут trino-proxy в docker compose ps станет healthy, и можно подключать DBeaver по первой главе. Самая частая ошибка там — заполнить Username. Его надо оставить пустым, иначе для Trino это запрос на выполнение от чужого имени, и подключение получит отказ.

Если что-то не поднялось или глава расходится с тем, что вы видите на экране, пишите issue в репозиторий. Я прогнал всё с нуля, но у каждого своя машина.

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

Теория, на которую опирается стенд, — в статьях про каталоги Iceberg и про Lakekeeper и Polaris.