Привет, Хабр! Меня зовут Валерий Бабушкин, я CDO МТС Web Services. Если достаточно много занимаешься машинным обучением, то однажды начинаешь говорить про дата-инженерию — как герой, который много сражается со злом и в итоге сам переходит на темную сторону. Вот и моя очередь настала.

На последнем True Tech Day я рассказал, как Apache Iceberg и Apache Parquet позволяют построить современную инфраструктуру для больших данных. В этом материале я расскажу, какие задачи решает каждый инструмент, как они работают в связке, и сравню производительность Hive с Parquet-партициями против Iceberg с Parquet-таблицами.

Эволюция архитектур данных

Если оглянуться назад, то все начиналось с классики: транзакционные базы, OLTP, затем OLAP. Потом кто-то пришел к идее: «А давайте хранить сырые данные в object storage. Дешево и масштабируемо». Так родился Data Lake.

Сначала казалось, что это гениально. Никаких жестких схем и ACID-транзакций. А потом появились проблемы с качеством данных, с governance, с совместимостью. В итоге Data Lake превратилось в Data Swamp:

Рядом все это время жил себе классический DWH. Красивый, аккуратный, со схемами, транзакциями, высокой скоростью, но дорогой. Потому что там все storage и compute связаны, поэтому оплата идет за оба сразу.

В какой-то момент стало ясно: нужен гибрид. Ребята из Databricks выкатили Delta Lake, затем в Apache начали делать Iceberg — open-source-решение, которое должно было объединить лучшее из двух миров: транзакции, эволюцию схемы, дешевое хранение, — и при этом работать и для BI, и для ML-стриминга.

Претензии к Apache Parquet

Конечно, у всей этой красоты есть свои вызовы, особенно при масштабировании. Одно из самых частых узких мест — метаданные. Когда у тебя тысячи partitions и каждый файл нужно как-то обнаружить, начинается медленный discovery. Классические high-level metadata-хранилища под такой нагрузкой начинают сдавать. LIST-операции становятся дорогими.

Второй момент — эволюция схемы. На практике это часто выглядит так: нужно добавить колонку — и начинается перезапись всего датасета. Где-то схему поменяли, где-то забыли, накатили новую версию — и вот уже несовместимость, downtime, и все переставало работать.

Из-за этого часто можно услышать, что настоящего Lakehouse не существует. Потому что ACID есть только на слайдах в презентациях, а на деле: race conditions, неконсистентные чтения, невозможность сделать rollback, перезаписи файлов, экспоненциальный рост стоимости и latency для CDC pipeline.

Но давайте проверим эту критику на бенчмарках с реальными нагрузками, техническими деталями оптимизаций и общую экономику такого решения. А начнем с того, что вспомним, как работает формат данных Apache Parquet.

Parquet: плюсы и минусы

Это колоночный формат хранения. В отличие от строчного, он лучше сжимается, потому что однородные данные идут рядом. С ним эффективнее аналитика: можно читать только нужные колонки (column pruning). И наконец-то нормально используется CPU: с ним хорошо работают векторизованные операции, кэш, locality — все, что долго лежало без дела.

У Parquet есть структура: row group (по умолчанию 128 Мб), внутри — column chunks, потом — pages. В пределах row group хранятся статистики: min, max, null count, что потом пригодится при чтении. Минимальная единица — page, примерно 1 Мб. Страницы бывают разные: data page, dictionary page, index page.

И тут мы можем делать довольно интересные вещи с энкодингом. Например:

  • Dictionary encoding: текстовые значения (Apple, Banana, Cherry) — меняем на ID, экономим до 90% объема;

  • RLE (run-length encoding): если значения идут подряд, как 1, 1, 1, 2, 2, 3, превращаем это в 1×3, 2×2, 3×2;

  • Delta encoding: если числа растут монотонно (1 000, 1 005, 1 010), можно хранить как старт и шаги (1 000, 5, 5...).

А потом на все это накладываются еще и разные алгоритмы. Тут есть варианты: для долгосрочного архивирования можно использовать сжатие в 5–10 раз, для real-time — всего в 2–3. В реальности выбирается некий компромисс — что-то около 3–7 раз.

Также по каждой колонке можно хранить дополнительные статистики — distinct count, null count и так далее. И это дает возможности для предикативной фильтрации. То есть мы сначала смотрим на статистику, и если она не попадает в нужный диапазон, просто пропускаем чтение этого куска данных.

Звучит отлично, да? Но есть нюансы.

Если нам нужна эволюция схемы, все ломается. Добавили колонку? Перезаписываем все файлы. Любое изменение — потенциально катастрофа. Нет ACID, атомарности и race conditions. Нормальные rollbacks невозможны, и неконсистентные чтения — обычное дело. Rollback — забудьте.

Метаданные хранятся прямо в файловой системе. Когда файлов очень много, LIST-операции становятся дорогими, а discovery partition будет очень медленным. Плюс нет никакого версионирования.

В итоге получаем, что Parquet — это отличная компрессия, зрелая экосистема и продвинутый энкодинг. Но при этом:

  • эволюция схемы — боль;

  • ACID нет;

  • дорогие метаоперации;

  • и весьма неэффективные updates/deletes.

И тут нам на помощь приходит Iceberg

Если смотреть на всю архитектуру Lakehouse, то картина вырисовывается примерно такая: внизу — File System Layer. Это может быть S3, Azure, HDFS. Над этим уровнем у нас собственно Storage — то, в каком формате хранятся данные. Чаще всего это Parquet, чуть реже — ORC или Avro.

А вот следующий уровень — самый интересный: Table Format Layer. Здесь как раз и вступает в игру Iceberg. Конечно, если вы живете в Databricks, то, вероятно, используете Delta Lake, но за пределами Databricks Iceberg становится все более популярным выбором.

Выше уже идут движки для работы с данными: Spark, Flink, Trino, Presto, даже Pandas — и все это вполне себе умеет работать с Iceberg.

Как работает Iceberg 

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

Самый верхний слой — это Catalog Layer. Он хранит указатель на актуальную версию таблицы. То есть у нас появляется версионирование и возможность откатываться, делать time-travel и работать с метаданными как с полноценной системой. Catalog может быть любым: AWS Glue, Nessie, Unity Catalog. Также можно использовать алиасы для бизнес-метаданных. Каждое состояние таблицы — это snapshot, у которого есть свой ID, timestamp и связанный с ним manifest list.

Да, в дополнение к трехуровневой архитектуре Iceberg использует двухуровневую систему хранения файлов: manifest files и manifest list.

Допустим, у нас в …/warehouse/sales/data/ лежат Parquet-файлы за день 1, день 2, день 3. По сути это просто файлы: 001.parquet, 002.parquet и так далее. В каждом из них определенное количество записей и свой вес.

На этом уровне создается manifest-file — по одному на partition. Каждый такой манифест хранится в виде Avro-файла. Что же в нем?

Например, в манифесте за 1 января описано:

  • файл 001.parquet;

  • вес такой-то;

  • записей — 1,2 млн;

  • колонка amount: min — 10,5, max — 5 400;

  • колонка customer_id: min — 1 000, max — 9 999.

Такие манифесты — это по сути мини-каталоги, где Iceberg уже заранее посчитал статистики по файлам и колонкам.

А поверх этого строится manifest list — сводка по манифестам. В нем описано, какой partition, сколько строк в каждом, какие границы значений. Например:

  • partition 2023-01-01: 3 файла, 3 миллиона строк;

  • partition 2023-01-02: 2 файла, 2 миллиона строк;

  • partition 2023-01-03: 1 файл, 1,3 миллиона строк.

Зачем такая сложность?

Возьмем запрос count с фильтром по amount. Для его выполнения Iceberg сначала читает manifest-list, откуда сразу понимает, какие манифесты подходят под фильтр. 

Допустим, из всех проходит 23-01-02.avro. Iceberg читает только его. Дальше — внутри манифеста — уже фильтрует сами датафайлы. У нас же там есть min, max, null count. Если файл гарантированно не попадает в диапазон — просто игнорируем. И вот мы получили count, не прочитав ни одной строки из самих данных.

Без Iceberg пришлось бы читать все Parquet-файлы, а это сотни мегабайт и гигабайт данных. С Iceberg читаем только метаданные: а это уже всего лишь килобайты данных. В 100 раз меньше I/O только на этапе планирования запроса. Плюс читаются только нужные Parquet-файлы, никакого бродкаста по всем директориям и листинга.

Iceberg использует Avro для метаданных и Parquet для данных, так как эти форматы идеально дополняют друг друга

Avro — легкий компактный формат, который быстро читается. У него отличная поддержка эволюции схем, что критично для Iceberg, потому что метаданные живут дольше и активно меняются. Там же могут быть десятки тысяч мелких файлов, которые нужно быстро обрабатывать.

А вот данные — это уже Parquet. Потому что он оптимизирован под аналитику: векторизация, сжатие, min-max для predicate pushdown, хорошее хранение чисел, нормальный I/O.

Iceberg использует сильные стороны обоих форматов. Он берет на себя управление схемой, партиционирование, метаданные, версионирование, ACID и наследует от Parquet всю эффективность хранения: бинарное кодирование, векторизацию, сжатие, оптимизацию по типам данных и быстрый predicate pushdown. Avro — отличный формат для метаданных, но даже с максимальной компрессией он дает сжатие порядка 2,8x. Parquet с ZSTD — до 10x.

Также появляется возможность использовать разные форматы хранения. Так, по умолчанию можно брать Iceberg с Parquet, для стриминга применить Avro, для архивов — ORC. А при желании можно смешать их в одной таблице.

Так появляются различные практические паттерны интеграции, например:

  • горячие данные на SSD (последние 30 дней) — Iceberg + Parquet + LZ4, храним на SSD;

  • архив за 7 лет в холодном хранилище — Iceberg + Parquet + GZIP.

Все это работает со Spark, Flink, Trino, Dremio, StarRocks и т. д. без особых ограничений. И в облачной инфраструктуре живет отлично. Но это теория — давайте посмотрим, как это выглядит в реальности.

Сравнение Iceberg и hive на продакшене

Для теста я взял Spark-кластер (от 1 до 30 ядер на ноду), кэш отключен. Таблица большая: 650 миллионов строк на партицию 65 Гб. И тут есть тонкий момент: Iceberg занял 892 Гб, а Hive — 334 Гб.

Почему так?

Это проблема неоптимальных настроек: обе таблицы лежат без zstd-сжатия, а колец-фактор (разбиение на файлы, layout, партиции) у Iceberg оказался менее удачным. Метаданные здесь почти ни при чем — разница от них всего 15 Мб.

Что проверяли

Простые сценарии:

  • SELECT COUNT(*);

  • SELECT ... LIMIT 10_000, LIMIT 100 — проверка коротких запросов;

  • GROUP BY origin — замер группировок.

Результаты

  • Запись: Iceberg сохраняет данные примерно в 1,5–2 раза быстрее, чем Hive.

  • Чтение (COUNT): тут у Iceberg прорыв из-за использования метаданных. COUNT-запросы выполняются мгновенно.

  • LIMIT-запросы: если читаем что-то мелкое (до 10 тысяч строк), Hive быстрее — в 2–3 раза. Он сразу смотрит нужный Parquet-файл. На выборках 100к+ разница стирается. Чем больше данных, тем лучше себя показывает Iceberg. От 500k строк и выше он обходит Hive за счет pruning и эффективного планирования чтения.

  • DISTINCT: Hive стабильнее и быстрее — 14–20 секунд. У Iceberg 30–50 секунд, то есть примерно в 1,5–2 раза медленнее. Hive консистентно обошел его на верификационных запросах.

  • GROUP BY: победителя нет. Все зависит от формата: на Parquet — одинаково, на ORC — у Iceberg чуть медленнее. Формат хранения, как выяснилось, влияет заметно.

  • ACID-операции: по сравнению с Hive, Iceberg может в 3−10 раз быстрее выполнять обновления или удаления. С ним не нужно перезаписывать партиции, и он отлично делает Insert. Это суперпреимущество.

Что из этого следует?

Iceberg отлично подходит:

  • если нужны метаданные, schema discovery, статистики, частые count-операции;

  • для ACID-операций вроде UPDATE, DELETE, MERGE;

  • при аналитических (OLAP) нагрузках, фильтрации по времени;

  • при эволюции схемы;

  • при работе с разными движками;

  • на крупных запросах от 500К строк.

Умеренная выгода идет:

  • при сложной аналитике — там требуется правильно настраивать compaction (хотя где не требуется);

  • в large-scale ETL;

  • при Time Travel Queries, когда нужно смотреть, что было в прошлом.

Если у вас небольшие датасеты, то вся архитектура с Iceberg может оказаться избыточной. Сложность управления, настройка метаданных и поддержка ACID не окупятся на маленьких объемах. В таких случаях разделять storage и compute просто незачем — скорее всего, Lakehouse вам не нужен.

Когда Iceberg лучше Hive

Если смотреть на работу с метаданными, то Iceberg гораздо лучше Hive ACID: он дает ускорение в 100–1 000 раз. По записи данных — выигрыш в 1,5–2 раза. А вот на чтении ситуация зависит от характера запроса: при небольших сканах Iceberg может проигрывать Hive в 2–5 раз, но при росте объемов разница нивелируется, а дальше уже Iceberg выходит вперед.

При этом есть важный момент: все сильно зависит от конфигурации. Если все настроено правильно, overhead составит 1%. Если нет, может улететь в сотни процентов. Ключевой компонент — compaction. Он критичен, потому что без него очень легко получить взрывной рост числа мелких файлов, который убьет перформанс. Причем эффективность compaction зависит еще и от выбранного формата хранения.

У нас в тестах получилось 557 Гб лишнего места. Из них 95% возникло из-за переизбытка файлов (проблема была из-за неверного coalesce), и только 5% — от метадаты. Но даже если бы это нельзя было оптимизировать, то катастрофы бы не случилось. Объем хранилища перестает быть критичным, когда storage и compute у вас разделены. Особенно если вместо классического Hadoop вы используете что-то более современное, с поддержкой Erasure Coding, где избыточность снижается с ×3 до ×2 — и в итоге хранение становится почти бесплатным.

Что важно понимать про Iceberg

Это не серебряная пуля, а инструмент под конкретные задачи. Он отлично заходит в сценариях с heavy metadata, ACID-операциями, большими аналитическими выборками. У нас получился performance crossover на отметке 500К строк: до этого Hive может быть быстрее, после — Iceberg уверенно выигрывает. И да, правильная архитектура решает все. Видел еще один бенчмарк, где просто добавили Iceberg поверх уже готового Parquet — и получили прирост скорости в 6 раз и снижение стоимости в 3 раза. 

Iceberg работает с любыми движками (Spark, Trino, Presto, Impala, Athena, Flink, StarRocks, HashData, Dremio, Snowflake), каталогами (Glue, Hive Metastore, Nessie), форматами хранения (Parquet, Avro, ORC) и storage (S3, ADLS, DCS, HDFS). В принципе, он подойдет для любого облака, потому что в реальности почти все используют S3 или его клон.

Iceberg — это открытый стандарт, и его сила в том, что он хорошо ложится поверх уже существующей инфраструктуры. То есть вам не нужно сносить старое и строить заново — достаточно эволюционно перейти на Iceberg, и вы уже в современном Lakehouse.

Комментарии (2)


  1. orthoxerox
    17.07.2025 09:15

    Обсёрт - это прекрасная описка, конечно.


  1. levge
    17.07.2025 09:15

    Спасибо за статью, а сравнение Iceberg с Delta Lake будет?