Check and repair the search graph
The language tabs use earlier client interfaces. Check SDK versions and connection setup before running them.
HNSW links nearby vectors in a graph. Health checks show whether the graph can reach the stored entries.
Before you run it: Concurrent writes can conflict with rebuilding. Memory limits and supported diagnostic metrics are listed below.
Use a table with a populated HNSW index, such as the single-vector setup expanded with representative data. HNSW is an index that links nearby vectors. A component is a disconnected group; an unreachable entry cannot be reached from the index's starting point. Start with the health request, not the rebuild.
- Run the health checkInspect disconnected components and unreachable vectors.
- Review a dry runCheck the proposed metric, memory and rebuild plan.
- Rebuild when justifiedPause conflicting writes and follow the documented limits.
- Compare health afterwardsInspect the returned before-and-after reports.
Follow the example
- Run the read-only health request.
- Inspect unreachable entries and components.
- Try a dry run before rebuilding.
- Plan memory use and pause writes if rebuilding.
Run the request
curl -G "https://$OC_HOST/v1/tenants/$OC_TENANT/vector/shop.products/hnsw-health" \
-H "Authorization: Bearer $OC_TOKEN" \
--data-urlencode "self_recall=512"Check the result
A rebuild reconstructs the index from stored vectors. The health report also offers optional recall checks.
{
"table": "shop.products",
"present": true,
"nodes": 41892,
"live": 41892,
"tombstoned": 0,
"reachable_live": 41892,
"reachable_fraction": 1.0,
"components": 1,
"zero_indegree_live": 0,
"indegree_percentiles": [1, 4, 9, 16, 27, 48, 91], // min p1 p10 p50 p90 p99 max
"mean_outdegree": 15.8,
"shattered": false,
"damage": [],
"build_metric": "cosine",
"stored_vectors": 41892,
"unindexed_records": 0,
"verdict": "healthy: 41892 of 41892 live vectors retrievable, ...",
"self_recall": {
"metric": "cosine",
"applicable": true,
"probes": 512,
"hits": 512,
"self_recall": 1.0,
"misses": [],
"verdict": "healthy: all 512 probed vectors retrieve themselves ..."
}
/* plus per-layer shape, norm deciles and advisories */
}self_recall is present only when you ask for it. Everything else comes back on every call.
Query options
| Field | Required | Notes |
|---|---|---|
| self_recall | no | Run the self-retrieval probe over this many live vectors as well as the topology report. Absent or 0 skips it; the cap is 4096 because each sample is a real query inside this one request. |
| metric | no | Metric to probe under. Defaults to the recorded build metric, then the schema's declared distance, then cosine. |
| dim | no | Vector width. Only needed when the probe runs and the table has no registered vector dimension. |
Understand the behavior
The topology half walks the graph and counts. It catches the failure it was built for - a graph that reaches a small fraction of its own nodes, where no amount of beam widening recovers the rest - and it reports shattered: true with the reasons in damage.
The topology half is structurally blind to one thing: an index built for one distance metric and queried under another. That graph is perfectly connected and reports every reachability field as healthy. ?self_recall=N asks the other question, and needs no ground truth to do it - under cosine, L2 or Manhattan a vector is its own exact nearest neighbour, so any miss is a defect. Under dot product that is not true, and the probe says so with "applicable": false rather than returning a number that would read as a failure.
That makes the probe the tool for an unstamped index. Run it once per candidate metric; the one the graph was really built under is the one that comes back at 1.0. Then pass that metric to the rebuild, which records it beside the graph so nothing has to guess again.
Rebuild the index
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/vector/shop.products/rebuild-hnsw" \
-H "Authorization: Bearer $OC_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"metric": "cosine",
"dry_run": true
}'Check the rebuild result
{
"tenant": "…",
"table": "shop.products",
"rebuilt": false, // false on a dry run
"metric": "cosine",
"build_metric_before": null, // null on an index built before the stamp
"dim": 768,
"vectors": 41892,
"estimated_peak_bytes": 137070624,
"estimated_build_secs": 55,
"health_before": { /* the same report as GET hnsw-health */ },
"health_after": null, // null on a dry run
"elapsed_ms": 412
}Drop dry_run to run it for real: rebuilt becomes true and health_after carries the post-rebuild report, so one response shows you the before and the after.
Rebuild options
| Field | Required | Notes |
|---|---|---|
| metric | conditional | Required unless the table has a registered vector schema or a recorded build metric. The engine refuses rather than defaulting - a rebuild under the wrong metric produces a well-connected index for the wrong distance, which no health check can detect. |
| dry_run | no | Report the plan - current health, vector count, memory estimate, time estimate - and build nothing. Defaults to false. |
| force | no | Rebuild even when the diagnostic reads healthy. Defaults to false so a fleet sweep cannot spend hours reindexing tables that are fine. |
| max_bytes | no | Override the per-request memory budget for the build. The default ceiling is 2 GiB; a rebuild whose estimate exceeds it is refused with the estimate in the message. |
Important limits and checks
- Rebuilding a table under write load. The build holds no lock, but the final swap refuses with 409 if any vector was written or deleted while it ran - overwriting the graph would leave the new embedding on disk and unreachable by every query. Nothing is written on that refusal, so a retry is free; a continuously written table will not converge until you pause writes to it.
- Rebuilding a healthy index. That is a 409, on purpose. Add
"force": trueif you mean it. - Guessing the metric. If the table has no schema and no recorded build metric, the rebuild refuses instead of assuming cosine. Use
?self_recall=to find out which metric the graph matches, then state it. - Reading
self_recallunder dot product. Self-retrieval is not a valid signal there, and the probe reports"applicable": falsewith the numeric fields zeroed. - Calling it on a table with no HNSW index. The rebuild answers 404 - there is no graph to rebuild. An IVF or IVF-PQ table is the usual reason.