Lance Architecture
Parquet стал де-факто стандартом колоночного хранения, но его архитектура 2013 года имеет фундаментальные ограничения для современных workload’ов: произвольный доступ к строкам требует полного чтения page, нет встроенного векторного поиска, нет нативного версионирования данных. Lance — это Rust-based, Arrow-native формат, разработанный компанией LanceDB специально для ML/AI workload’ов, где эти ограничения критичны.
Проект развивается с 2022 года. Текущий формат — Lance v2 (апрель 2025, arxiv 2504.15247). В продакшене у LanceDB Cloud на петабайтных масштабах — multimodal GenAI компании, платформы для self-driving cars, e-commerce рекомендательные системы. Экосистема: PyArrow, Pandas, DuckDB, Polars, PyTorch.
В Модуле 07 мы разобрали Apache Arrow — in-memory columnar формат с zero-copy. Lance строится на Arrow как фундаменте: все данные при чтении возвращаются как Arrow RecordBatch без копирования. Это ключевое отличие от Parquet, который при чтении декодирует данные из собственного формата в Arrow.
Почему Parquet недостаточен для ML
Parquet оптимизирован для аналитических scan’ов: sequential read колонок с predicate pushdown. Но ML-pipeline’ы требуют другой паттерн доступа:
Analytics Workload
Аналитические запросы: SELECT columns FROM table WHERE predicate. Последовательное чтение колонок, предикаты отсекают row groups. Parquet идеален для этого паттерна — columnar layout + statistics = эффективный scan.Parquet = идеален
Parquet оптимален: columnar layout минимизирует I/O, dictionary encoding сжимает повторы, page-level statistics обеспечивают пропуск ненужных данных.ML / AI Workload
ML workload: обучение модели требует случайных батчей строк (random mini-batches), vector search для retrieval (nearest neighbor queries), версионирование датасетов для воспроизводимости экспериментов.Parquet = неэффективен
Parquet неэффективен: random access требует чтения + декодирования целого page (64KB) ради одной строки. Нет нативного векторного индекса. Нет нативного версионирования.Три конкретные проблемы Parquet для ML:
-
Random access = full page decode. Parquet хранит данные в pages (~64KB). Чтобы прочитать строку #42, нужно найти row group, загрузить page, декодировать весь page (dictionary → RLE → значение). Для mini-batch из 1000 случайных строк — потенциально 1000 page read + decode. Lance решает это через sliceable encodings — прямой доступ к offset строки без декодирования page.
-
Нет векторного поиска. ML-pipeline’ы требуют nearest-neighbor search по embedding-векторам (CLIP, sentence-transformers). Parquet не хранит векторные индексы — нужен отдельный vector database (Pinecone, Weaviate). Lance встраивает IVF-PQ и HNSW-индексы прямо в data fragments.
-
Нет версионирования. Воспроизводимость ML-экспериментов требует возврата к конкретной версии датасета. Parquet — stateless файл. Lance добавляет append-only transaction log с version snapshots — аналог Delta Lake, но на уровне формата.
Фрагментная модель хранения
Lance организует данные в фрагменты — самодостаточные единицы хранения размером ~64 МБ. Каждый фрагмент содержит подмножество строк датасета:
Lance Dataset (директория)
Lance dataset — директория на файловой системе (или object storage). Содержит data fragments, manifest'ы, transaction log и опционально — векторные индексы. Формат самоописывающий — всё необходимое для чтения внутри директории.Каждый фрагмент содержит:
Fragment (единица хранения)
Fragment — единица I/O и параллелизма. Чтение и запись работают на уровне фрагментов: один reader обрабатывает один фрагмент, параллелизм = количество фрагментов. Фрагменты могут храниться на разных storage devices.Фрагментная модель — прямая аналогия с FileGroup в Hudi (Модуль 13). Но в Hudi FileGroup содержит base file + log files (MOR), а Lance fragment — это колоночные данные + deletion vector + опциональный ANN индекс. Фрагменты в Lance иммутабельны — update создаёт новый deletion vector, не новый data file.
Manifest и Version Log
Lance использует двухуровневую metadata-структуру: manifest описывает текущее состояние датасета, а version log хранит историю всех manifest’ов:
Version Log (append-only)
Version Log — append-only журнал всех версий датасета. Каждая запись содержит: номер версии, timestamp, ссылку на manifest, тип операции (append/delete/merge/create_index). Аналог transaction log в Delta Lake.Manifest — это protobuf-сериализованный файл, содержащий:
Физическая структура на диске
Lance dataset — это директория с фиксированной структурой:
dataset.lance/
Корневая директория dataset'а. На локальном диске или object storage (S3, GCS, Azure Blob). Имя директории = имя dataset'а. Всё содержимое — самодостаточно, нет внешних зависимостей.Deletion Vectors и компакция
Lance использует deletion vectors для soft-delete — аналог deletion vectors в Delta Lake 3.x и Apache Iceberg v2:
DELETE WHERE id = 42
Операция DELETE WHERE user_id = 'abc'. Вместо перезаписи фрагмента — создаётся deletion vector. Стоимость delete = O(1) write (маленький файл), не O(N) rewrite (весь фрагмент).UPDATE id=42 SET name=…
UPDATE WHERE id = 42 SET name = 'new'. В Lance update = delete старой строки + append новой. Deletion vector маркирует строку 42 в старом фрагменте, новое значение добавляется в новый фрагмент.Deletion vectors накапливаются: после 100 delete операций у фрагмента будет 100 строк в deletion vector. Чтение замедляется (фильтрация на каждой строке). Компакция решает проблему: перезаписывает фрагмент без удалённых строк, обнуляя deletion vector.
Компакция
Компакция в Lance — это фоновый процесс, который физически удаляет строки из deletion vectors и оптимизирует layout фрагментов:
До компакции
До компакции: три фрагмента, два из которых имеют deletion vectors. Суммарно 200K 'мёртвых' строк — занимают место на диске и замедляют чтение (проверка deletion vector на каждом read).После компакции
После компакции: живые строки из fragments 0 и 1 записаны в новый фрагмент. Fragment 2 без изменений. Deletion vectors удалены. Новая версия manifest ссылается на новые фрагменты.Transaction Log и Time Travel
Append-only version log обеспечивает MVCC (Multi-Version Concurrency Control): каждая операция (append, delete, merge, create_index) создаёт новую версию, не изменяя предыдущие:
Python API для time travel:
import lance
# Открыть текущую версию
ds = lance.dataset("s3://bucket/embeddings.lance")
print(f"Current: version {ds.version}, {ds.count_rows()} rows")
# Открыть конкретную версию
ds_v1 = lance.dataset("s3://bucket/embeddings.lance", version=1)
print(f"V1: {ds_v1.count_rows()} rows")
# Список всех версий
for v in ds.list_versions():
print(f" v{v['version']}: {v['timestamp']} — {v['metadata']}")
Time travel в Lance — лёгкий: manifest’ы занимают килобайты, data files переиспользуются. В отличие от Delta Lake, где time travel требует хранения всех исторических Parquet-файлов, Lance хранит только manifest’ы + deletion vectors. Очистка старых версий: ds.cleanup_old_versions(older_than=timedelta(days=30)).
Сравнение с Lakehouse-форматами
Lance решает задачи, которые lakehouse-форматы решают иначе или не решают вовсе:
Write Path и Read Path
Append (новые данные)
Append: добавление новых данных. Самая частая операция в ML-pipeline (новые embedding'и, новые обучающие примеры). Стоимость: O(new_data) — пропорциональна только объёму новых данных.Update (изменение строк)
Update: изменение существующих строк. В Lance update = soft delete старой строки + append новой. Два шага: создать deletion vector + записать новые данные.Full Scan (аналитика)
Scan: чтение всех строк с опциональным предикатом. Аналогично Parquet scan, но с Arrow-native zero-copy. Параллелизм = количество фрагментов.Random Access (ML batch)
Random access: чтение конкретных строк по row ID. Ключевое преимущество Lance: O(1) доступ к строке внутри фрагмента через sliceable encodings. Для ML mini-batch: 1000 случайных строк за ~1ms.Конкурентный доступ
Lance поддерживает optimistic concurrency control — несколько writer’ов могут работать с dataset’ом одновременно:
Conflict resolution в Lance автоматический для большинства случаев: если writer’ы работают с разными фрагментами — конфликта нет. Конфликт возникает только при одновременном update одних и тех же строк. В ML-pipeline’ах это редкость: обычно один writer append’ит новые данные, другой обновляет metadata или создаёт индексы.
Итоги
Lance — это не “ещё один Parquet”. Это формат, спроектированный для другого набора задач:
В следующем уроке мы разберём формат Lance v2 — “контейнерный формат” без встроенной системы типов и фиксированных кодировок, который позволяет адаптировать физический layout данных под конкретные workload’ы.