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 его удаляет.
Что запускать в рабочей системе и когда
Сами процедуры запускаются одной строкой, сложнее решить, как часто, в каком порядке и с какими сроками хранения их запускать. Я бы отталкивался от такой схемы:
Процедура | Когда | На что смотреть |
|---|---|---|
| каждую ночь, примерно 01:00–04:00, после ночных загрузок и до утренних отчётов. Для потока с коммитом раз в минуту и разбивкой по часам — дополнительно каждый час по прошлому часу | только по партициям, куда уже не пишут, через |
| каждую ночь, сразу после | срок хранения снимков = насколько далеко назад можно откатиться |
| раз в неделю, в ночь на воскресенье | срок хранения заметно больше самой долгой загрузки, не меньше нескольких дней |
| раз в неделю, после | при ежедневном |
Ночь выбрана потому, что 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.

