Vector index overview for Virtuoso

Learn about Virtuoso's native HNSW-backed vector index for semantic similarity search over text, including architecture, embedding models, both SQL and SPARQL query surfaces, and a glossary of key terms.

Virtuoso Open Source includes a native vector index, backed by HNSW (Hierarchical Navigable Small World), for semantic similarity search over text. Unlike external vector databases, which require you to manage embeddings separately, Virtuoso's server handles the full pipeline: embedding generation, vector storage, approximate nearest-neighbor search, and index maintenance—all integrated with the SQL and SPARQL query engines you already use.

This overview covers:

  • What is semantic search: Why vector search differs from keyword search
  • Architecture: How embeddings, the HNSW graph, and query surfaces fit together
  • Query surfaces: SQL (X_SIMILAR, VECTOR_DISTANCE), SPARQL (bif:vector_search), and search over RDF literals (knnSimilar)
  • Embedding models: Local ONNX models and remote HTTP providers
  • Automatic maintenance: How inserts, updates, and deletes keep the index current
  • Glossary: Key terms and concepts

Traditional keyword search matches documents by finding exact or stemmed word overlaps. It fails when the query and the relevant document use different vocabulary — "how to reset my password" won't match a document titled "credential recovery procedure."

Semantic search solves this by converting text into embedding vectors — high-dimensional float arrays where texts with similar meaning are close together in vector space. Instead of matching words, it matches meaning.

Virtuoso's vector index makes this work end-to-end inside the database:

  1. You store text in a table column (SQL) or as RDF literals (SPARQL)
  2. The server converts that text into embedding vectors using a bound model
  3. You query with plain text — the server embeds your query and finds the nearest neighbors in the vector space
  4. Results are ranked by semantic similarity, not keyword overlap

How it works

Virtuoso's vector index follows a text-in, text-out design. You never handle raw vectors for normal queries — you pass text, and the server embeds it using the index's bound model.

The search pipeline

Output
Query text ──→ Embedding model ──→ Query vector
                                        │
                                        ▼
Data text  ──→ Embedding model ──→ Data vectors ──→ HNSW graph
                                        │                   │
                                        │                   ▼
                                        │           Nearest neighbor search
                                        │                   │
                                        ▼                   ▼
                                    Hidden table ←── Ranked results ──→ SQL/SPARQL output
  1. You create a vector index on a text column (SQL) or an RDF graph/predicate pair (SPARQL), binding it to an embedding model.
  2. The server embeds your data — each row or RDF resource's text is converted to a float vector and stored in an HNSW graph.
  3. You query with text — the server embeds your query text with the same model, searches the HNSW graph for nearest neighbors, and returns ranked results.

Key components

ComponentDescription
Embedding modelConverts text to float vectors. Local ONNX (the bundled granite model) or remote HTTP (OpenAI, Ollama, LM Studio, any OpenAI-compatible service)
HNSW graphNative page-level storage for approximate nearest neighbor (ANN) search. A graph built in one pass (build = 'bulk' or a fast rebuild) uses a packed page format, several nodes per page (~1.3× amplification at 384 dimensions); a graph grown incrementally uses one page per node until it is rebuilt
Hidden embedding tablePer-index managed table storing embeddings, source hashes, and metadata
Maintenance queueTransactional enqueue of DML changes; drained by a scheduled worker or an explicit drain call
Query rewriteSQL compiler rewrites X_SIMILAR/VECTOR_DISTANCE to use the HNSW index
vonnx pluginServer extension providing ONNX Runtime integration and embedding BIFs

Architecture layers

The vector index is built in three layers:

  1. Plugin layer — The vonnx plugin (vonnx.so) provides ONNX Runtime integration, running each loaded model in helper processes on the server host. It exposes BIFs like ONNX_EMBED_TEXT(handle, text) and ONNX_LOAD_MODEL(path, options). HTTP providers are called from SQL procedures (DB.DBA.ONNX_EMBED_TEXT_FOR_MODEL and friends) that share the same model registry.
  2. SQL layer — The vector index is implemented in the database engine as native database pages (a dedicated page type for HNSW graph nodes). The SQL compiler rewrites X_SIMILAR and VECTOR_DISTANCE calls to use the HNSW index when one exists on the referenced column. A maintenance queue (VEC_SQL_QUEUE) handles deferred embedding updates.
  3. SPARQL layer — RDF-specific catalog tables (SPARQL_VEC_INDEX, SPARQL_VEC_INDEX_TRIGGER), SPARQL DDL (CREATE/REFRESH/DROP VECTOR INDEX), the bif:vector_search magic predicate, and a separate maintenance queue (SPARQL_VEC_QUEUE) with async triggers on RDF quad inserts/updates/deletes. For general (KEY/EMBEDDING/WHERE) selectors, the parser extracts (graph, predicate) dependency pairs at create time so that only relevant triple changes enqueue the changed subject.

An RDF literal index combines the first two: it is an ordinary SQL vector index on DB.DBA.RDF_OBJ, the table that holds RDF literal values, and is searched with knnSimilar as a condition on the object of any triple pattern — see Vector search over RDF literals.


Supported configurations

The vector index is supported on single-server Virtuoso Open Source only. It has not been validated on a clustered (multi-node) instance, and cluster deployment is not supported.

Within a single server:

  • Concurrent writers on one index are serialized by a per-index writer lock, so parallel inserts/updates/deletes to the same index are safe. Writers on different indexes still run in parallel, and readers (searches) are never blocked by writers. See Index maintenance for the full contract.
  • Durability is crash-safe: each index tracks a durability epoch, and an index found inconsistent with its stored embeddings after a crash refuses to serve searches until it is rebuilt. See Durability and recovery.

Query surfaces

SQL vector index

For relational tables with a text column. You create an index with CREATE VECTOR INDEX, insert data, and query with X_SIMILAR or VECTOR_DISTANCE.

SQL
CREATE VECTOR INDEX DOCS_VEC ON DB.DBA.DOCS (content)
USING 'granite-384-local'
WITH (dimensions = 384, distance_metric = 'cosine');

SELECT TOP 10 id, content,
       X_SIMILAR(content, 'reset password') AS score,
       VECTOR_DISTANCE(content, 'reset password') AS distance
FROM DB.DBA.DOCS
ORDER BY VECTOR_DISTANCE(content, 'reset password');

Key concepts:

  • X_SIMILAR returns a similarity score in [0, 1] (higher = more similar)
  • VECTOR_DISTANCE returns a raw distance (lower = more similar)
  • When in WHERE or ORDER BY, the compiler rewrites these to use the HNSW index
  • The query text is embedded with the index's bound model when the query runs

For the full SQL guide, see SQL vector index how-to.

SPARQL vector index

For RDF data indexed by graph and predicate, or by any graph pattern. You create an index with SPARQL DDL and query with the bif:vector_search magic predicate.

SQL
SPARQL DEFINE input:storage ""
CREATE VECTOR INDEX <urn:docs:idx>
  ON GRAPH <urn:docs:g>
  for predicate <urn:doc:text>
  USING MODEL 'granite-384-local'
  WITH (dimensions = 384, distance = 'cosine')
;

-- Embed the triples the graph already holds; until this runs, the index is empty
SPARQL DEFINE input:storage ""
refresh VECTOR INDEX <urn:docs:idx>
;

SPARQL DEFINE input:storage ""
SELECT ?doc ?score WHERE {
  ?doc bif:vector_search "reset password"
  option (INDEX <urn:docs:idx>, score ?score, LIMIT 10) .
}
ORDER BY DESC(?score)
;

Key concepts:

  • bif:vector_search is a SPARQL magic predicate, not a regular triple match
  • The query lowers to VEC_INDEX_SEARCH_ROWS in the generated SQL
  • Graph permission checks (ACLs) filter results the user can't read
  • Supports shorthand selectors (graph + predicate) and general selectors (graph pattern)
  • One query can search several indexes: option (index (<a>, <b>)) or option (index *)

For the full SPARQL guide, see SPARQL vector index how-to.

Search over RDF literals (knnSimilar)

For semantic search over literal values wherever they occur, without choosing a graph or predicate in advance. One call indexes the string literals of DB.DBA.RDF_OBJ; knnSimilar then constrains the object of a triple pattern, much as bif:contains does for free-text search:

SQL
DB.DBA.RDF_OBJ_VEC_INDEX_CREATE ('urn:faq:literals', 'granite-384-local');

SPARQL
SELECT ?s ?sim
WHERE {
  ?s ?p ?o .
  ?o bif:knnSimilar "how do I back up the database" option (LIMIT 5, score ?sim) .
}
ORDER BY DESC (?sim)
;

For the full guide, see Vector search over RDF literals.

When to use which surface

CriterionSQLSPARQL vector indexRDF literal index
Data formatRelational table with text columnRDF resources with text selected by graph/predicate or a patternRDF string literals, wherever they occur
Query languageSQL SELECT with X_SIMILAR/VECTOR_DISTANCESPARQL SELECT with bif:vector_searchbif:knnSimilar in SPARQL, knnSimilar (O, …) in SQL
Index creationCREATE VECTOR INDEX ... ON table (col)sparql create vector index <iri> ...DB.DBA.RDF_OBJ_VEC_INDEX_CREATE (...)
ResultTable rowsSubjects (the selector's key)Triples whose object matches
PermissionsDBA creates; SQL table grants, plus read access to the model registry, to search (details)RDF graph ACLsRDF graph ACLs on the matched triples
MaintenanceTriggers + VEC_SQL_QUEUE, drained explicitly or by a scheduled workerREFRESH VECTOR INDEX, or async triggers + SPARQL_VEC_QUEUE drained explicitlyNew literals logged, embedded by RDF_OBJ_VEC_LOG_DRAIN or the scheduled SQL worker
Best forApplication data in tablesLinked Data, Knowledge GraphsExploring a graph by meaning, hybrid graph + vector queries

Embedding models

Virtuoso supports two categories of embedding models, both managed through the DB.DBA.ONNX_MODELS catalog table.

Local ONNX models

The bundled granite-384-local model (IBM granite-embedding-30m-english, 384 dimensions) is auto-provisioned at startup. It runs on the host through the vonnx plugin and ONNX Runtime, in helper processes the server starts — no network calls, no API keys, no per-query cost.

PropertyValue
ModelIBM granite-embedding-30m-english (English, 512-token input)
Dimensions384
Model size60,458,629 bytes (weights stored as bf16, run as fp32)
LatencySee Performance characteristics
CostFree (runs on the host)
PrivacyData stays on the host

Remote HTTP embedding providers

You can register external embedding APIs through a unified OpenAI-compatible interface:

Provider_provider valueDefault endpointKey required
OpenAIopenaihttps://api.openai.com/v1/embeddingsYes
Ollamaollamahttp://127.0.0.1:11434/v1/embeddingsNo
LM Studiolmstudiohttp://127.0.0.1:1234/v1/embeddingsNo
OpenAI-compatibleopenai_compatibleYou supply itAs needed

For the full model guide, see Embedding models.


HNSW and similarity metrics

The vector index uses the HNSW (Hierarchical Navigable Small World) algorithm for approximate nearest neighbor search. HNSW builds a multi-layer graph where each node is a vector, and edges connect similar vectors. Search starts from the top layer (sparse, fast traversal) and descends to the bottom layer (dense, accurate matching).

Build-time parameters

ParameterDefaultDescription
hnsw_m16Max neighbors per node per layer. Higher = better recall, more memory
ef_construction200Search depth during index build. Higher = better graph quality, slower build
reduce'none'Matryoshka dimension reduction: 'none' or 'truncate' (SQL and SPARQL)

For accuracy-focused builds with high-dimensional models (e.g. 1536-dim OpenAI embeddings), use hnsw_m = 32 and ef_construction = 400. For memory-constrained deployments, use reduce = 'truncate' to reduce vector dimensions (e.g. 1536 → 256) with MRL-compatible models.

Query-time parameters

ParameterWhereDefaultDescription
kX_SIMILAR 3rd arg (SQL)the query's TOP nNumber of HNSW candidates to retrieve; with neither k nor TOP, rows are scored exactly instead
ef_searchbif:vector_search option (SPARQL)100HNSW search beam width (at least limit)
kknnSimilar 3rd/4th arg (SQL), limit option (SPARQL)100Number of nearest literals, which is also the search breadth

Distance metrics

MetricKeywordDistance rangeBest for
Cosinecosine[0, 2]Text embeddings (default)
L2 (Euclidean)l2[0, inf)Image embeddings, magnitude-sensitive
L1 (Manhattan)l1[0, inf)Robust to outliers

For the full parameter guide, see Index parameters. For metric details, see Similarity metrics.


Automatic maintenance

After index creation, base-table writes keep the index current without running embedding generation in the write transaction:

  • INSERT / UPDATE / DELETE triggers enqueue work into a transactional queue
  • Hash-skip detects unchanged text and skips re-embedding (via SOURCE_HASH)
  • An explicit drain call, or a scheduled worker for SQL indexes, processes the queue and updates the HNSW graph

New rows become searchable after the next queue drain. A SQL CREATE VECTOR INDEX backfills existing rows inline, so create-then-search works immediately; a SPARQL index is populated by REFRESH VECTOR INDEX.

For the full maintenance guide, see Index maintenance.


Prerequisites

  • Virtuoso Open Source built with --enable-onnxruntime — see Build Virtuoso with ONNX support
  • The vonnx plugin loaded in virtuoso.ini
  • An embedding model registered in DB.DBA.ONNX_MODELS (auto-provisioned for the bundled granite-384-local model)

Glossary

TermDefinition
EmbeddingA vector (array of floats) that represents the semantic meaning of text. Similar texts produce similar vectors.
Embedding modelA machine learning model that converts text into embedding vectors. Virtuoso supports local ONNX models and remote HTTP providers.
HNSWHierarchical Navigable Small World — a graph-based algorithm for approximate nearest neighbor search. Provides sub-linear search time with high recall.
ANNApproximate Nearest Neighbor — finding the closest vectors without exhaustive comparison. Trades a small accuracy loss for large speed gains.
Vector indexA database index structure that stores embedding vectors and supports similarity search. In Virtuoso, backed by HNSW with native page-level storage.
Cosine similarityA distance metric that measures the angle between two vectors, ignoring magnitude. The default and recommended metric for text embeddings.
RecallThe fraction of true nearest neighbors that an ANN search finds. 100% recall = exact search. HNSW typically achieves 95%+ with tuned parameters.
hnsw_mHNSW build parameter controlling max neighbors per node per layer. Higher = denser graph = better recall = more memory.
ef_constructionHNSW build parameter controlling search depth during index construction. Higher = better graph quality = slower build.
ef_searchHNSW query parameter controlling search beam width. Higher = better recall = slower query. In SQL, X_SIMILAR's k (or the query's TOP n) plays this role.
Managed tableA hidden table created per vector index, storing embeddings, source hashes, and metadata. Named <index_name>_vec_embeddings for SQL indexes.
Maintenance queueA transactional queue that records DML changes (insert/update/delete) for deferred embedding updates. Drained by a worker or manual call.
Query rewriteThe SQL compiler's transformation of X_SIMILAR/VECTOR_DISTANCE calls into HNSW index lookups when a vector index exists on the column.
bif:vector_searchA SPARQL magic predicate that performs vector similarity search. Lowered to VEC_INDEX_SEARCH_ROWS in the generated SQL.
Bound modelThe embedding model associated with a vector index. All embeddings for that index — both data and queries — use this model.
Source hashAn MD5 hash of the original text, stored in the managed table. Used to skip re-embedding when text hasn't changed during an UPDATE.
Matryoshka reductionTruncating an embedding vector to its first N dimensions, enabled by reduce = 'truncate'. Only works with MRL-trained models (e.g. OpenAI text-embedding-3). Reduces memory and speeds up search at a small cost in recall.
MRLMatryoshka Representation Learning — a training technique where the first N dimensions of a vector form a valid lower-dimensional embedding. Named after Russian nesting dolls.

Documentation map

DocumentTypeAudience
QuickstartQuickstartNew users — get searching in 5 minutes
FAQ semantic search tutorialTutorialAll — end-to-end example with OpenLink FAQ data
SQL vector index how-toHow-toSQL developers — build, complete SQL surface
SPARQL vector index how-toHow-toSPARQL developers — complete SPARQL surface
Vector search over RDF literalsHow-toSPARQL and SQL developers — knnSimilar over every string literal
Naming an indexHow-toSPARQL index owners — labels, descriptions, who may set them
VVec consoleHow-toOperators — manage indexes and models from a browser
Embedding modelsReferenceAll — model registration, local and remote providers
Index parametersReferenceAll — HNSW tuning, build and query parameters
Similarity metricsConceptAll — cosine, L2, L1 explained with score interpretation
Index maintenanceHow-toOperators — queue, worker, monitoring, best practices
API referenceReferenceDevelopers — complete function and catalog reference
TroubleshootingReferenceAll — symptoms, causes, fixes, health checks

Next steps

Reference:

External:

NextQuickstart: Vector index for Virtuoso