Lance ML Features
В предыдущих уроках мы разобрали архитектуру Lance и формат v2. Теперь — зачем это всё: ML/AI возможности, ради которых Lance создавался. Три ключевых feature: 100x random access для training data loading, встроенный векторный поиск для RAG/retrieval, и мультимодальные данные (изображения, видео, 3D) в одном датасете.
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 всегда берёт последовательные строки, модель переобучается на локальных паттернах.
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 случайных позиций.Sliceable Encodings
Механизм 100x ускорения — sliceable encodings: кодировки, которые позволяют прочитать произвольное значение без декодирования предшествующих:
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 байт.Variable-Width Slicing
Variable-width types (strings, binary, lists): дополнительный массив offset'ов. offsets[i] = начало значения #i в data buffer. Два чтения: offsets[42] → start, offsets[43] → end, data[start:end] → значение.Benchmark: random access 10,000 строк из 100M-строчного датасета:
Встроенный Vector Search
Lance — единственный колоночный формат со встроенным векторным поиском. Индексы хранятся рядом с данными в тех же фрагментах:
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). Быстро, компактно, масштабируется до миллиардов.HNSW Index
HNSW (Hierarchical Navigable Small World): граф-based индекс. Многоуровневая структура: верхние уровни — sparse граф для грубого поиска, нижний уровень — dense граф для точного. Высокая точность recall, но больше памяти чем IVF-PQ.Фрагментный ANN индекс
Уникальная особенность Lance: векторные индексы привязаны к фрагментам, а не к глобальному dataset’у:
Глобальный ANN (Pinecone, Milvus)
Глобальный индекс (Pinecone, Milvus): один индекс на весь dataset. При append новых данных — reindex всего dataset. При delete — rebuild или tombstone. Стоимость index maintenance растёт линейно с dataset size.Per-fragment индексация — архитектурное решение, аналогичное per-segment индексам в Lucene/Elasticsearch. Преимущество: append = O(new_fragment), не O(dataset). Недостаток: поиск по N фрагментам медленнее, чем по одному глобальному индексу. На практике Lance компенсирует это параллельным поиском и merging.
Мультимодальные данные
Lance нативно поддерживает хранение разнородных данных в одном dataset’е — текст, изображения, видео, 3D модели рядом с embedding-векторами:
Multimodal Dataset
Мультимодальный ML датасет: каждая строка содержит изображение (JPEG bytes), текстовое описание (string), CLIP embedding (float32[512]), метаданные (struct). Все модальности — в одном dataset'е, не в разных системах.Сравнение с альтернативами:
Zero-Copy Arrow Integration
Все операции чтения в Lance возвращают данные как Arrow RecordBatch без копирования:
Lance Data File (disk)
Lance data file: данные хранятся в формате, бинарно совместимом с Arrow layout (для fixed-width типов). При чтении: memory-map файл → указатель на данные в памяти. Нет deserialize + copy.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).LanceDB: Database Layer
LanceDB — это vector database, построенная на Lance формате. Она добавляет database-level абстракции поверх raw format:
LanceDB (serverless, embedded)
LanceDB: serverless vector database. Нет отдельного сервера — библиотека встраивается в приложение (embedded, как SQLite). Данные на S3/GCS/local disk. Python SDK: pip install lancedb.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'ы
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:
User Query
Пользовательский запрос: 'Как работает attention mechanism в transformers?'. Нужно найти релевантные документы в knowledge base и передать их LLM как контекст.Итоги
Lance для ML — не просто “быстрый формат”. Это платформа для ML данных:
В следующем уроке мы перейдём к Vortex — другому Rust-based формату, который атакует Parquet с противоположной стороны: не random access, а compressed compute — выполнение запросов без декомпрессии данных.