04 · DATA MODEL

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

KEY
TYPE
DEFAULT
DESCRIPTION
hnsw_m
u32
16
Bi-directional edges per layer. Higher = better recall, more RAM.
hnsw_ef_construction
u32
200
Beam width at index-build time. Higher = better graph, slower writes.
hnsw_ef_search
u32
100
Parsed for ES-compat and stored, but the query-time kNN path is exact brute-force, so this does not change results today. Reserved for future approximate serving.
default_metric
enum
"cosine"
"cosine" · "dot_product" · "euclidean".

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.

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