Summary
lib/Search.js currently performs an extra GET /collections/:name request during search() whenever the caller provides q but omits query_by, so the client can infer searchable_fields before issuing the actual search. In a high-performance search client, this hidden round trip turns one logical search into two network calls, increases tail latency, and adds avoidable load to the cluster.
Context
The hot path is in lib/Search.js inside _searchCollection(), where this.collections.get(collectionName) is called to populate queryParams.query_by if the caller did not pass it explicitly. That behavior is convenient, but it is expensive in the exact path that should be cheapest: user-facing query execution.
This is especially relevant now because the recent changes expand search-facing workflows and documentation, including SAM search/status usage in lib/SAM.js and README.md. As adoption grows, the current fallback will amplify p95/p99 latency and double request volume for callers who rely on the client’s inferred defaults.
For a RocksDB-backed search system, the client should avoid adding synchronous control-plane lookups to the query path unless absolutely necessary. Metadata lookup belongs in a cacheable preparation step, not on every request.
Proposed Implementation
- Add a collection schema cache keyed by collection name, storing at least
searchable_fields and a timestamp/version.
- Resolve
query_by from that cache in Search._searchCollection() instead of calling collections.get() on every search.
- Populate or refresh the cache on collection-oriented operations in
lib/Collections.js such as get(), create(), and update(), and invalidate on delete().
- Add a configurable cache strategy:
- default in-memory caching with TTL
- optional
disable_schema_cache for strict callers
- optional
strict_query_by mode that throws when query_by is missing and cache is cold
- Add tests covering:
- repeated searches without
query_by only trigger one schema fetch
- cache invalidation after schema updates
- graceful behavior on cold cache and metadata fetch failure
Impact
Addressing this removes an avoidable network hop from a core read path, which should reduce median and tail search latency, cut control-plane traffic, and make throughput scale more cleanly under load. It also makes client behavior more predictable: a search request should not silently depend on a second synchronous API call unless the caller explicitly asked for that tradeoff.
Summary
lib/Search.jscurrently performs an extraGET /collections/:namerequest duringsearch()whenever the caller providesqbut omitsquery_by, so the client can infersearchable_fieldsbefore issuing the actual search. In a high-performance search client, this hidden round trip turns one logical search into two network calls, increases tail latency, and adds avoidable load to the cluster.Context
The hot path is in
lib/Search.jsinside_searchCollection(), wherethis.collections.get(collectionName)is called to populatequeryParams.query_byif the caller did not pass it explicitly. That behavior is convenient, but it is expensive in the exact path that should be cheapest: user-facing query execution.This is especially relevant now because the recent changes expand search-facing workflows and documentation, including SAM search/status usage in
lib/SAM.jsandREADME.md. As adoption grows, the current fallback will amplify p95/p99 latency and double request volume for callers who rely on the client’s inferred defaults.For a RocksDB-backed search system, the client should avoid adding synchronous control-plane lookups to the query path unless absolutely necessary. Metadata lookup belongs in a cacheable preparation step, not on every request.
Proposed Implementation
searchable_fieldsand a timestamp/version.query_byfrom that cache inSearch._searchCollection()instead of callingcollections.get()on every search.lib/Collections.jssuch asget(),create(), andupdate(), and invalidate ondelete().disable_schema_cachefor strict callersstrict_query_bymode that throws whenquery_byis missing and cache is coldquery_byonly trigger one schema fetchImpact
Addressing this removes an avoidable network hop from a core read path, which should reduce median and tail search latency, cut control-plane traffic, and make throughput scale more cleanly under load. It also makes client behavior more predictable: a search request should not silently depend on a second synchronous API call unless the caller explicitly asked for that tradeoff.