When the filter disconnects the graph — pre-filtering HNSW with extra intra-category edges

Pre-filtering an HNSW search means only matching nodes are walkable, but if the path to a valid neighbor runs through a filtered-out node, the greedy walk dead-ends and recall craters — so production engines add extra intra-category edges or two-hop jumps, or fall back to brute force below a cutoff.

Previously

Pre-filtering avoided starvation, but on an HNSW graph it can sever the very paths the greedy walk needs — and now we've seen exactly how: the bridge node got filtered out and the walk dead-ended. Engines fix it with extra edges, two-hop jumps, or a brute-force fallback. But there's a different limit filters can't touch: similarity itself sometimes misses the exact word the user typed.

Scene 12

When the filter disconnects the graph

  1. Watch
  2. Try it
  3. Predict
  4. Capture
acoustic → electroniccalm → energeticLo-fi RainAcoustic Suns…Campfire FolkCoffeehouseIndie DriveSynth DawnNeon CityClub PulseRave PeakBass DropMidnight DriveGarage BeatStudy BeatsLonely SynthNow PlayingPre-filter grays out non-matching songs — the walk needs a node that's gone.RECALL vs LATENCYrecallslower →FlatHNSW (no …Filtered …
What to watch for

Last scene, pre-filtering looked like the safe answer: remove the non-matching songs first, then search only what's left — no starvation. Here is the trap. We're searching the HNSW graph from scenes 6-7, but first we apply the filter 'genre = electronic'. About 80% of the songs don't match, so they gray out and the walk is only allowed to step on the solid ones. Watch the greedy walk start at Indie Drive (#5) and head for the query. To get closer it needs to hop to 'Synth Dawn' (#6) — but #6 isn't electronic, so it was filtered out. With that one node gone, there's no walkable step any closer to the query. The walk dead-ends. And 'Lonely Synth' (#14, red) — which DOES match the filter and sits right next to the query — is stranded on the far side of the missing bridge, never reached.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Index.search
the dispatcher: scan tiny match-sets, else walk the graph
def search(query, predicate, k):
matching = nodes_where(predicate)
# below the cutoff the graph isn't worth it
if len(matching) <= flatSearchCutOff:
return exact_topk(query, matching, k)
return filtered_greedy_walk(
query, matching, k,
)
HNSW.filteredGreedyWalk
best-first hops, but only onto nodes that match the filter
def filtered_greedy_walk(query, matching, k):
frontier = [entry_node] # size ef_search
while frontier.improving():
cur = frontier.closest_to(query)
for nbr in neighbors(cur):
if nbr in matching:
frontier.add(nbr) # walkable
elif two_hop:
for far in neighbors(nbr): # ACORN
if far in matching:
frontier.add(far) # skip the gap
return frontier.topk(k)
HNSW.buildEdges
wiring the graph — filterable HNSW adds same-category links
def build_edges(node):
link(node, nearest_neighbors(node, M))
# filterable HNSW: also link to same-category
# nodes so a walk inside one category never
# depends on a node another filter removes
if filterable_hnsw:
peers = same_category(node)
link(node, nearest(peers, M))

Where this sits in Build a vector database (Pinecone / Weaviate / pgvector style)

Scene 12 of 15, in the Production act — Filters, the graph-disconnection trap, hybrid RRF, and sharded scatter-gather.. Pre-filtering an HNSW search makes only matching nodes walkable — and if the path to a valid neighbor ran through a filtered-out node, the greedy walk dead-ends and recall craters.

Up next. Even perfectly filtered, dense vector search has a blind spot: it captures MEANING, so it nails paraphrases but fumbles exact tokens — a product SKU, an error code, a rare name the embedding never learned. Keyword search nails those but misses meaning. The next idea runs both and fuses their rankings.

All 15 scenes in Build a vector database (Pinecone / Weaviate / pgvector style) · Every curriculum

Built with Arqly
Every scene in Build a vector database (Pinecone / Weaviate / pgvector style) builds on the one before it.All 15 Build a vector database (Pinecone / Weaviate / pgvector style) scenes