Blob Storage#
Lance blob v2 columns store large binary payloads out of line β queries return small descriptors, not bytes. Callers read actual blob data through dedicated fetch APIs after obtaining stable row IDs from a query.
Blob columns require:
- Lance file format β₯ v2.2 (auto-enforced at table creation)
- Stable row IDs enabled on the table
Blob APIs are local-table only; remote (LanceDB Cloud) tables return NotImplementedError.
Schema and Column Type#
A blob v2 column is an Arrow extension type (lance.blob.v2) backed by a 4-field struct :
| Child field | Arrow type | Nullable |
|---|---|---|
data | large_binary | yes |
uri | utf8 | yes |
position | uint64 | yes |
size | uint64 | yes |
Python β declare with lancedb.schema.blob(name, nullable=True), which returns a pa.Field of type BlobType.
Rust β declare with lancedb::blob(name, nullable), which wraps lance::blob::blob_field.
The extension marker ARROW:extension:name = "lance.blob.v2" on the field metadata distinguishes v2 blobs from legacy v1 blobs (lance-encoding:blob).
File Format Requirements (data_storage_version)#
ensure_blob_storage_version() is called during table creation and automatically bumps data_storage_version to at least LanceFileVersion::V2_2 whenever the schema contains any blob v2 column. If the caller specifies a higher version (e.g., V2_3), that is preserved.
The tests in blob.rs document this behavior explicitly:
- Default params β bumped to V2_2
- Explicit V2_0 β overridden to V2_2
- Explicit V2_3 β kept at V2_3
- No blob columns β no-op
The descriptor struct layout (Struct<data, uri> with the lance.blob.v2 extension marker) cannot be safely stored in format versions below V2_2.
Null vs. Empty Value Semantics#
All three fetch APIs preserve the following invariant :
- Null blob β null slot in the result (
Nonein Python,nullin Arrow) - Valid empty blob (zero bytes, non-null) β
b""in the result
This is enforced in the Rust layer via take_blobs_aligned and take_blob_ranges_aligned, which match on payload.data: Some(data) appends the value; None appends a null slot.
In take_blob_files_aligned, null rows propagate as None entries in the returned Vec<Option<BlobFile>>. Lance v10.0.0-beta.3 fixed a potential panic here by switching from .unwrap() to .flatten() when advancing the handle iterator over nullable results.
Read APIs#
Three materialization strategies are available. All require row IDs obtained via with_row_id(True) on a query. Results preserve input length and order.
| Method | Returns | Use case |
|---|---|---|
fetch_blobs(column, row_ids) | pa.LargeBinaryArray | Eager full-payload read for small blobs |
fetch_blob_ranges(column, requests) | pa.LargeBinaryArray | Batched sub-range reads β each request is (row_id, offset, length) |
fetch_blob_files(column, row_ids) | list[Optional[BlobFile]] | Lazy seekable handles for large payloads; null rows return None |
BlobFile is a RawIOBase subclass with seek(), tell(), size(), read_range(offset, length), and async aread().
fetch_blob_ranges is the right choice when you have an index of clip offsets inside videos or byte windows into large files β all ranges are submitted as a single planned Lance operation.
Rust entry points are take_blobs_aligned, take_blob_ranges_aligned, and take_blob_files_aligned in rust/lancedb/src/blob.rs.
Write Path#
On write, LanceDB automatically coerces Binary / LargeBinary Arrow inputs into the blob struct layout. Three input forms are accepted :
- Raw binary β lands in the
datachild field - Pre-built struct with
dataorurichild already populated - Null β written as a null descriptor row
The coercion logic lives in rust/lancedb/src/table/datafusion/blob_coerce.rs. Users do not need to construct the descriptor struct manually.
Nested Blobs and Dotted Paths#
Blob columns can be nested inside structs or lists. The fetch APIs address them with dotted paths (e.g., "info.blob").
Schema traversal is recursive and handles Struct, List, LargeList, and FixedSizeList.
Example from tests: a schema with info.blob (blob nested inside struct field info) β blob_column_names() returns ["info.blob"].
Limitations and Error Handling#
- Local-only: All blob fetch APIs are unsupported on remote (LanceDB Cloud) tables.
- Legacy v1 columns rejected: Columns with
lance-encoding:blobmetadata returnInvalidInputwith a migration hint pointing tolance.blob.v2. - Invalid row IDs: If
fetch_blobs/fetch_blob_rangesreceives row IDs not present in the table, it returnsInvalidInput. - Non-blob column name: Passing a non-blob column name to any fetch API returns
InvalidInput.
Key source files:
rust/lancedb/src/blob.rsβ Rust blob v2 definitions,ensure_blob_storage_version, aligned fetch helperspython/python/lancedb/_blob.pyβ PythonBlobFileand projection helperspython/python/lancedb/schema.pyβBlobType,blob(), schema traversalpython/python/lancedb/table.pyβ Abstractfetch_blobs,fetch_blob_ranges,fetch_blob_filesinterfacesrust/lancedb/src/table/datafusion/blob_coerce.rsβ Write-path coercion