The default advice in the WordPress AI world is to throw your chunks into a vector database and let it handle search. That is laziness dressed up as architecture, and it gets worse as a site grows. Clients keep asking me why their RAG systems started hallucinating after six months of content updates when the LLM never changed. Usually the answer is HNSW recall degradation.
Pinecone, Milvus, and Qdrant all lean on Hierarchical Navigable Small World (HNSW) graphs because they are fast. After 14 years of debugging race conditions and transient bottlenecks, though, I have learned that fast and wrong is still wrong. HNSW search quality is not fixed. It degrades quietly as your database grows.
The silent failure: why Recall@k matters
Recall, in a production RAG pipeline, is the share of relevant document chunks your retriever actually finds. Miss the context and the LLM has to guess, which is where the confident hallucinations come from. A Flat search (brute force) gives you 100% recall in theory. HNSW only approximates it.
Add enough vectors and the high-dimensional space gets crowded, so HNSW starts skipping the nearest neighbors unless you adjust for it. Nothing gets logged. Latency still looks perfect and the logs still say everything is fine, while the context coming back is garbage. That is HNSW recall degradation.
I reviewed a custom WooCommerce AI integration recently where the developer could not work out why the chatbot had stopped recommending the right products. They were busy optimizing chunk sizes and had never looked at how the search algorithm behaves across 500k product variations.
Tuning the three HNSW knobs
HNSW gives you three levers: M for connections, ef_construction for index depth, and ef_search for how thoroughly a query gets explored. The first two are locked in when you build the index. ef_search is the one you can still change at query time.
Raising ef_search buys recall and costs latency. It is the usual trade-off. If your database has grown 4x, you probably need a higher search factor to hold the same context quality.
<?php
/**
* Example: Dynamically adjusting ef_search to prevent HNSW Recall Degradation
* in a production WordPress RAG integration.
*/
function bbioon_query_vector_db( $vector, $db_size ) {
// Naive approach: Fixed search depth.
// $ef_search = 40;
// Proactive approach: Scale search depth based on database volume.
// This is a heuristic to maintain recall as the corpus grows.
$ef_search = ( $db_size > 100000 ) ? 120 : 64;
$response = wp_remote_post( 'https://your-vector-db.api/search', [
'body' => json_encode([
'vector' => $vector,
'top_k' => 10,
'ef_search' => $ef_search, // The critical parameter
]),
'headers' => [ 'Content-Type' => 'application/json' ]
]);
return json_decode( wp_remote_retrieve_body( $response ) );
}
Moving beyond approximate search
Cranking HNSW parameters up will not save you forever. Past a certain size, vector search gets too noisy on its own, and the fix is a hybrid architecture: filter on metadata first, with something like a SQL category check, so the HNSW index only ever sees a narrowed search space. The Milvus documentation puts it in graph terms: traversal is only as good as the graph’s density relative to your query parameters.
Do not trust a system because it worked on day one, either. Run regular evaluations of your RAG pipeline, treat Flat search as your ground truth, and measure how far the HNSW results have drifted from it.
If this kind of retrieval tuning is eating your dev hours, I can take it on. I have been wrestling with WordPress since the 4.x days, and I know how to keep AI systems stable at scale.
Takeaways
- Stable response times tell you nothing about retrieval quality, so monitor Recall@k rather than milliseconds alone.
- As the vector database grows, your
ef_searchvalue has to grow with it or recall slips. - Run an expensive Flat search now and then to check whether your ANN (Approximate Nearest Neighbor) results still hold up.
- Pre-filter on metadata to cut the crowding effect in your vector space.