Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Property (equality) indexes

An equality index makes MATCH (n:Label {field: value}) a direct lookup instead of a full scan of every node with that label. It is the right tool for selective property filters — WHERE-style equality on an id, email, city, status, or any scalar field — once a label holds more than a few thousand nodes.

Only scalar values are indexable (Int, Float, Str, Bool). List- and map-valued properties are skipped: a list has no single equality key.

Declaring an index

Via a schema file (idempotent — re-applying is a no-op):

{ "indexes": [["Person", "city"], ["Company", "status"]] }
mushroomdb schema apply mydb.db schema.json
# → created index:Person.city

Or from the embedded API:

#![allow(unused)]
fn main() {
db.enable_index("Person", "city")?;
// db.disable_index("Person", "city")?;
// db.is_index_enabled("Person", "city");
}

Over HTTP, apply a schema through POST /query / the schema endpoint the same way you declare rules or fulltext fields.

Semantics

  • Maintained incrementally. Every insert, SET, and delete updates the index; you never rebuild it by hand.
  • Rebuilt on open. The declaration is persisted in the WAL (and the snapshot baseline); the postings are rebuilt from live data when the store opens — so an index survives a snapshot and reopen with no format migration.
  • Used automatically. The planner rewrites a single non-id equality in a MATCH property map to an indexed lookup. If the field is not indexed the query still runs, just as a scan — declaring an index never changes results, only speed.
  • Which query shapes. The fast path covers:
    • A single non-id equality in the inline property map: MATCH (n:Person {city: 'austin'}) or MATCH (n:Person {city: $c}).
    • A WHERE-clause single equality on the scan variable: MATCH (n:Person) WHERE n.city = 'austin' or WHERE n.city = $c. The equality is folded into an IndexScan at plan time.
    • Compound equalities (two or more equalities on the same scan variable): MATCH (n:Person) WHERE n.city = 'austin' AND n.age = 30 or equivalently as an inline map MATCH (n:Person {city: 'austin', age: 30}). The planner emits an IndexIntersect op; the executor resolves each field against the property index, two-pointer-intersects the resulting sorted node-ID lists, then post-filters any fields that have no declared index. If no fields are indexed the query falls back to a full label scan — results are always correct; only the speed benefit depends on indexes being declared. Compound WHERE predicates with non-equality residuals (AND n.score > 10) fold the equality terms and leave the rest as a trailing filter.
    • Eligibility rules: the fold applies when the scan node has no prior Expand binding it (conservative — cross-expand pushdown is not yet supported). The id pseudo-property is always resolved via the identity map and is excluded from index intersection.

Relationship to the vector index

A property index answers equality; the vector index answers nearness. It is not declared — it is built by a VectorSimilar rule over its two sides. Its shape, the MUSHROOMDB_HNSW_PARAMS override, its memory per indexed vector and the recall its gates assert are documented under Vector index parameters.

From 0.6.6 both kinds of VectorSimilar rule take their candidates from it: an approximate: true rule in one beam pass at k, an approximate: false rule by widening the beam — doubling from ef_search (400 by default) up to a ceiling of 4,096 — and then re-scoring every candidate exactly, which is why its scores are exact and its candidate set carries a committed recall floor of 0.98.

For an exact rule the index only ever narrows the candidates when a beam comes back full with its worst hit below min, which is the one case that proves what the beam left out. A beam that comes back short of its own width (layer 0 need not be one connected component), or that reaches the ceiling with its worst hit still at or above min, falls back to every vector on that side — so a dense cluster above min costs a scan and never costs recall. The same path serves an All([VectorSimilar, …]) rule, whose candidates are that set intersected with the other conjuncts’ index lookups. MUSHROOMDB_VECTOR_SCAN=1 restores the O(n²) full scan for a caller who needs every pair above min provably found.

Query-time find_similar is a different path from rule derivation: brute find_similar is exact GEMM, HNSW is still the approximate path, and exact=True (or a where= predicate) forces GEMM.

Relationship to fulltext

Use a property index for exact scalar equality (city = 'austin'). Use a fulltext index for tokenized text search (BM25, stemming, phrase and prefix queries) over free-text fields.