Vectors & HNSW
XERJ builds an HNSW graph over your vectors at ingest time and stores the full vectors alongside it. Distance metric, graph parameters, and quantization are set in config (some are also accepted per-request for Elasticsearch compatibility). Note: the query-time kNN serving path is exact brute-force — after any pre-filter it scores every candidate and returns the true nearest neighbours. There is no query-time approximate graph traversal, so ef_search / num_candidates are parsed for ES-compat but do not change results. See KNN query below.
HNSW graph parameters
Quantization
XERJ supports four quantization modes. scalar8 (SQ8) is the default and gives ~4× memory reduction with almost no recall loss on typical embedding spaces.
- none — full-precision f32. Use when recall matters more than RAM.
- scalar8 — 8-bit per dimension. Default. ~4× smaller. Recall impact typically under 1%.
- scalar4 — 4-bit per dimension. ~8× smaller. Noticeable recall hit on fine-grained spaces.
- binary — 1-bit per dimension with Hamming distance. ~32× smaller. Use only if you know the embedding space tolerates it.
KNN query
{
"knn": {
"field": "embedding",
"query_vector": [0.12, 0.08, -0.31, ...],
"k": 20,
"num_candidates": 200,
"ef_search": 180
}
}
num_candidates and ef_search are accepted for Elasticsearch compatibility, but XERJ serves this query with an exact brute-force scan — it returns the true nearest neighbours regardless of those values. There is no query-time HNSW / ef_search beam traversal.
Hybrid — BM25 + KNN in one planner pass
{
"hybrid": {
"fusion": "rrf",
"queries": [
{ "match": { "message": "kernel panic on reboot" } },
{ "knn": { "field": "embedding", "query_vector": [...], "k": 50 } }
]
}
}
Source · engine/crates/vector/src/hnsw.rs