How fast is hybrid search?
October 2026 · 6 min read
Hybrid search — run a keyword (BM25) leg and a vector leg, then fuse their rank lists with Reciprocal Rank Fusion — is the safe default for most product search, site search and RAG retrieval. The usual objection is cost: surely running two retrievals and fusing them is slow? On the Index Server, measured on a 16-core Graviton box, a hybrid query returns in 3.64 ms at the median — about 2× faster than the only two other engines in our comparison that can run server-side hybrid at all, and within noise of its own pure-vector latency. The keyword leg is effectively free.
Below are the numbers, the honest version of the ingest story (where the real bottleneck is, and it isn't the index), and a recipe you can run yourself.
The query numbers
20,000 documents, each a ~30-word body plus a 384-dim vector; k=10;
RRF fusion; 200 timed iterations after 20 warmups; client on the same host over
loopback. Only engines with server-side BM25 + fusion can run this leg —
Qdrant and Chroma have no ranked keyword fusion, so they sit it out.
| engine | text+vector ingest | hybrid p50 | hybrid p95 |
|---|---|---|---|
| Index Server 1.6.0 | 4,305 docs/s | 3.64 ms | 3.78 ms |
| OpenSearch 3.8.0 | 1,470 docs/s | 6.42 ms | 6.94 ms |
| Weaviate | 2,159 docs/s | 8.54 ms | 9.00 ms |
Two things worth sitting with. First, the p95 is 3.78 ms — the tail is tight, not just the median, which is what actually matters under load. Second, the Index Server's pure kNN query on the same box is 3.88 ms p50; hybrid comes in at 3.64. In other words, adding the BM25 leg and fusing costs nothing measurable over doing vector search alone. You are not trading latency for the recall that keyword matching buys you — you get both.
Why it's free
The reason hybrid is cheap here is that both legs run server-side, over one index. Text is stored once with a BM25 inverted index; the vector lives in the same document. A hybrid query is a single clause — the engine runs the two retrievals, fuses the rank lists with RRF, and returns — with no second datastore to round-trip to and no client-side score juggling across BM25 and cosine scales. It's one request on the same OpenSearch REST API the rest of your stack already speaks:
POST /articles/_search
{ "query": { "hybrid": { "queries": [
{ "multi_match": { "query": "renewable energy policy",
"fields": ["title^2", "body"] } },
{ "neural": { "title_vec": {
"query_text": "renewable energy policy", "k": 10 } } }
] } } }
Compare that to the two-system pattern it replaces — a keyword engine, a separate vector database, and glue code that queries both and reconciles scores in the client. That glue is where the latency (and the bugs) usually live.
The honest ingest story
The table shows 4,305 documents per second for combined text + vector indexing — about 2.9× OpenSearch and 2× Weaviate. That figure is the index doing its job: the vectors are supplied with the documents and the engine builds the inverted index and the HNSW graph. If you only index text (no vectors), the same engine sustains ~18,600 docs/s. The index is not your bottleneck.
The bottleneck, when there is one, is producing the vectors — turning
text or images into embeddings. 1.6.0 does that in-process
(embedding.native=true), so there's no network hop to a separate
inference service, and it lets each index pick its model: a small, fast
text embedding model where that's all you need, or a multimodal
one when you're embedding images too. That choice is the single biggest lever on
ingest speed — a small text model embeds roughly 50× faster than a
large multimodal one. So the honest guidance is: the index will keep up with
thousands of docs a second; size your embedding model to the job and that
ceiling is yours. (If you precompute vectors in a batch pipeline, you hit the
4,305 docs/s indexing number directly.)
Run it yourself
Install the server (one line, no external services):
curl -fsSL https://index-server.searchblox.com/install | sudo bash
Create an index with a text field and a vector field, and turn on in-process embedding so the server vectors your text at ingest:
PUT /articles
{ "mappings": { "properties": {
"title": { "type": "text" },
"body": { "type": "text" },
"title_vec": { "type": "knn_vector", "dimension": 384,
"embed": { "source_field": "title", "model": "text" } }
} } }
Bulk-load your documents (text only — the title_vec is filled in
at ingest), then fire the hybrid query above. The admin console's
Compare search strategies tab will run keyword, semantic, hybrid and
reranked side by side on your own data, with per-strategy latency, so you can
confirm these numbers on your corpus rather than ours.
The fine print
One box — an AWS c8g.4xlarge (Graviton4, 16 vCPU, 32 GB), Amazon
Linux 2023, gp3 EBS. Engines ran strictly one at a time with wiped data
directories; clients ran on the same host over loopback (no network variance),
auth off on every engine for parity. HNSW at m=24,
ef_construction=128. The full five-engine methodology — vector,
hybrid, full-text and memory, with recall measured against brute-force ground
truth — is published on the benchmarks page, and
the trade-offs between keyword, semantic, hybrid and reranking are in
One engine, four ways to search.
Hybrid is a plain query clause on the documented
REST API; nothing here needs a second system.