Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Vector Search

RiffDB vector search is a named, bounded RiffQL read over authoritative application-supplied embeddings. The application computes embeddings; RiffDB validates and stores them through compiled commands, tracks when they become stale, and maintains a derived per-organization search projection.

The application never selects an index implementation. A contract may declare an approximate-search threshold and recall floor; RiffDB then uses exact search for small admitted partitions. Above the threshold it can reuse a matching first-party HNSW graph; a cold or unavailable graph falls back to exact search.

The scan ceiling also bounds materialization of the organization’s merged candidate rows, before policy admission and distance calculations. Oversized partitions return a typed scan-budget refusal. Scalar range filters exclude null cells; null does not satisfy an upper-bound comparison.

Declare the vector field

entity Document {
    key (organization_id: uuid, document_id: uuid)
    field title: string<256>
    field body: string<4096>
    vector_field embedding(1536, cosine, (title, body), staleness_slo 60,
        model "text-embedding-v1", current_version "2026-08-21",
        replay_age_seconds 86400, replay_bytes 1073741824,
        replay_backlog 100000,
        ann_threshold 256, recall_target_bps 9500)
}

ann_threshold and recall_target_bps are an atomic pair. In this example, search is exact at 256 or fewer admitted rows in the requested organization and may use a matching warm graph above 256. Cold queries remain exact while a bounded background build may prepare reuse. The declared 9,500 basis points means recall@K must be at least 0.95 against exact search at the same frontier. Query text and parameters cannot lower that target or force a tier.

The source fields (title, body) declare what makes the embedding stale. The model identity and current version are also contract facts; a caller cannot write a differently identified embedding and claim it is current.

Write embeddings through a command

command SetDocumentEmbedding {
    input request_id: string<128>
    input organization_id: uuid
    input document_id: uuid
    input embedding: vector<1536>
    input submitted_model: string<256>
    input submitted_version: string<256>

    idempotency_key request_id
    mutate Document(organization_id, document_id) as document else Missing {}
    embed document.embedding = embedding
        from (submitted_model, submitted_version)
    return Embedded { document: document }
}

The embed effect commits the vector and its model/version/write-sequence evidence atomically with the command outcome, provenance, and idempotency record. Generated clients provide a field-specific constructor using the contract-sealed model identity and version. Generic set cannot mutate a vector field.

Changing a declared source field without a later embedding command makes that row stale. Generated clients expose bounded symbolic stale-row and model-version inspection when the application role declares the corresponding authority.

Read through a named RiffQL operation

query SimilarDocuments(
    $organization_id: Document.organization_id,
    $query_vector: Document.embedding,
    $k: Limit<499>,
) {
    source projected Document.embedding
    freshness causal inherit_session_commit true max_wait_ms 500

    many documents from Document
        where organization_id == $organization_id
        nearest(embedding, $query_vector, $k)

    return Found {
        documents: documents {
            document_id
            title
        }
    }
    outcomes Found
}

The organization equality is mandatory, K is positive and compiler-bounded, and the source must name the same vector field used by nearest. Row policy is applied on every query to the complete candidate set before vector validation, graph lookup or construction, scoring, ranking, or K selection. Another organization and a policy-denied row cannot shape graph statistics or results.

freshness causal is the normal read-after-write choice. available permits a currently published frontier, while duration-bounded freshness uses trusted commit timestamps. Every successful projected response reports its frontier.

Correct typed failures

  • RDB-QP002 means the query lacks a provable single partition. Add the declared organization equality; do not fetch broadly and filter in the app.
  • RDB-PROJECTION-0104 means the named query lacks the exact compiler-owned source projected Entity.vector_field identity. Refresh and deploy the query module rather than selecting a storage index manually.
  • RDB-PROJECTION-0103 means no published generation satisfies the declared freshness floor. Retry according to the typed outcome; do not silently weaken freshness.
  • A vector dimension or model/version mismatch is an input error naming the symbolic field or input path. Regenerate or use the field-specific generated constructor; do not encode vectors as bytes.
  • Building, rebuilding, or detached projection state is unavailable until a complete checkpoint is published. RiffDB never serves a partial graph or an old generation as if it were current.

POC bounds

Production nearest reads admit at most 500 rows from one organization when ann_threshold is omitted, or up to 65,536 rows per organization when ann_threshold and recall_target_bps are declared. Search is exact at or below ann_threshold; above it, both primary and follower providers can reuse warm in-memory HNSW graphs for an unchanged admitted population at the same generation and frontier, falling back to exact search while a graph is cold. Model, policy, or capability changes cannot reuse a mismatched graph. Graphs reopen cold and are not part of backup identity; persistent incremental graphs remain deferred. The current projected-vector endpoint does not support continuation cursors.

For the full declarations and query grammar, see Contract Authoring and RiffQL.