---
title: "Troubleshooting Virtuoso Vector Index"
description: "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."
url: urn:osdi:this
dateModified: 2026-10-30T15:45:34Z
language: en
---
One page of the Vector Index documentation, MERGED by build_vector_index_docs.py from this template and the markdown in fixture/vector-index/. Do not edit the generated file: change this template or the generator and rebuild. It opens on the documentation header (dxshell.py), not on the site hero.

[Home](urn:osdi:site)/[Virtuoso](urn:osdi:sitevirtuoso/)/[Vector index](urn:osdi:sitevirtuoso/docs/vector-index/)/Troubleshooting the Virtuoso vector index

# 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.

Start  [Overview](urn:osdi:sitevirtuoso/docs/vector-index/) [Quickstart](urn:osdi:sitevirtuoso/docs/vector-index/quickstart/) [Tutorial: FAQ semantic search](urn:osdi:sitevirtuoso/docs/vector-index/tutorial-faq-semantic-search/)  Build  [SQL vector index how-to](urn:osdi:sitevirtuoso/docs/vector-index/howto-sql-vector-index/) [SPARQL vector index how-to](urn:osdi:sitevirtuoso/docs/vector-index/howto-sparql-vector-index/) [Search RDF literals (knnSimilar)](urn:osdi:sitevirtuoso/docs/vector-index/knn-similar/)  Configure  [Embedding models](urn:osdi:sitevirtuoso/docs/vector-index/embedding-models/) [Index parameters](urn:osdi:sitevirtuoso/docs/vector-index/index-parameters/) [Similarity metrics](urn:osdi:sitevirtuoso/docs/vector-index/similarity-metrics/)  Operate  [VVec Console (web UI)](urn:osdi:sitevirtuoso/docs/vector-index/vvec-console/) Troubleshooting  Reference  [API reference](urn:osdi:sitevirtuoso/docs/vector-index/api-reference/)

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.

Tip

Run the full health check sequence first. It takes seconds and points you to the right symptom category in the majority of cases.

## 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)
```

Note

The `__VEC_*` functions are BIFs — call them unqualified (no `DB.DBA.` prefix). The `ONNX_*` and `VEC_*` procedures live in `DB.DBA` and must be qualified.

## 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](#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](urn:osdi:sitevirtuoso/docs/vector-index/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](#symptom-new-rows-dont-appear-in-search-results)). 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> ;
```

Important

New rows become searchable only **after** the next queue drain. If you insert and immediately query, the rows will not yet be in the graph.

### Symptom: EXPLAIN shows full table scan instead of ANN search

- **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](urn:osdi:sitevirtuoso/docs/vector-index/howto-sql-vector-index/#query-with-text-primary-form).

### 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](urn:osdi:sitevirtuoso/docs/vector-index/howto-sql-vector-index/#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](urn:osdi:sitevirtuoso/docs/vector-index/index-parameters/#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](urn:osdi:sitevirtuoso/docs/vector-index/embedding-models/#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`.)

Tip

Never store raw API key values in SQL files or model registrations. Use the registry name and load the secret via `registry_set` once, out of source control.

### 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](urn:osdi:sitevirtuoso/docs/vector-index/index-parameters/#matryoshka-dimension-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](#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](urn:osdi:sitevirtuoso/docs/vector-index/howto-sql-vector-index/#deployment-and-sizing))

## Diagnostic functions quick reference

| Function | Purpose |
| --- | --- |
| `__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](urn:osdi:sitevirtuoso/docs/vector-index/api-reference/)** — Reference for all vector index procedures, BIFs, and catalog tables
- **[SQL vector index how-to](urn:osdi:sitevirtuoso/docs/vector-index/howto-sql-vector-index/)** — Complete guide for creating, querying, and maintaining SQL vector indexes

## Related content

**Reference and concepts:**

- [Embedding models](urn:osdi:sitevirtuoso/docs/vector-index/embedding-models/) — Local ONNX models, remote HTTP providers, API key management, and model registration
- [Index parameters](urn:osdi:sitevirtuoso/docs/vector-index/index-parameters/) — Tune HNSW parameters (`hnsw_m`, `ef_construction`) and query-time settings (`k`, `ef_search`) for accuracy and performance
- [Vector index overview](urn:osdi:sitevirtuoso/docs/vector-index/) — Architecture and concepts behind the Virtuoso vector index

[PreviousManaging vector indexes visually with the VVec Console](urn:osdi:sitevirtuoso/docs/vector-index/vvec-console/) [NextAPI reference for the Virtuoso vector index](urn:osdi:sitevirtuoso/docs/vector-index/api-reference/)

On this page

1. [Overview](#overview)
2. [Health check](#health-check)
3. [Plugin and model issues](#plugin-and-model-issues)
4. [Index creation issues](#index-creation-issues)
5. [Query issues](#query-issues)
6. [Build performance issues](#build-performance-issues)
7. [HTTP provider issues](#http-provider-issues)
8. [SPARQL-specific issues](#sparql-specific-issues)
9. [Upgrade issues](#upgrade-issues)
10. [Durability and rebuild issues](#durability-and-rebuild-issues)
11. [Performance issues](#performance-issues)
12. [Diagnostic functions quick reference](#diagnostic-functions-quick-reference)
13. [Next steps](#next-steps)
14. [Related content](#related-content)
