Nimble Architecture
Parquet проектировался в 2013 году для аналитических workload’ов: десятки колонок, SQL-запросы, Hadoop MapReduce. Внутри Meta масштабы другие: AI/ML training таблицы с 10 000+ колонок (features, embeddings, метрики), где Parquet’s Thrift-based metadata становится узким горлом, а фиксированный набор кодировок ограничивает эффективность для новых типов данных.
Nimble — это C++ формат хранения, разработанный Meta (facebookincubator/nimble) как ответ на эти ограничения. Открыт в 2024 году (до этого — внутренний проект «Alpha»). Тесно интегрирован с Velox — execution engine Meta, используемым в Presto, Spark и внутренних ML pipeline’ах.
Nimble находится на ранней стадии. Нет Python-биндингов, нет standalone-библиотеки — требуется сборка с Velox (gtest, glog, folly, abseil). Это не формат для самостоятельного использования, а компонент экосистемы Meta. Ценность для курса — архитектурные решения и философия дизайна.
В Модуле 15 мы разобрали Lance и Vortex — два Rust-based формата с Arrow-native архитектурой. Nimble идёт другим путём: C++, привязка к Velox, философия «одна библиотека — один формат». В уроке 05 мы сравним все четыре подхода.
Мотивация: широкие таблицы Meta
Meta обрабатывает таблицы, структура которых радикально отличается от типичных OLAP-датасетов:
OLAP-таблица
Типичная аналитическая таблица: 10-100 колонок, строковые/числовые типы, SQL-запросы с GROUP BY и WHERE. Metadata — десятки записей. Parquet справляется идеально.Meta AI/ML таблица
AI/ML training таблица Meta: 10 000+ колонок — feature vectors, embeddings, модельные предсказания, AB-test метрики. Каждый эксперимент добавляет колонки, а старые не удаляются.Конкретные проблемы Parquet для таблиц Meta:
-
Metadata bottleneck. Parquet использует Apache Thrift для сериализации метаданных. Thrift требует полного парсинга всего сообщения — нельзя прочитать metadata для одной колонки из 10 000 без десериализации всего блока. При 10K+ колонок это 50-200ms на row group только на metadata.
-
Фиксированные кодировки. Parquet поддерживает ~6 кодировок (PLAIN, DICTIONARY, RLE, DELTA_BINARY_PACKED, BYTE_STREAM_SPLIT, DELTA_LENGTH_BYTE_ARRAY). Добавление новой кодировки требует обновления спецификации, всех реализаций (Java, C++, Rust), совместимости. Meta хочет добавлять кодировки быстро.
-
Непредсказуемое потребление памяти. Parquet декодирует данные потоково (stream) — объём памяти зависит от размера page и паттерна кодирования. Для scheduler’а ML-pipeline, который должен разместить N декодеров на GPU/CPU cores, непредсказуемость — проблема.
Структура файла Nimble
Nimble организует данные в stripes (аналог row groups в Parquet), но с принципиально другой организацией внутри:
Footer размещён в конце файла — это позволяет writer’у записывать данные последовательно и финализировать метаданные одним write. Reader сначала читает footer (seek to end → read size → read footer), затем навигирует к нужным stripes.
Stripes, Streams и Blocks
Внутри каждого stripe данные организованы в streams. Каждая колонка представлена одним или несколькими streams (для nested типов — дополнительные streams для definition/repetition levels):
Stripe
Stripe — единица параллельного декодирования. Содержит подмножество строк для всех колонок. Stripe footer хранит offset'ы streams для O(1) навигации к конкретной колонке.Stripe Footer (FlatBuffers)
Stripe Footer: FlatBuffers metadata для данного stripe. Содержит offset и размер каждого stream, encoding информацию, statistics (min/max/null count) для каждой колонки. Позволяет skip ненужных streams без чтения.Ключевое отличие от Parquet: блочная организация вместо страничной. В Parquet данные в page — это stream (произвольной длины), декодирование которого может потребовать непредсказуемый объём памяти. В Nimble block — это атомарная единица с известным размером decoded output.
FlatBuffers vs Thrift: доступ к метаданным
Выбор формата метаданных — одно из ключевых архитектурных решений Nimble:
Thrift (Parquet)
Apache Thrift: формат сериализации Meta (изначально разработан в Facebook). Используется в Parquet для file metadata, row group metadata, page headers. Основной недостаток: требует полной десериализации.FlatBuffers (Nimble)
Google FlatBuffers: zero-copy сериализация. Данные читаются прямо из буфера без десериализации — доступ по offset'ам. Размер overhead: ~4 bytes per field (vtable offset). Используется в Nimble для всех метаданных.FlatBuffers изобретены в Google для игровой индустрии (низкая latency), но идеально подходят для wide-schema метаданных. Nimble — не единственный формат с FlatBuffers: Vortex (Модуль 15) и F3 (урок 03) тоже используют FlatBuffers. Это тренд нового поколения форматов — уход от Thrift/Protobuf к zero-copy сериализации.
Block Encoding vs Stream Encoding
Главное архитектурное решение Nimble — блочное кодирование вместо потокового. Это напрямую влияет на предсказуемость потребления памяти:
Stream Encoding (Parquet)
Stream Encoding (Parquet): данные в page кодируются как непрерывный поток. Декодер читает byte за byte, пока не восстановит все значения. Объём decoded output зависит от данных — dictionary size, run lengths, delta magnitudes.Непредсказуемый memory footprint
Результат: scheduler'у ML-pipeline приходится перевыделять память (overallocate) или рисковать OOM. Для GPU kernels, где memory budget фиксирован, непредсказуемость критична.Block Encoding (Nimble)
Block Encoding (Nimble): данные разбиты на blocks фиксированного размера. Каждый block header содержит: encoding type, encoded size, decoded size, value count. Декодер знает объём памяти ДО начала декодирования.Предсказуемый memory footprint
Результат: scheduler точно знает memory budget каждого block. Можно распределить N blocks на M cores с гарантией отсутствия OOM. Идеально для GPU kernel scheduling.Предсказуемость памяти — не теоретическое преимущество. В инфраструктуре Meta, где тысячи ML-training jobs конкурируют за GPU/CPU ресурсы, scheduler должен точно знать memory requirements каждого task. Block encoding делает decoding планируемым — как CPU instruction с известным latency.
Encoding Pipeline
Nimble использует рекурсивный, композируемый encoding pipeline. В отличие от Parquet, где кодировка выбирается на уровне page и применяется целиком, Nimble составляет цепочки кодировок:
Raw Column Data
Raw данные: column values перед кодированием. Pipeline анализирует данные и строит дерево кодировок — от высокоуровневых (structural) к низкоуровневым (compression).Encoded Block
Encoded Block: результат трёхуровневого pipeline. Metadata блока хранит полное дерево кодировок для decode: 'Nullable → Dictionary → RLE → ZSTD'. Декодирование — обратный обход дерева.Рекурсивность означает, что каждый уровень может создавать sub-streams, которые проходят следующие уровни. Пример: Nullable(Dictionary(Delta(ZSTD(data)))) — nullable колонка с dictionary encoding, где indices закодированы delta + сжаты ZSTD.
Интеграция с Velox
Nimble не существует как standalone библиотека — он тесно интегрирован с Velox, execution engine Meta:
Presto
Presto: distributed SQL engine Meta. Использует Velox как execution backend. Nimble — один из форматов чтения/записи через Velox connectors.Spark (Gluten)
Spark: Apache Spark с Velox plugin (Gluten). Nimble файлы читаются через тот же Velox connector.ML Pipelines
ML Pipelines: внутренние Meta training pipelines. Nimble оптимизирован для wide-table reads при feature extraction.Velox Engine
Velox: C++ vectorized execution engine Meta. Columnar in-memory format (Velox Vectors). Connectors для разных storage formats: Parquet, ORC, Nimble. Nimble — нативный формат Velox, без conversion overhead.HDFS / S3 / Local FS
Storage: HDFS, S3, локальная файловая система. Nimble файлы — обычные файлы, без специальных требований к storage layer.Tight coupling с Velox даёт преимущество в performance (нет conversion layer), но создаёт серьёзное ограничение: без Velox нельзя читать Nimble файлы. Нет standalone decoder, нет Python bindings, нет Java/Rust реализации.
Сравнение с Parquet: структура метаданных
Stripe Layout: потоки данных
Каждый stripe содержит параллельные streams для каждой колонки. Для nested типов (struct, list, map) создаётся иерархия streams:
Иерархия streams зеркалирует иерархию типов — в отличие от Parquet’s Dremel encoding, где nested types «размазываются» в плоские колонки с repetition/definition levels. Подход Nimble более прямолинейный: каждый уровень вложенности = отдельный stream. Это упрощает декодирование и делает его параллелизуемым.
Параллельное декодирование
Блочная архитектура Nimble обеспечивает предсказуемый параллелизм:
Velox Scheduler
Scheduler (Velox): получает запрос на N колонок из stripe. Читает stripe footer (FlatBuffers) → знает offset, size, decoded size каждого block в каждом stream. Может точно спланировать параллельное декодирование.Core 0: stream A, blocks 0-3
Core 0: декодирует blocks 0-3 stream column_A. Memory budget: sum(decoded_size[0..3]). Известен заранее из stripe footer.Core 1: stream B, blocks 0-3
Core 1: декодирует blocks 0-3 stream column_B параллельно. Нет зависимостей от Core 0. Полная изоляция.Core 2: stream A, blocks 4-7
Core 2: декодирует blocks 4-7 stream column_A. Один stream может быть разбит на несколько cores — blocks независимы.Velox Vectors (decoded)
Velox Vectors: decoded колонки в in-memory columnar формате Velox. Готовы для vectorized execution (filter, aggregate, join). Нет дополнительной конвертации.Ключевое отличие от Parquet: в Parquet scheduler не может точно узнать decoded size page до начала декодирования (зависит от dictionary size, RLE runs, null count). В Nimble block header содержит decoded_size — scheduler знает memory budget до первого байта декодирования.
Сравнение с форматами курса
Итоги
Nimble — это целенаправленный ответ на конкретные проблемы Meta:
-
FlatBuffers metadata решает bottleneck Thrift при 10K+ колонках: O(1) random access вместо O(N) десериализации.
-
Block encoding обеспечивает предсказуемое потребление памяти: decoded size известен до начала декодирования. Scheduler точно планирует параллельное декодирование на N cores.
-
Рекурсивный encoding pipeline позволяет комбинировать кодировки произвольной глубины без изменения спецификации.
-
Tight coupling с Velox даёт performance (нет conversion layer), но ограничивает экосистему.
В следующем уроке мы разберём философию дизайна Nimble — подход «библиотека как спецификация», почему Meta осознанно отказывается от multi-implementation модели Parquet, и какие уроки из фрагментации Parquet-экосистемы привели к такому решению.