Core Concepts — Vector
The vector data engine has no service of its own. A vector collection is a table with a vector<n> column on the tabled data plane, and everything below is grounded in that plane’s SQL — the type vocabulary in dodil-tables/crates/vector/src/vocab.rs, the KNN operators in crates/tables-sql/src/knn.rs, and the ANN index DDL in crates/tables-sql/src/ddl.rs.
Collections are created two ways, both landing on the same table. You either write vectors yourself into a
vector<n>column (BYO — a plainCREATE TABLE), or a pipeline auto-embeds ingested objects into one. The auto-embed path —CreateVectorPipeline,VectorConfig, the embedding templates — is a control-plane concern and lives in Pipelines → Vector Collections. This page is about the data model underneath both.
The wire vocabulary is wider than the plane supports. The proto and pgvector surfaces name more embedding types, sparse modes and distance metrics than are actually reachable today. The working subset is small and the callouts below cite each limit: the
floatembedding type,vector<n>andhalfvec<n>storage (abit<n>column stores but only exact-scans), and thecosine/euclidean/dotmetrics. Everything else — Hamming / Jaccard / BM25 metrics, learned-sparse (SPLADE) modes, binary ANN — is vocabulary the runtime does not honour. Full per-wire divergences: Vector → Wire Compatibility.
The column types
The plane has three vector-family column types. All three take a mandatory dimension — bare vector is rejected, because pgvector’s dim-less form defers typing to first insert and this plane does not.
| Type | Storage | What it holds |
|---|---|---|
vector<n> | Arrow List<Float32> | Dense f32 embeddings — the common case |
halfvec<n> | Arrow Float16 (~half the on-disk size) | Dense f16 embeddings |
bit<n> | packed bytes (n is the bit count) | Binary vectors |
vector<n> and VECTOR(n) are the same type — short aliases normalize and the type passes through uppercased. _vec_bench on rebuild-smoke, for example, describes its embedding column as emb vector<64> and its integer PK id as long.
SQL
CREATE TABLE ada_embeddings (
id VARCHAR PRIMARY KEY,
content VARCHAR,
embedding vector(1536)
);Distance metrics — operators and opclasses
KNN is plain SQL. Three pgvector operators are recognised by the dialect, each lowering to a distance function (knn.rs:40-46):
| Operator | Metric | Lowers to | Opclass (for the ANN index) |
|---|---|---|---|
<=> | cosine | list_cosine_distance | vector_cosine_ops |
<-> | L2 / Euclidean | list_distance | vector_l2_ops |
<#> | inner product (dot) | list_negative_inner_product | vector_ip_ops |
-- verified live against _vec_bench (emb vector<64>): ascending distances, closest first
SELECT id, tag, emb <=> '[0.1, 0.1, …]' AS distance
FROM _vec_bench
ORDER BY distance
LIMIT 5;
score/distanceis a distance, not a similarity — lower is closer. Every path returns the raw metric distance: the SQL operators, the typedTablesVector.QueryVectorsfacet (VectorMatch.score), and the Qdrant wire, which passes the facet’s score through verbatim. A stock Qdrant client will happily print it as if higher were better. Sort ascending.
The plane’s honest metric surface is three values.
Metricondodil.tables.v1is exactlyCOSINE,EUCLIDEAN,DOT_PRODUCT. There is no Hamming, Jaccard or BM25 metric on the query facet,QueryVectorscarries no metadata filter or namespace, andDeleteVectorshas one filter arm (idsorall: true, never both).
The ANN index
An optional approximate-nearest-neighbour index is a pgvector CREATE INDEX (ddl.rs:190-230):
CREATE INDEX ON ada_embeddings USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);- Methods:
USING hnswandUSING ivfflat, plus the quantized kinds they promote to. Any otherUSINGis ignored — the column stays exact-scan. - Opclasses:
vector_l2_ops,vector_ip_ops,vector_cosine_ops— and nothing else. Omitting the opclass defaults to L2. An unknown opclass is a cleanUnsupportederror naming the three valid ones. - Params:
WITH (m = …, ef_construction = …)for HNSW,WITH (lists = …)for IVFFlat. - Scalar quantization is opt-in via
WITH (quantization = …):hnsw+f16ori8promotes to a quantized HNSW;ivfflat+sq8promotes toIvfSq8. Any other (method, quantization) pair is a clean error, never a silent drop.
Which kind to ask for, what a build guarantees, and the serving knobs — including why
hnsw_sq_i8on an inner-product column is refused at DDL — are on ANN Indexes. How the engine keeps recall and build memory bounded as a collection grows into the millions is on Vector at Scale.
The plane’s storage coverage is narrower than pgvector’s full opclass set.
bit<n>’sbit_hamming_ops/bit_jaccard_opsopclasses are rejected atCREATE INDEXon the vector index path — only the threevector_*_opsclasses resolve. Binary HNSW is a hard error in the f32 engine. If you declare abit<n>column, expect exact scan, not an ANN index.
Reaching the data — one table, many wires
The same vector<n> table answers on every wire the tabled plane exposes:
| Wire | KNN call | Notes |
|---|---|---|
| Postgres | ORDER BY col <=> '[…]' LIMIT k | pg.uk-lon-1.dodil.io:5432, sslmode=require |
| Qdrant | client.search(collection_name, query_vector, limit) | vector column hardcoded to vector; metrics Cosine / Euclid / Dot only |
| Pinecone | index.query(vector, top_k) | table name from X-Dodil-Index header, not the SDK index name |
| Typed gRPC / REST | TablesVector.QueryVectors · POST /v1/vector/:table/query | dodil data vsearch rides this facet |
The Qdrant wire adapter is narrower than the SQL surface.
Distance::from_wireaccepts onlyCosine,Euclid(or the longEuclidean) andDot;Manhattanor anything else is a loud400(crates/adapter/src/qdrant.rs:59-68). It maps those to the same three opclasses / operators above.
Endpoints and credentials: Connect & wire adapters. The full BYO write/read/delete flow: Pipelines → External Collection.
The managed search route — dispatch model
POST /:bucket/search/vector is the one control-plane surface that stays with the vector engine. It is not raw KNN — it is a thin dispatcher over the per-modality *_embedding_search Scriptum templates, for pipeline-created collections (it reads store_entities rows with kind='vector'). There is no Search RPC; the route’s authz borrows PipelineService/ListPipelines.
- Resolve the target collections — explicit
collectionNameswin (names that match nothing →NOT_FOUND); else a file query auto-filters to its modality; else every collection in the bucket. - Drop collections that are not
activeor have no bound search pipeline — the latter are skipped with an entry in the response’swarnings[]. - Group survivors by
(embed_model, dimensions, embedding_type)— collections with differentembed_modelnever co-mingle. - One search-template thread per group embeds the query once and fans out to its members; hybrid + rerank are owned by the template, not the caller.
- RRF-merge (k=60) the groups’ ranked lists into the final
topK. A single group is a pass-through — no fusion applied.
topK defaults to 10 and is floored at 10 when <= 0. searchMode, rerank, minScore and rerankText are parsed but feed no K3-side logic (services/search/search.rs:67-70). Full contract: API Reference → Search.
A pre-embedded
vectorquery on this route returnsUNIMPLEMENTED— “pre-embedded vector search retired with Milvus (#83) — pending the tabled KNN read-path” (services/search/search.rs:194-198). KNN by a vector you already hold is a data-plane operation: SQL, a stock Qdrant / Pinecone client, ordodil data vsearch --vector.
Hybrid is not live on the plane.
dodil-tables/crates/vector-sparse/is a learned-sparse index (SPLADE / BGE-M3 term weights) ranked by inner product — no IDF, nok1/b, and no binary depends on it, so it is unreachable.QueryVectorsRequesthas no sparse, hybrid or filter field. Searches on the plane are dense-only; whether a search template does anything more is Scriptum’s business and not visible from these repos. See Recipes → Hybrid + Rerank.
Where collection creation lives now
Creating a collection — whether template-driven auto-embed or the manual mode — is a Pipelines concern. This engine documents the data model and the search route; the control-plane lifecycle is over there:
- Pipelines → Vector Collections (API) —
CreateVectorPipeline,VectorConfig, the schema-resolution rules, and where the retiredEMBEDDING_SOURCE_EXTERNAL/AddVectorCollectionmode went - Pipelines → Templates → the vector catalog — the
*_embedding_indextemplates - Pipelines → Concepts — the pipeline / destination / rule model
See also
- Quickstart — a pure-SQL vector table + KNN in 5 minutes
- API Reference → Search — the managed search route contract
- Connect & wire adapters — Qdrant / Pinecone / Postgres endpoints + credentials
- ANN Indexes — choosing an index kind, the build guarantee, tuning and residency
- Vector at Scale — the guarantees that hold at millions of embeddings
- Wire Compatibility — the full Qdrant / Pinecone divergence list
- Pipelines — auto-embed-on-ingest collections