Troubleshooting the Virtuoso vector index

Resolve common Virtuoso vector index problems — missing plugins, model load failures, index creation errors, empty or wrong query results, HTTP provider issues, SPARQL-specific errors, and performance problems. Use the health check sequence and symptom-based troubleshooting entries to diagnose and fix issues fast.

This guide covers common issues with Virtuoso vector indexes, from build problems to query errors. When something goes wrong, start with the health check sequence below to narrow down the fault domain, then find your specific symptom in the category that matches.

This reference covers:

  • Overview: How to approach vector index troubleshooting
  • Health check: A quick diagnostic sequence to run before deep troubleshooting
  • Plugin and model issues: Missing ONNX BIFs, model load failures, bootstrap errors
  • Index creation issues: Duplicate names, dimension mismatches, storage limits
  • Query issues: Empty results, NULL distances, stale data, full scans, text parameters, access denied
  • Build performance issues: Long refreshes, slow updates to bulk-built indexes, remote-provider builds
  • HTTP provider issues: Authentication, rate limits, latency
  • SPARQL-specific issues: Missing index options, graph ACLs, selector options
  • Upgrade issues: Stored copies of built-in procedures
  • Durability and rebuild issues: Indexes that refuse searches, rebuild errors
  • Performance issues: Slow builds, high latency, high memory usage
  • Diagnostic functions quick reference: A table of functions for inspecting index health

Overview

Vector index troubleshooting usually falls into one of a few fault domains: the ONNX plugin and embedding model, the index creation itself, the query plan, the HTTP embedding provider, SPARQL-specific plumbing, or raw performance. Each symptom in this guide is grouped under the category it most commonly belongs to.

Health check

A quick diagnostic sequence to run before deep troubleshooting. Work through the steps in order — each one narrows down where the fault lies.

SQL
-- 1. Verify the vonnx plugin loaded (check server log for
--    "SUCCESS plugin N: loaded from .../vonnx.so")

-- 2. Verify the model is loaded:
SELECT model_name, model_type, handle FROM DB.DBA.ONNX_MODELS;
-- For local models (text_embedding), handle must be non-null
-- For HTTP providers (openai, ollama, etc.), handle is null (expected)

-- 3. Test embedding:
SELECT DB.DBA.ONNX_EMBED_TEXT_FOR_MODEL('granite-384-local', 'health check');
-- Expect: a 384-element float vector

-- 4. Verify index exists and is populated:
SELECT obj2json (__VEC_INDEX_METRICS ('DOCS_VEC'));
-- managed_rows and graph_nodes should be > 0
-- state should be 'READY_FOR_INTERNAL_LOAD'
-- (for a SPARQL index, also check SVI_STATE = 'active' in DB.DBA.SPARQL_VEC_INDEX)

-- 5. Verify search works:
SELECT __VEC_INDEX_SEARCH('DOCS_VEC', DB.DBA.ONNX_EMBED_TEXT_FOR_MODEL('granite-384-local', 'test'), 10, 100);
-- Expect: a vector of (row id, distance) pairs, nearest first (not empty)

Plugin and model issues

Symptom: SR185: Undefined procedure DB.DBA.ONNX_EMBED_TEXT, or "Model granite-384-local is not present in DB.DBA.ONNX_MODELS"

  • Cause: The vonnx plugin is not loaded, so the ONNX BIFs are unregistered. ONNX_EMBED_TEXT then fails with SR185; on a new database the bundled model is never registered, so ONNX_EMBED_TEXT_FOR_MODEL reports that granite-384-local is not present in DB.DBA.ONNX_MODELS and CREATE VECTOR INDEX fails with VEC18. The server log has no SUCCESS plugin N: loaded from .../vonnx.so line, and has instead:
    Output
    ONNX bootstrap disabled granite-384-local: helper configuration failed for …/onnx_helper: [42001] SR185: Undefined
  • Fix: Load vonnx in the [Plugins] section of virtuoso.ini, and say where the model lives in [ONNX]. The installed virtuoso.ini already does, when the server was built with the ONNX plugin; with the paths of a default make install:
INI
[Plugins]
LoadPath = /usr/local/virtuoso-opensource/lib/virtuoso/hosting
Load17   = plain, vonnx

[ONNX]
ModelRoot = /usr/local/virtuoso-opensource/lib/virtuoso/hosting/onnx-models

Use your installation's hosting directory; the LoadN number only has to be unique within [Plugins]. Restart the server and confirm the log shows "SUCCESS plugin N: loaded from .../vonnx.so". If the plugin was not built at all, configure the server with --enable-onnxruntime=<dir> and build it again.

Symptom: handle is NULL for a local ONNX model

  • Cause: The model files or the helper couldn't be found or loaded — usually a path issue
  • Fix: - If the server log has a line beginning ONNX bootstrap disabled granite-384-local: model=<unresolved> vocab=<unresolved> …, the bootstrap could not find the bundled model's files. Set [ONNX] ModelRoot in virtuoso.ini to the installed onnx-models directory (or set GraniteModelPath and GraniteVocabPath), and HelperPath if onnx_helper is not in the plugin LoadPath; use absolute paths, and restart. Until then, a new database has no granite-384-local row at all; a database that had one keeps it with model_type unavailable, a NULL handle, and the full message in description, and text searches on its indexes fail with VEC51: Automatic embedding for vector index … requires a loaded handle for model granite-384-local in DB.DBA.ONNX_MODELS. - Check the model path exists: SELECT model_path FROM DB.DBA.ONNX_MODELS WHERE model_name = 'granite-384-local' - Try refreshing: SELECT DB.DBA.ONNX_MODEL_REFRESH('granite-384-local') - Check the server log for ONNX load errors - ONNX Runtime is linked into vonnx.so and onnx_helper, so there is no separate runtime library to install; what must be found is an executable onnx_helper

Index creation issues

Symptom: CREATE VECTOR INDEX fails with "SQ022: Duplicate index name"

  • Cause: An index of that name — vector or ordinary — already exists.
  • Fix: Drop it with DROP INDEX <name>; or choose another name. CREATE VECTOR INDEX and DROP INDEX are atomic: a create that fails leaves no index, hidden table or triggers behind, so there is nothing to clean up by hand before retrying.

Symptom: dimensions mismatch error

  • Cause: The WITH (dimensions = N) value doesn't match the model's output dimension:
    Output
    VEC19: Model granite-384-local output dimension 384 does not match requested vector dimension 256.
  • Fix: Use the correct dimension — the bundled granite-384-local = 384, OpenAI text-embedding-3-small = 1536, etc. — or leave dimensions out and let it come from the model. Check with:
SQL
SELECT output_shape FROM DB.DBA.ONNX_MODELS WHERE model_name = '...';

The SPARQL DDL reports the same mismatch as VEC75; see SPARQL-specific issues.

Symptom: "VEC17: Vector storage cannot fit node metadata for hnsw_m=N"

  • Cause: hnsw_m is too large for a node page. Each node reserves room for 17 × hnsw_m neighbour references on its 8 KB page, so above hnsw_m = 118 not even a reference to an out-of-line vector fits (and above 95, a 384-dimension vector no longer fits inline and moves to overflow pages).
  • Fix: Use a smaller hnsw_m. The default is 16, and 32 is the usual choice for high-dimensional models; see Index parameters.

Query issues

Symptom: Query returns 0 rows (empty result set)

  • Cause: The HNSW graph is empty (no nodes)
  • Diagnosis:
SQL
SELECT obj2json (__VEC_INDEX_METRICS ('DOCS_VEC'));
-- If managed_rows = 0 and graph_nodes = 0, the graph was never populated
  • Fix: If the rows were written after the index was created, drain the queue (see New rows don't appear). Otherwise drop and recreate the index:
SQL
DROP INDEX DOCS_VEC;
CREATE VECTOR INDEX DOCS_VEC ON DB.DBA.DOCS (content)
USING 'granite-384-local'
WITH (dimensions = 384, distance_metric = 'cosine');

If the create succeeds but graph_nodes is still 0, check that the base table has rows and the model can embed.

Symptom: Query returns NULL for VECTOR_DISTANCE / X_SIMILAR

  • Cause: The row has no embedding: it was written after the index was created and its change is still in the maintenance queue, or its text is NULL, which is never embedded.
  • Fix: Drain the queue (below). A row whose text is NULL keeps a NULL distance and score, and never appears in an index-driven search.

Symptom: New rows don't appear in search results

  • Cause: The maintenance queue hasn't been drained. A scheduled worker drains it only every few minutes, and the SPARQL async queue is drained only when something calls a drain — see Index maintenance.
  • Fix:
SQL
-- For SQL indexes:
SELECT DB.DBA.VEC_SQL_QUEUE_DRAIN(1000);

-- For SPARQL indexes with maintenance = 'async':
SELECT DB.DBA.VEC_INDEX_QUEUE_DRAIN(1000);
-- Or refresh the index (also picks up changes written straight into RDF_QUAD):
SPARQL DEFINE input:storage "" refresh VECTOR INDEX <urn:docs:idx> ;
  • Cause: The optimizer didn't recognize the X_SIMILAR/VECTOR_DISTANCE call as indexable
  • Fix: - Ensure X_SIMILAR or VECTOR_DISTANCE is in the WHERE clause or ORDER BY, not just the SELECT list - Ensure the column name in the function matches the indexed column - Check that a vector index exists on the table — each one has its <name>_VVEC_AI maintenance trigger: SELECT T_NAME FROM DB.DBA.SYS_TRIGGERS WHERE T_TABLE = 'DB.DBA.DOCS' AND T_NAME LIKE '%_VVEC_AI'. By name, SELECT __VEC_INDEX_METRICS ('DOCS_VEC') fails with VEC01 when there is no such index and VEC02 when it is not a vector index.

Symptom: VEC33 "Malformed query vector" for a text parameter

  • Cause: The query text was bound as a parameter (?, as ODBC, JDBC and exec pass it). X_SIMILAR and VECTOR_DISTANCE embed text only when it is a literal in the statement; a parameter is taken as the query vector itself.
  • Fix: Embed the parameter and pass the vector — see Query with text.

Symptom: SQ033 "SELECT access denied for column model_id of table DB.DBA.ONNX_MODELS"

  • Cause: A non-DBA account searched by text with X_SIMILAR or VECTOR_DISTANCE. A text search reads the model registry, and SELECT on the table does not cover that.
  • Fix: Give the account the SPARQL_SELECT role, or GRANT SELECT (model_id, model_name, model_type, handle) ON DB.DBA.ONNX_MODELS TO "<account>". See Permissions.

Build performance issues

Symptom: a large REFRESH takes hours and appears to hang

Usually one of the following, and most of them are configurable.

Embedding is running on one core. A serial embedding caller holds a single ONNX helper at a time, so aggregate helper CPU sits at 100% regardless of how many cores the machine has. Raise the concurrency:

SQL
registry_set ('__vec_embed_concurrency', '8');   -- default 4, max 32
SELECT DB.DBA.VEC_INDEX_EMBED_CONCURRENCY ();    -- confirm the effective value

Verify it took effect by sampling helper CPU while the refresh runs — the aggregate across onnx_helper processes should be several hundred percent, not ~100%. The backfill starting line in the server log also reports the concurrency the refresh actually used. If it could not raise the ONNX helper pool to match, the refresh's result message and SVI_LAST_ERROR say so (could not raise ONNX setting helper_pool_size …) — ONNX_CONFIG is DBA-only, so a refresh run by another account cannot resize the pool.

The useful ceiling is the number of physical cores: each helper is single-threaded, and on a 9.7M-row build 99.998% of the embedding time was model inference. Each concurrent batch also needs a thread of the server's async-queue pool (AsyncQueueMaxThreads, 10 in the stock virtuoso.ini); asked for more, the refresh uses what the pool has and says so in its result — see Embedding concurrency.

The texts are of mixed length. The helper sorts each request by token count, so a larger batch lets labels and paragraphs run apart instead of padding every label to the longest paragraph: on a mixed corpus (measured with all-MiniLM-L6-v2), batch 256 was 1.85 times as fast as the default 32. ONNX_MODEL_INFO's real_tokens / padded_tokens says how much of the model's work was padding. See Batch size.

The refresh is not silent — look at the server log. Progress is reported about every 2000 embedded rows with a percentage, rate and ETA:

Output
vector index VECIDX_...: embedded 2048 of 3000 rows to embed (68%), 884.7 rows/s, ~1 s remaining

If you see no such lines, check whether __vec_progress_every has been set to '0'. Each phase also logs its own time (phase enumerate took ... ms), so the last phase line shows where a refresh is.

The graph build fell back to the serial builder. Look for auto-rebuild falling back to replay in the log. Above about 1.2 million 384-d rows the fast builder's memory estimate exceeds its default 2 GB cap, and the replay builder takes days on a corpus that size; a logged rebuild whose transaction log would not fit under TransactionAfterImageLimit falls back the same way (run it after log_enable (2) — see Rebuild methods). Raise the cap and, on a large machine, the thread count, then rebuild:

SQL
registry_set ('__vec_fast_rebuild_max_bytes', '20000000000');
registry_set ('__vec_rebuild_threads', '16');

See Operating a multi-million-row build.

Symptom: a small change to a bulk-built index takes as long as the first build

A refresh of a build = 'bulk' index links a change of up to __vec_bulk_delta_pct percent of the graph (default 10) into the existing graph, and logs linking them into the graph instead of rebuilding it; a larger change rebuilds the whole graph. If you see no such line for a small change, check the setting — '0' disables linking — and whether the graph was healthy: a refresh always rebuilds an empty or condemned graph. See Later refreshes of a bulk index.

See Embedding concurrency and Build progress.

Symptom: clearing a registry setting made things slower, not default

registry_get returns a blank string for a key cleared with registry_set (k, ''), which is not the same as an unset key. Both are treated as "use the default" by VEC_INDEX_EMBED_CONCURRENCY and VEC_INDEX_PROGRESS_EVERY, but confirm rather than assume:

SQL
registry_set ('__vec_embed_concurrency', '');
SELECT DB.DBA.VEC_INDEX_EMBED_CONCURRENCY ();   -- expect 4, not 1

Symptom: a remote provider build is extremely slow

Check provider_max_batch. If it is 1, every text costs a separate HTTPS round trip — 150–300 ms each. null uses the per-provider default (128 for OpenAI, 32 otherwise); see Request batching.

SQL
SELECT model_name, provider_max_batch FROM DB.DBA.ONNX_MODELS
 WHERE model_type <> 'text_embedding';

HTTP provider issues

The error a caller sees from a failed provider call is deliberately generic, because a provider's response can echo the text being embedded:

Output
HTTP embedding provider request failed after 1 attempt(s); see the server log for the provider response.

(… batch request (N inputs) failed after … for a batch.) The server log has one line per failed attempt, with the provider's status line:

Output
ONNX HTTP embedding provider <provider> model=<model> endpoint=<url> attempt 1/3 failed: HTTP/1.0 401 Unauthorized

The model's row in DB.DBA.ONNX_MODELS also records the failure, in provider_consecutive_failures and provider_last_error.

Symptom: the provider answers 401 Unauthorized

  • Cause: The provider rejected the key. Either the stored key is wrong, or the model's key name points at a registry entry that was never set — the request still goes out, with an invalid key, and the provider answers 401. A 401 (like any 4xx other than 429) is not retried.
  • Diagnosis: - Check which key name the model uses: SELECT provider_api_key_name FROM DB.DBA.ONNX_MODELS WHERE model_name = '...' - Check that a key is stored under that name: SELECT registry_get ('ONNX_API_KEY_OPENAI') returns 0 when nothing is stored there
  • Fix: The _api_key_name parameter in ONNX_REGISTER_HTTP_EMBED_MODEL must be the REGISTRY NAME (e.g. 'ONNX_API_KEY_OPENAI'), not the key value itself. For the 'openai' provider, passing null auto-defaults to 'ONNX_API_KEY_OPENAI'. (An openai model whose key is stored as an empty string fails before sending anything, with OpenAI embedding requires a configured API key.)

Symptom: the provider answers 429 Too Many Requests, or 5xx

  • Cause: API rate limit exceeded, or the provider failing. A 429, 500, 502, 503 or 504 — and a connection failure or timeout — is retried: up to three attempts, waiting as long as a numeric Retry-After header asks (at most 30 s), otherwise 0.5 s and then 1 s. The caller sees an error only when all three attempts fail (failed after 3 attempt(s)).
  • Fix: Reduce the rate of embedding calls — drain the queues less often or in smaller batches, and lower __vec_embed_concurrency for a refresh. Consider using a local model for development.

Symptom: Remote provider query is slow

  • Cause: Each query makes an HTTP round-trip to embed the query text — ORDER BY VECTOR_DISTANCE (col, 'text') makes two, WHERE X_SIMILAR (col, 'text') > … one.
  • Fix: Use the explicit-vector form, which embeds once:
SQL
SELECT TOP 10 id, content,
       VECTOR_DISTANCE(content, qv.v) AS distance
FROM DB.DBA.DOCS
CROSS JOIN (SELECT DB.DBA.ONNX_EMBED_TEXT_FOR_MODEL('openai-small', 'query') AS v) qv
ORDER BY VECTOR_DISTANCE(content, qv.v);

SPARQL-specific issues

Symptom: VEC58 or VEC59 error

  • Cause: The index option was omitted from bif:vector_search, and either no active index (VEC58) or several active indexes (VEC59) exist:
    Output
    VEC59: bif:vector_search requires an INDEX option because 4 active vector indexes are available
  • Fix: Always specify the index: option (index <urn:your-index>, ...).

To confirm that a query is answered by the vector index rather than as an ordinary triple pattern, look at the SQL it compiles to:

SQL
SELECT CAST(sparql_to_sql_text('select ?doc where { ?doc bif:vector_search "test" option (index <urn:docs:idx>, limit 5) }') AS VARCHAR);
-- Should contain DB.DBA.VEC_INDEX_SEARCH_ROWS, not an RDF_QUAD scan

Symptom: No rows returned for a non-DBA user

  • Cause: Graph ACL — the user doesn't have read permission for the indexed graph
  • Fix: Grant read permission: DB.DBA.RDF_GRAPH_USER_PERMS_SET('urn:docs:g', 'username', 1)

Symptom: VEC72 "Unsupported reduce mode"

  • Cause: The reduce option in a SPARQL CREATE VECTOR INDEX was set to a value other than 'none' or 'truncate' — for example reduce = 'halve' gives Unsupported reduce mode: halve (expected none or truncate)
  • Fix: Use reduce = 'truncate' (Matryoshka reduction) or omit the option (defaults to 'none'). See Index parameters: Matryoshka reduction.

Symptom: VEC73 "reduce = truncate requires WITH (dimensions = \<n\>)"

  • Cause: reduce = 'truncate' was specified without an explicit dimensions value in the WITH clause
  • Fix: Add dimensions = <n> where <n> is the desired reduced dimension (must be less than the model's native output dimension).

Symptom: VEC74 "reduce = truncate requires dimensions (N) to be less than the model native output dimension (M)"

  • Cause: reduce = 'truncate' was used with dimensions >= the model's native output dimension
  • Fix: Set dimensions to a value strictly less than the native dimension. For example, with OpenAI text-embedding-3-small (1536 native), use dimensions = 256 or dimensions = 512.

Symptom: VEC75 "dimensions (N) must equal the model native output dimension (M) when reduce is not truncate"

  • Cause: reduce = 'none' (the default) was used with dimensions that does not match the model's native output dimension
  • Fix: Either set dimensions to the model's native dimension, or use reduce = 'truncate' with a smaller dimensions value.

Symptom: VEC76 "Vector index <...> not found for trigger setup"

  • Cause: The internal VEC_INDEX_SET_TRIGGERS procedure was called for an index IRI that does not exist in DB.DBA.SPARQL_VEC_INDEX. This should not happen during normal CREATE VECTOR INDEX flow; it indicates the catalog row was removed while the index was being created.
  • Fix: Re-run CREATE VECTOR INDEX. If it persists, check for orphaned trigger rows: SELECT * FROM DB.DBA.SPARQL_VEC_INDEX_TRIGGER WHERE SVT_INDEX_IRI NOT IN (SELECT SVI_IRI FROM DB.DBA.SPARQL_VEC_INDEX) and delete them.

Symptom: VEC77 "maintenance = async for a general (KEY/EMBEDDING/WHERE) vector index requires every indexed triple pattern to be scoped to a named graph"

  • Cause: A general (KEY/EMBEDDING/WHERE) selector was created with maintenance = 'async', but the WHERE pattern contains a triple that is neither inside an explicit GRAPH <iri> nor covered by a FROM <iri> clause on the selector (or sits in a variable graph with no FROM). The parser could not extract a (graph, predicate) dependency pair for that triple, so the async change-matcher would have nothing to match against. The create fails and leaves no index behind.
  • Fix: Wrap every triple pattern in an explicit GRAPH <iri> block, or add a FROM <iri> clause naming the graphs the pattern reads, and retry — or use maintenance = 'manual' with periodic REFRESH VECTOR INDEX. See Maintenance: async scoping.

Upgrade issues

Symptom: a fix in a new server build has no effect on an existing database

  • Cause: The vector-index procedures (DB.DBA.VEC_INDEX_*, DB.DBA.VEC_SQL_*, DB.DBA.ONNX_*) are compiled into the server and installed afresh on every start; they are not stored in the database. But a procedure created with create procedure — for example by loading a patched sparql_vec_index.sql through isql — is stored, and a stored procedure takes precedence over the built-in of the same name, across restarts and upgrades. The database keeps running the hand-loaded copy.
  • Diagnosis: on an unmodified database this returns nothing; anything it returns is a stored copy.
SQL
SELECT P_NAME FROM DB.DBA.SYS_PROCEDURES
 WHERE P_NAME LIKE 'DB.DBA.VEC!_INDEX%' ESCAPE '!'
    OR P_NAME LIKE 'DB.DBA.VEC!_SQL%' ESCAPE '!'
    OR P_NAME LIKE 'DB.DBA.ONNX!_%' ESCAPE '!';
  • Fix: drop the stored copies, then restart the server. The built-in versions are installed only at start-up: between the DROP PROCEDURE and the restart the procedure does not exist at all (SR185: Undefined procedure), so do this in a maintenance window. Alternatively, load the .sql file that matches the new build, which replaces the stored copies with current ones.

Durability and rebuild issues

Symptom: VEC80 "vector index is inconsistent with its stored embeddings and cannot serve searches"

  • Cause: The index's stored HNSW graph no longer matches its stored embeddings — typically after an unclean shutdown mid-write. When the graph has only fallen behind the data, the server rebuilds it automatically on first use and the server log says so; VEC80 then means the search arrived while that rebuild was running, or the index could not be repaired automatically and has been marked needs_rebuild. Either way it fails closed: it refuses to serve searches rather than return silently degraded results.
  • Diagnosis:
SQL
SELECT VIE_NAME, VIE_STATE, VIE_EPOCH, VIE_CHECKED_AT FROM DB.DBA.VEC_INDEX_EPOCH;
-- VIE_STATE = 'needs_rebuild' identifies the affected index
  • Fix: Rebuild the graph from the durable stored embeddings (no re-embedding or model call is needed):
SQL
SELECT __VEC_INDEX_REBUILD('DOCS_VEC');
SELECT __VEC_INDEX_HEALTH_RECHECK('DOCS_VEC');  -- expect 1 = ok

For a SPARQL index, REFRESH VECTOR INDEX <iri> recovers it too: a refresh that finds the graph unhealthy rebuilds it. See Maintenance: durability and recovery.

  • Only a rebuild clears a needs_rebuild verdict. It is stored, so it survives a restart, and neither a repeated __VEC_INDEX_HEALTH_RECHECK nor inserts and deletes into the index clear it.

Symptom: VEC95 "a logged fast rebuild ... would fail at commit"

  • Cause: An explicit fast rebuild — __VEC_INDEX_REBUILD (idx, 'fast'), or an index created WITH (rebuild = 'fast') — was started with the transaction log on, and even a lower bound on the log it would write exceeds what is left of TransactionAfterImageLimit (50 MB by default). A transaction whose log outgrows the limit fails at commit, after the whole build, so the rebuild is refused before the old graph is touched, and the old graph keeps answering:
    Output
    VEC95: Vector index FA_IDX: a logged fast rebuild of 31000 vectors writes at least 51584000 bytes of transaction log, more than the 50000000 bytes left under TransactionAfterImageLimit, so it would fail at commit after building the whole graph.  Run it after log_enable (2) and checkpoint afterwards, or raise TransactionAfterImageLimit.

    The bound is a floor, so an explicit logged fast rebuild that passes it can still fail at commit with SR325 when it ends up just over the limit.

  • auto, and a build = 'bulk' refresh, do not get this error: when a logged fast build would not fit, they use the replay rebuild, which commits as it goes, and say so in the server log:
Output
Vector index FA_IDX: auto-rebuild falling back to replay -- a logged fast build of 31000 vectors (dim 384) would write about 53816000 bytes of transaction log, more than the 50000000 bytes left under TransactionAfterImageLimit; replay commits as it goes.  Rebuild after log_enable (2) and checkpoint afterwards to use the fast builder.
  • Fix: Run it unlogged, then checkpoint:
SQL
log_enable (2, 1);
SELECT __VEC_INDEX_REBUILD('DOCS_VEC', 'fast');
COMMIT work;
log_enable (1, 1);
CHECKPOINT;

Or raise TransactionAfterImageLimit in virtuoso.ini, if you want the rebuild in the log.

Symptom: VEC94 "cannot allocate fast-rebuild input arena"

  • Cause: The fast builder allocates room for every stored vector before it starts, and the allocation failed. Nothing was changed: the old graph still answers.
  • Fix: Free memory, or rebuild with 'replay' — slower, but it holds one node at a time. See the memory guard under Maintenance: rebuild methods.

Symptom: VEC81 "method must be 'replay', 'fast' or 'auto'"

  • Cause: __VEC_INDEX_REBUILD (__VEC_INDEX_REBUILD method must be 'replay', 'fast' or 'auto'.) or the rebuild option on CREATE VECTOR INDEX (Vector rebuild option must be 'replay', 'fast' or 'auto'.) was given a method other than replay, fast, or auto. There is no legacy method — legacy and packed refer to the on-disk page format, not the rebuild method.
  • Fix: Use a valid method: SELECT __VEC_INDEX_REBUILD('DOCS_VEC', 'fast');. See Maintenance: rebuild methods.

Performance issues

Symptom: Index creation is slow

  • Cause: Embedding the rows, a high ef_construction value, or a large dataset
  • Fix: With a local model, embedding usually dominates — see Build performance issues. The graph build scales as O(N * log(N) * ef_construction): the default ef_construction=200 suits development, and 400 is a good balance for production. Values above 1000 have diminishing returns.

Symptom: Search latency is high

  • Cause: High ef_search/k, large dataset, or high-dimensional vectors
  • Fix: - Reduce k (SQL) or ef_search (SPARQL) — but this may reduce recall - Check __VEC_INDEX_METRICS for search_usec / search_count to see average latency (searches are timed to the millisecond, so on a small index the average reads 0) - For remote providers, network latency dominates — consider a local model

Symptom: High memory usage

  • Cause: HNSW graph memory + ONNX helper process
  • Fix: - Reduce hnsw_m (fewer neighbors per node) - Use a lower-dimensional model - Use reduce = 'truncate' with an MRL-trained model (e.g. OpenAI text-embedding-3-small 1536 → 256) to reduce per-vector memory by up to 6x - Reduce NumberOfBuffers in virtuoso.ini - Budget for the ONNX helpers: two or more per local model, up to about 285 MB each at rest with the bundled model (see Deployment and sizing)

Diagnostic functions quick reference

FunctionPurpose
__VEC_INDEX_METRICS('idx')Index health: node counts, search stats, state
__VEC_INDEX_PAGE_STATS('idx')Storage stats: page counts, entry point, layers
__VEC_INDEX_VALIDATE('idx')ANN vs exact recall, and graph/data consistency
__VEC_INDEX_HEALTH_RECHECK('idx')Re-evaluate health: 1 = ok, 2 = needs_rebuild
__VEC_INDEX_REBUILD('idx' [, method [, threads]])Rebuild graph from stored embeddings; recovers a needs_rebuild (VEC80) index
__VEC_INDEX_QUERY_TEXT_VECTOR_CALLS(reset)Count of query texts embedded, server-wide; a non-zero reset also clears it
DB.DBA.VEC_SQL_WORKER_STATUS()SQL queue health (a name/value vector: SELECT obj2json (...))
DB.DBA.VEC_INDEX_WORKER_STATUS()SPARQL queue worker health
explain('SELECT ...')Check if ANN or full scan is used
sparql_to_sql_text('...')Check that bif:vector_search compiles to VEC_INDEX_SEARCH_ROWS

The __VEC_INDEX_* functions in this table are DBA-only. Those that return name/value vectors read best through obj2json.


Next steps

  • Maintenance — Learn how to drain maintenance queues, schedule workers, and keep indexes current as data changes
  • API reference — Reference for all vector index procedures, BIFs, and catalog tables
  • SQL vector index how-to — Complete guide for creating, querying, and maintaining SQL vector indexes

Reference and concepts:

  • Embedding models — Local ONNX models, remote HTTP providers, API key management, and model registration
  • Index parameters — Tune HNSW parameters (hnsw_m, ef_construction) and query-time settings (k, ef_search) for accuracy and performance
  • Vector index overview — Architecture and concepts behind the Virtuoso vector index

PreviousManaging vector indexes visually with the VVec Console NextAPI reference for the Virtuoso vector index