Learning Platform
Глоссарий Troubleshooting
Урок 16.03 · 40 мин
Продвинутый
LanceRandom AccessVector SearchIVF-PQHNSWMultimodalLanceDBPython APIZero-CopyArrow Integration

Lance ML Features

В предыдущих уроках мы разобрали архитектуру Lance и формат v2. Теперь — зачем это всё: ML/AI возможности, ради которых Lance создавался. Три ключевых feature: 100x random access для training data loading, встроенный векторный поиск для RAG/retrieval, и мультимодальные данные (изображения, видео, 3D) в одном датасете.

NOTE

Lance — не замена Parquet для аналитики. Parquet остаётся лучшим выбором для SQL-запросов, OLAP, BI. Lance оптимизирован для ML-специфичных workload’ов: random sampling, vector search, dataset versioning, multimodal storage. Два формата дополняют друг друга.

100x Random Access

ML-модели обучаются на случайных mini-batch’ах: DataLoader выбирает N случайных строк из датасета, формирует batch, отправляет на GPU. Качество обучения зависит от истинной случайности выборки — если DataLoader всегда берёт последовательные строки, модель переобучается на локальных паттернах.

ML DataLoader: random mini-batch

DataLoader(shuffle=True, batch_size=256)

PyTorch DataLoader с shuffle=True: на каждой эпохе случайно выбирает batch_size строк из датасета. Для ImageNet (14M изображений, batch_size=256) — 54,000 random batches per epoch. Каждый batch: 256 случайных позиций.
Parquet BackendДля каждой случайной строки: найти row group → загрузить page (64KB) → декодировать page → извлечь строку. 256 строк × 64KB/page = потенциально 16MB I/O + decode. Реальность: строки попадают в разные pages → I/O scatter.
WorkaroundНа практике Parquet-based DataLoaders используют prefetch + buffer: загружают row groups последовательно, буферизуют, выбирают random из буфера. Это НЕ истинная случайность — это 'случайность внутри окна'.
Lance BackendДля каждой случайной строки: row_id → fragment + offset → mini-block read. 256 строк: 256 × mini-block read (десятки байт). Нет page decode, нет I/O scatter по большим блокам.
True RandomИстинная случайность: каждая строка доступна за O(1) независимо от позиции. Нет buffer window, нет compromise. DataLoader получает именно те строки, которые запросил.

Sliceable Encodings

Механизм 100x ускорения — sliceable encodings: кодировки, которые позволяют прочитать произвольное значение без декодирования предшествующих:

Sliceable Encoding: прямой доступ по offset

Fixed-Width Slicing

Fixed-width types (int32, int64, float32, float64, fixed-size binary): byte offset вычисляется арифметически. Для int64 строки #42: offset = 42 × 8 = 336 байт от начала chunk. Один seek + read 8 байт.
Формулаbyte_offset = row_index × value_width. Для int64 (8 байт): строка 42 → offset 336. Для float32 (4 байта): строка 42 → offset 168. Нет промежуточных вычислений, нет зависимости от предыдущих значений.

Variable-Width Slicing

Variable-width types (strings, binary, lists): дополнительный массив offset'ов. offsets[i] = начало значения #i в data buffer. Два чтения: offsets[42] → start, offsets[43] → end, data[start:end] → значение.
Offsets ArrayМассив offsets: int64 array, по одному элементу на строку + 1 (sentinel). offsets[42] = 15680, offsets[43] = 15720. Значение строки 42: data[15680:15720] = 40 байт. Два I/O: offset read + data read.

Benchmark: random access 10,000 строк из 100M-строчного датасета:

Benchmark: Random Access 10K rows / 100M dataset
Parquet10K random rows: ~10K page loads × 64KB decode = ~640MB decode overhead. Реальность с caching: ~50-200ms. Без cache (cold): ~1-2 секунды. Bottleneck: page decode, не I/O.
Lance v210K random rows: 10K × mini-block read = ~10K × 8-64 байт = ~640KB I/O. Нет decode overhead. Latency: ~1-5ms (warm), ~10-20ms (cold). 100x ускорение за счёт отсутствия page decode.
Ускорение50-100x ускорение на random access. Разница растёт с размером page в Parquet (128KB page → 200x). Для ML training: разница между GPU idle time и full utilization.

Lance — единственный колоночный формат со встроенным векторным поиском. Индексы хранятся рядом с данными в тех же фрагментах:

Vector Index: IVF-PQ и HNSW

IVF-PQ Index

IVF-PQ (Inverted File Index + Product Quantization): двухступенчатый индекс. 1) IVF разбивает пространство на K кластеров (centroids). 2) PQ квантует вектора внутри кластеров (128-dim → 16 sub-quantizers × 8 bits = 16 байт per vector). Быстро, компактно, масштабируется до миллиардов.
IVF: кластерыK-Means кластеризация: пространство embedding'ов разбивается на K кластеров (обычно 256-4096). Каждый вектор приписан к ближайшему centroid. При поиске: сравнить query с centroids → выбрать top-n кластеров → искать только в них.
PQ: квантованиеProduct Quantization: вектор 128-dim разбивается на 16 sub-vectors по 8 dim. Каждый sub-vector квантуется в 8-bit codebook (256 centroids per sub-space). Итого: 128-dim float32 (512 байт) → 16 байт. 32x compression. Approximate distance: сумма расстояний по sub-vectors.

HNSW Index

HNSW (Hierarchical Navigable Small World): граф-based индекс. Многоуровневая структура: верхние уровни — sparse граф для грубого поиска, нижний уровень — dense граф для точного. Высокая точность recall, но больше памяти чем IVF-PQ.
Multi-level GraphУровни: Level 0 (все вектора, dense граф), Level 1 (подмножество, sparser), Level 2 (ещё меньше). Поиск: начать с верхнего уровня → greedy спуск через ближайших соседей → refine на Level 0. Log-вероятность вставки на верхние уровни.
Greedy SearchНачать с entry point на верхнем уровне. На каждом уровне: greedy переход к ближайшему соседу. Спуск на нижний уровень. На Level 0: explore ef_search ближайших кандидатов. Recall >95% при ef_search=100.

Фрагментный ANN индекс

Уникальная особенность Lance: векторные индексы привязаны к фрагментам, а не к глобальному dataset’у:

Per-Fragment ANN Index

Глобальный ANN (Pinecone, Milvus)

Глобальный индекс (Pinecone, Milvus): один индекс на весь dataset. При append новых данных — reindex всего dataset. При delete — rebuild или tombstone. Стоимость index maintenance растёт линейно с dataset size.
Проблема1M vectors: index build ~10 секунд. 1B vectors: index build ~часы. Каждый append: rebuild или incremental update (сложно для IVF-PQ). Delete: tombstone → fragmentation → periodic rebuild.
Lance: per-fragment ANN
Fragment 01M vectors. Собственный IVF-PQ index: 256 centroids, PQ16. Index build: ~2 секунды. Index size: ~16MB. Полностью локальный — не зависит от других фрагментов.
Fragment 1Ещё 1M vectors. Собственный IVF-PQ index. При append — строится только для нового фрагмента, старые не затрагиваются. O(fragment_size) вместо O(dataset_size).
Fragment 2Новый фрагмент (только что записан). Index строится асинхронно. До готовности — brute-force поиск (exact search). После build — переключение на ANN.
vector search query
Merge ResultsПоиск: query → каждый фрагмент возвращает top-k кандидатов → global merge → top-k финальных результатов. Аналогия: distributed search как в Elasticsearch (per-shard → merge). Параллелизм = количество фрагментов.
TIP

Per-fragment индексация — архитектурное решение, аналогичное per-segment индексам в Lucene/Elasticsearch. Преимущество: append = O(new_fragment), не O(dataset). Недостаток: поиск по N фрагментам медленнее, чем по одному глобальному индексу. На практике Lance компенсирует это параллельным поиском и merging.

Мультимодальные данные

Lance нативно поддерживает хранение разнородных данных в одном dataset’е — текст, изображения, видео, 3D модели рядом с embedding-векторами:

Multimodal Dataset: один Lance dataset

Multimodal Dataset

Мультимодальный ML датасет: каждая строка содержит изображение (JPEG bytes), текстовое описание (string), CLIP embedding (float32[512]), метаданные (struct). Все модальности — в одном dataset'е, не в разных системах.
image (binary)Изображение как binary blob (JPEG/PNG bytes). Хранится в variable-width encoding с offsets array. При чтении: raw bytes → PIL Image / torchvision decode. Нет double serialization — bytes хранятся as-is.
caption (string)Текстовое описание: UTF-8 строка. Dictionary encoding для повторяющихся (шаблонных) caption'ов, plain для уникальных. Используется для text-image alignment в multimodal моделях.
embedding (float32[512])CLIP embedding: fixed-size list of float32, 512 dimensions. Хранится как FixedSizeList в Arrow schema. ANN index строится на этой колонке для vector search.
metadata (struct)Структурированные метаданные: source, timestamp, license, dimensions, file_size. Arrow Struct type — nested columns. Predicate pushdown на metadata полях: WHERE source = 'flickr' AND width > 1024.

Сравнение с альтернативами:

Multimodal Storage: Lance vs альтернативы
АспектКлючевые различия в хранении мультимодальных данных.
LanceВсе модальности в одном dataset: columnar format для metadata + blob storage для images/videos + vector index для embeddings.
Parquet + S3Типичная архитектура: Parquet для metadata, S3 для media files, отдельный vector DB для embeddings. Три системы, три consistency models.
Vector DBPinecone/Weaviate/Milvus: хранят вектора + metadata. Не хранят raw media. Нет columnar analytics на metadata.
Data Co-locationХранятся ли все данные (metadata, media, embeddings) в одном месте или в разных системах?
Всё в одном dataset: metadata, images, embeddings. Одна consistency model. Один version history. Atomic operations на весь dataset.
Три системы: Parquet (metadata), S3 (images), VectorDB (embeddings). Consistency: нужна координация. Version mismatch возможен.
Вектора + metadata. Raw media — вне системы (URL ссылки). Неполная co-location.
Version ControlВозможность вернуться к конкретной версии всего мультимодального датасета.
Нативный: version log покрывает все модальности. 'Dataset v42' = конкретный набор images + captions + embeddings. Atomic.
Ручной: нужно версионировать Parquet, S3 paths и vector DB отдельно. Нет гарантии consistency между версиями разных систем.
Частичный: vector DB может версионировать вектора, но не raw media. Metadata versioning — через collections.
Training AccessЭффективность чтения данных в ML training loop (DataLoader).
Один read: row #42 возвращает image bytes + caption + embedding + metadata. Всё из одного source. Zero-copy Arrow → PyTorch tensor.
Три reads: (1) Parquet → metadata + S3 path, (2) S3 GET → image bytes, (3) VectorDB → embedding. Network round-trips. Latency: 3x.
Один read → vector + metadata. Но image → отдельный fetch по URL. Два reads.

Zero-Copy Arrow Integration

Все операции чтения в Lance возвращают данные как Arrow RecordBatch без копирования:

Zero-Copy Pipeline: Lance → Arrow → ML Framework

Lance Data File (disk)

Lance data file: данные хранятся в формате, бинарно совместимом с Arrow layout (для fixed-width типов). При чтении: memory-map файл → указатель на данные в памяти. Нет deserialize + copy.
memory-map / read

Arrow RecordBatch (zero-copy)

Arrow RecordBatch: набор колонок в Arrow формате. Каждая колонка — Arrow Array (pointer + length + null bitmap). Для fixed-width: pointer указывает прямо на данные из Lance file (zero-copy). Для variable-width: один copy (offsets recalculation).
PandasRecordBatch.to_pandas(): Arrow → Pandas DataFrame. Для numeric колонок: zero-copy (numpy array backed by Arrow buffer). Для string: copy (Arrow UTF-8 → Python objects). Метод: ds.to_pandas().
DuckDBDuckDB может читать Arrow RecordBatch напрямую через Arrow C Data Interface. Нет copy, нет serialization. SQL на Lance данных: duckdb.arrow(table).query('SELECT ...').
PyTorchArrow buffer → torch.Tensor: для numeric данных — torch.from_numpy(arrow_array.to_numpy(zero_copy_only=True)). Zero-copy chain: disk → Arrow → numpy → torch. GPU transfer — единственный copy.
PolarsPolars нативно использует Arrow в качестве memory model (polars Series = Arrow ChunkedArray). Lance → Arrow → Polars: полный zero-copy pipeline.

LanceDB: Database Layer

LanceDB — это vector database, построенная на Lance формате. Она добавляет database-level абстракции поверх raw format:

LanceDB Architecture

LanceDB (serverless, embedded)

LanceDB: serverless vector database. Нет отдельного сервера — библиотека встраивается в приложение (embedded, как SQLite). Данные на S3/GCS/local disk. Python SDK: pip install lancedb.
TablesТаблицы поверх Lance datasets. Каждая таблица = Lance dataset с schema. CRUD операции: insert, update, delete, search. SQL-like filtering поверх columnar данных.
Vector SearchВстроенный vector search: ANN queries с metric (L2, cosine, dot). Hybrid search: vector similarity + metadata filter. Reranking: combine ANN score + BM25 text score.
Full-Text SearchBM25-based full-text search с inverted index. Tantivy backend (Rust). Комбинируется с vector search для hybrid retrieval (RAG pipeline).
StorageВсё хранится как Lance datasets: таблицы, индексы, metadata. Local disk, S3, GCS, Azure Blob. Нет отдельного сервера — LanceDB процесс встраивается в приложение. Scaling: LanceDB Cloud для multi-tenant production.

Python API Walkthrough

Практический пример: создание мультимодального датасета с vector search:

import lance
import pyarrow as pa
import numpy as np

# === Создание датасета ===
schema = pa.schema([
 pa.field("id", pa.int64()),
 pa.field("text", pa.utf8()),
 pa.field("embedding", pa.list_(pa.float32(), 384)), # sentence-transformers
 pa.field("category", pa.utf8()),
 pa.field("created_at", pa.timestamp("ms")),
])

# Данные как Arrow Table
n_rows = 100_000
table = pa.table({
 "id": range(n_rows),
 "text": [f"Document {i}: ..." for i in range(n_rows)],
 "embedding": [np.random.randn(384).astype(np.float32).tolist() for _ in range(n_rows)],
 "category": np.random.choice(["tech", "science", "business"], n_rows).tolist(),
 "created_at": pa.array([datetime.now()] * n_rows, type=pa.timestamp("ms")),
})

# Записать Lance dataset
ds = lance.write_dataset(table, "s3://bucket/documents.lance")
print(f"Written: {ds.count_rows()} rows, {len(ds.get_fragments())} fragments")
# === Чтение и random access ===
ds = lance.dataset("s3://bucket/documents.lance")

# Full scan → Arrow Table
full = ds.to_table() # zero-copy Arrow

# Column projection
texts = ds.to_table(columns=["id", "text"])

# Filter (predicate pushdown)
tech_docs = ds.to_table(
 columns=["id", "text", "embedding"],
 filter="category = 'tech'"
)

# Random access по row IDs
batch = ds.take([42, 7891, 3, 55000]) # O(1) per row
print(f"Random batch: {batch.num_rows} rows")

# Pandas integration (zero-copy для numeric)
df = ds.to_pandas()
# === Vector Search ===
# Создание ANN индекса
ds.create_index(
 "embedding",
 index_type="IVF_PQ",
 num_partitions=256, # IVF clusters
 num_sub_vectors=16, # PQ sub-vectors
)

# kNN search
query_vector = np.random.randn(384).astype(np.float32)
results = ds.search(query_vector) \
 .limit(10) \
 .nprobes(20) \
 .to_pandas()

# Hybrid: vector search + metadata filter
results = ds.search(query_vector) \
 .where("category = 'tech'") \
 .limit(10) \
 .to_pandas()

print(results[["id", "text", "_distance"]])
# === Versioning ===
# Append новых данных
new_data = pa.table({"id": [100_000], "text": ["New doc"], ...})
lance.write_dataset(new_data, "s3://bucket/documents.lance", mode="append")

# Time travel
ds_v1 = lance.dataset("s3://bucket/documents.lance", version=1)
print(f"V1: {ds_v1.count_rows()} rows")

# Delete + компакция
ds.delete("category = 'spam'")
ds.compact_files() # Физическое удаление
ds.cleanup_old_versions() # Удалить старые manifest'ы
WARNING

create_index() блокирует dataset на время построения индекса. Для больших датасетов (100M+ vectors) процесс занимает минуты-часы. LanceDB Cloud решает это через background index building + incremental updates. В open-source Lance: планируйте index build вне production read path.

RAG Pipeline с LanceDB

Типичный RAG (Retrieval-Augmented Generation) pipeline с Lance:

RAG Pipeline: LanceDB + LLM

User Query

Пользовательский запрос: 'Как работает attention mechanism в transformers?'. Нужно найти релевантные документы в knowledge base и передать их LLM как контекст.
encode
Embedding ModelSentence-transformer (e.g., all-MiniLM-L6-v2): query → 384-dim float32 vector. Latency: ~5ms на CPU. Вектор используется для ANN search в LanceDB.
search
LanceDB SearchHybrid search: ANN (vector similarity) + BM25 (keyword match). Reranking: RRF (Reciprocal Rank Fusion) для объединения scores. Результат: top-5 документов с scores.
context
LLM (Claude, GPT)Контекст: top-5 документов + original query → LLM prompt. LLM генерирует ответ на основе предоставленного контекста. Грounding: ответ основан на конкретных документах, не на general knowledge.

Итоги

Lance для ML — не просто “быстрый формат”. Это платформа для ML данных:

Lance ML Features: summary
Random Access100x vs Parquet. Sliceable encodings: O(1) доступ к строке. True random sampling для DataLoader — нет compromise на случайность. GPU utilization: минимальное idle time.
Vector SearchВстроенный IVF-PQ / HNSW. Per-fragment индексы: append = O(new). Hybrid search: vector + metadata filter. Sub-ms latency. Нет отдельного vector DB.
MultimodalImages, text, vectors, metadata — один dataset. Version control на все модальности. Один read per training sample. Zero-copy → PyTorch/Pandas/Polars.
EcosystemPython-first: lance + lancedb на PyPI. Arrow-native: zero-copy в любой Arrow-aware фреймворк. LanceDB: embedded vector DB. LanceDB Cloud: managed production.

В следующем уроке мы перейдём к Vortex — другому Rust-based формату, который атакует Parquet с противоположной стороны: не random access, а compressed compute — выполнение запросов без декомпрессии данных.

Проверьте понимание

Результат: 0 из 0
Аналитический
Вопрос 1 из 4. PyTorch DataLoader тренирует модель на 100M-строчном датасете. batch_size=256, shuffle=True. С Parquet backend каждый batch занимает ~100ms (page decode). С Lance backend — ~1ms. Epoch = 390K batches. Какой суммарный выигрыш по времени на одном epoch, и что является root cause разницы?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5