Benchmarks

In recorded benchmark runs, the benchmark suite scores Narsil against production search engines on BEIR datasets, and against in-process libraries at matched corpus sizes. A continuous-integration check fails the build whenever this page differs from the raw results.

Synced from the Narsil repository on 5 October 2026. The source of truth lives in BENCHMARKS.md on GitHub.

Narsil benchmarks: one engine, embedded or on a server

Narsil runs two ways from a single codebase. You can embed it inside your application process like a library, and you can run it as a search server that scales across machines. This page measures both, because portability is the goal: the engine that indexes a few thousand documents inside a browser tab is the same engine that answers queries behind an HTTP API.

Every number and every chart on this page comes from a recorded run. A script reads the latest run of each suite and fills the tables below from the raw results, while a second script draws each figure into the run's own directory. A continuous-integration check fails the build if the page or a figure ever differs from those recordings. Each section links to the run it came from so you can read the per-engine detail and reproduce the figures yourself.

Search servers: keyword, vector, and hybrid retrieval

The first comparison runs over HTTP against six production search engines on BEIR datasets, the datasets and metrics that the published information-retrieval leaderboards use. Each engine ingests the corpus, answers the dataset's test queries, writes a TREC run file, and gets scored with pytrec_eval, the same tool the BEIR leaderboard uses. The comparison runs three tracks. The keyword track scores BM25 ranking. The vector track scores dense nearest-neighbour search. The hybrid track scores keyword and vector combined. On the vector and hybrid tracks every engine receives identical precomputed vectors from one fixed embedding model, so the comparison measures the index and holds the embedder constant.

Narsil calibrates its BM25 against the Anserini reference configuration, so the rest of the comparison stands on a trusted baseline. The setup, the pinned engine versions, and the datasets all come from the recorded run.

  • Run. These figures come from run 20260928T002255Z, recorded on 2026-09-28 from commit 5dc940c70724. The raw per-engine results and the full comparison are in the run report.
  • Datasets. The harness measured SciFact (5,183 documents), NFCorpus (3,633 documents), DBpedia entities 100K (100,000 documents), and DBpedia entities 1M (995,000 documents). SciFact is loaded and hash-verified through ir_datasets, with sentence-transformers/all-MiniLM-L6-v2 vectors at 384 dimensions; NFCorpus is loaded and hash-verified through ir_datasets, with sentence-transformers/all-MiniLM-L6-v2 vectors at 384 dimensions; DBpedia entities 100K is read from a published dataset artifact pinned by its SHA-256, with text-embedding-ada-002 vectors at 1,536 dimensions; DBpedia entities 1M is read from a published dataset artifact pinned by its SHA-256, with text-embedding-ada-002 vectors at 1,536 dimensions.
  • Engines. The comparison runs Narsil 0.3.0 against Elasticsearch 9.5.4, Meilisearch 1.54.0, OpenSearch 3.8.0, Qdrant 1.19.1, Typesense 30.2, and Weaviate 1.39.5, and every engine runs from a pinned image.
  • Equal conditions. Every engine receives the same 20 GiB memory cap, the same run depth of 1,000, and the same run-file ordering. The harness tests one engine at a time, so each engine has the machine to itself. Each Java engine divides that cap between its heap and the memory outside it. Elasticsearch reports a 10 GiB heap and OpenSearch reports a 6 GiB heap.
  • Load. The harness measures throughput at 1, 2, 4, 8, 16, 32, and 64 concurrent clients, with one pass per level and 3 passes at each engine's peak level. The tables report the median peak pass with a 95% bootstrap interval. The load generator shares the machine with the engine under test, so its client processes take CPU time that the engine could otherwise use. The harness measures every engine under that same arrangement.
  • Narsil threads. Narsil started at the engine defaults, and the harness read 7 worker threads, 7 request threads receiving requests, and the benchmark index scaled out across them.
  • Narsil vector search. Narsil's server reports that it searches vector graphs through its native search core in C, which npm installs with the package on Node.js for macOS, Linux, and Windows.
  • Machine. The engines ran on 3 machines: Narsil, Elasticsearch, and OpenSearch on GCP c3-standard-8, us-central1-a, which reports Intel(R) Xeon(R) Platinum 8481C CPU @ 2.70GHz and Linux 7.0.0-1011-gcp x86_64, Meilisearch on GCP c3-standard-8, us-west1-a, which reports Intel(R) Xeon(R) Platinum 8481C CPU @ 2.70GHz and Linux 7.0.0-1011-gcp x86_64, and Qdrant, Typesense, and Weaviate on GCP c3-standard-8, us-east1-b, which reports Intel(R) Xeon(R) Platinum 8481C CPU @ 2.70GHz and Linux 7.0.0-1011-gcp x86_64.
  • BM25 calibration. Narsil indexes each corpus with BM25 k1=0.9 and b=0.4, the Anserini reference configuration.

Each track below opens with two bar charts per dataset, ranking quality and peak throughput, followed by the table behind them. Where a run measured more than one concurrency level, a line chart then follows each engine across those levels, and the setup block above says which levels a run measured. A latency profile at the end of the track shows how far each engine's tail stretches at its own peak. That under-load latency comes from the throughput passes, because their five-second windows hold several times more samples than the serial track, so p99.9 rests on more than the slowest one or two requests.

Keyword track

Narsil's BM25 is calibrated to the Anserini reference, so it ranks with the Lucene engines on these graded judgements and finishes a little ahead of them on both datasets. Typesense and Meilisearch apply their own documented ranking models in place of BM25, which places them lower here.

SciFact.

Two bar panels for the keyword track on SciFact: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EnginenDCG@10Recall@100MAPMRRPeak QPS
Narsil0.68140.92530.64170.64944,200 (4,169 to 4,212) (client-limited)
Elasticsearch0.67890.92530.64010.65062,796 (2,750 to 2,798)
OpenSearch0.67890.92530.64010.65063,027 (2,989 to 3,031)
Typesense0.54070.75010.50810.5191331 (331 to 333)
Meilisearch0.50180.59820.48090.48891,559 (1,551 to 1,565)
Line panels for the keyword track on SciFact against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

NFCorpus.

Two bar panels for the keyword track on NFCorpus: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EnginenDCG@10Recall@100MAPMRRPeak QPS
Narsil0.32780.24890.15320.53054,940 (4,719 to 4,950)
Elasticsearch0.32060.24570.15030.52553,128 (3,128 to 3,137)
OpenSearch0.32060.24570.15030.52553,252 (3,250 to 3,261)
Meilisearch0.26710.15530.12210.44432,428 (2,419 to 2,463)
Typesense0.22370.20750.11010.38682,376 (2,365 to 2,379)
Line panels for the keyword track on NFCorpus against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 100K.

One bar panel for the keyword track on DBpedia entities 100K: peak queries per second per engine with a 95% confidence interval across passes.
EnginePeak QPS
Narsil4,397 (4,327 to 4,401) (client-limited)
OpenSearch3,242 (3,232 to 3,245)
Elasticsearch3,100 (3,075 to 3,118)
Meilisearch2,039 (2,037 to 2,044)
Typesense1,908 (1,906 to 1,913)
Line panels for the keyword track on DBpedia entities 100K against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 1M.

One bar panel for the keyword track on DBpedia entities 1M: peak queries per second per engine with a 95% confidence interval across passes.
EnginePeak QPS
OpenSearch2,994 (2,956 to 2,999)
Narsil2,842 (2,758 to 2,888)
Elasticsearch2,782 (2,773 to 2,785)
Typesense1,782 (1,782 to 1,798)
Meilisearch1,144 (1,134 to 1,149)
Line panels for the keyword track on DBpedia entities 1M against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Server-side latency at p50, p95, p99, p99.9, and the maximum for each engine on the keyword track at its peak concurrency level, one panel per dataset, on a logarithmic scale. An engine that reports no server-side time is absent, and a whole-millisecond timer leaves out the points it floors to zero.

Vector track

Every engine indexes the identical vectors and tunes its search effort up to the same matched recall point against the exact nearest neighbours. Retrieval quality is therefore equal across engines by construction, so this track compares speed at that point. The throughput differences at a few thousand vectors reflect per-request handling at this corpus size, since every engine sits near full recall at a modest search effort. Where the run recorded throughput at every step of the recall sweep, a further chart draws queries per second against recall, so you can see how much throughput each engine gives up for the last point of recall.

On SciFact, every engine tunes its search effort to reach ann_recall@10 of at least 0.99 against the exact neighbours, and each returns the same ranking, so nDCG@10 is 0.6239 and Recall@100 is 0.9227 across the field. On NFCorpus, every engine tunes its search effort to reach ann_recall@10 of at least 0.99 against the exact neighbours, and each returns the same ranking, so nDCG@10 is 0.3145 and Recall@100 is 0.3094 across the field. On DBpedia entities 100K, every engine tunes its search effort to reach ann_recall@10 of at least 0.99 against the exact neighbours. The set carries no relevance judgements, so it reports recall, latency, and throughput and no ranking quality. On DBpedia entities 1M, every engine tunes its search effort to reach ann_recall@10 of at least 0.99 against the exact neighbours. The set carries no relevance judgements, so it reports recall, latency, and throughput and no ranking quality.

SciFact.

Two bar panels for the vector track on SciFact: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
NarsilefSearch 640.99432,824 (2,820 to 2,837) (client-limited)
Qdranthnsw_ef 320.99372,413 (2,411 to 2,417) (client-limited)
OpenSearchef_search 640.99572,300 (2,297 to 2,304)
Elasticsearchnum_candidates 640.99402,235 (2,208 to 2,236)
Weaviateef 640.99671,233 (1,222 to 1,234)
Line panels for the vector track on SciFact against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Queries per second against ANN recall@10 on SciFact, one point per search-effort level per engine, with equal precision drawn solid and recommended production settings dashed.

NFCorpus.

Two bar panels for the vector track on NFCorpus: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
NarsilefSearch 1280.99412,744 (2,744 to 2,752)
Qdranthnsw_ef 640.99602,359 (2,354 to 2,364)
OpenSearchef_search 1280.99472,253 (2,244 to 2,253)
Elasticsearchnum_candidates 1920.99042,168 (2,152 to 2,176)
Weaviateef 1920.99571,172 (1,169 to 1,172)
Line panels for the vector track on NFCorpus against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Queries per second against ANN recall@10 on NFCorpus, one point per search-effort level per engine, with equal precision drawn solid and recommended production settings dashed.

DBpedia entities 100K.

One bar panel for the vector track on DBpedia entities 100K: peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
OpenSearchef_search 1280.99181,211 (1,209 to 1,216)
NarsilefSearch 1280.99071,197 (1,194 to 1,203)
Elasticsearchnum_candidates 7680.9922986 (980 to 996)
Qdranthnsw_ef 1280.9942841 (833 to 841)
Weaviateef 1920.9931467 (467 to 469)
Line panels for the vector track on DBpedia entities 100K against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Queries per second against ANN recall@10 on DBpedia entities 100K, one point per search-effort level per engine, with equal precision drawn solid and recommended production settings dashed.

DBpedia entities 1M.

One bar panel for the vector track on DBpedia entities 1M: peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
OpenSearchef_search 3840.9940869 (869 to 872)
NarsilefSearch 3840.9936670 (663 to 675)
Elasticsearchnum_candidates 20480.9921609 (609 to 614)
Qdranthnsw_ef 1920.9931525 (522 to 529)
Weaviateef 5120.9942362 (359 to 371)
Line panels for the vector track on DBpedia entities 1M against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Queries per second against ANN recall@10 on DBpedia entities 1M, one point per search-effort level per engine, with equal precision drawn solid and recommended production settings dashed. Server-side latency at p50, p95, p99, p99.9, and the maximum for each engine on the vector track at its peak concurrency level, one panel per dataset, on a logarithmic scale. An engine that reports no server-side time is absent, and a whole-millisecond timer leaves out the points it floors to zero.

Hybrid track

Hybrid fusion combines the keyword and vector rankings, and the fusion method differs per engine, so ranking quality varies again.

SciFact.

Two bar panels for the hybrid track on SciFact: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EnginenDCG@10Recall@100MAPMRRPeak QPS
Qdrant0.71410.95770.67220.67622,238 (2,237 to 2,248)
Elasticsearch0.70530.96100.65870.66431,753 (1,749 to 1,754)
OpenSearch0.70530.96100.65870.66431,775 (1,771 to 1,781)
Narsil0.70260.96430.65430.66152,602 (2,588 to 2,604) (client-limited)
Weaviate0.68030.95770.63160.6410679 (670 to 680)
Line panels for the hybrid track on SciFact against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

NFCorpus.

Two bar panels for the hybrid track on NFCorpus: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EnginenDCG@10Recall@100MAPMRRPeak QPS
Narsil0.35600.32390.18780.57452,633 (2,619 to 2,639) (client-limited)
OpenSearch0.35220.32150.18670.56491,934 (1,931 to 1,937)
Elasticsearch0.35210.32150.18670.56491,859 (1,858 to 1,873)
Qdrant0.34990.32350.18260.56402,234 (2,230 to 2,234)
Weaviate0.34250.31950.18080.5548766 (765 to 767)
Line panels for the hybrid track on NFCorpus against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 100K.

One bar panel for the hybrid track on DBpedia entities 100K: peak queries per second per engine with a 95% confidence interval across passes.
EnginePeak QPS
Narsil1,187 (1,185 to 1,190)
OpenSearch1,110 (1,109 to 1,114)
Elasticsearch907 (901 to 912)
Qdrant777 (776 to 781)
Weaviate396 (395 to 402)
Line panels for the hybrid track on DBpedia entities 100K against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 1M.

One bar panel for the hybrid track on DBpedia entities 1M: peak queries per second per engine with a 95% confidence interval across passes.
EnginePeak QPS
OpenSearch792 (791 to 795)
Narsil617 (616 to 621)
Elasticsearch570 (566 to 572)
Qdrant489 (488 to 495)
Weaviate307 (301 to 307)
Line panels for the hybrid track on DBpedia entities 1M against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Server-side latency at p50, p95, p99, p99.9, and the maximum for each engine on the hybrid track at its peak concurrency level, one panel per dataset, on a logarithmic scale. An engine that reports no server-side time is absent, and a whole-millisecond timer leaves out the points it floors to zero.

The tracks above hold every engine at full float so that the comparison isolates the index. Each engine also recommends a quantisation for production, and this second pass runs the vector and hybrid tracks under those settings. The tables name the quantisation each engine applied once a run records it. Every engine still tunes its search effort to the same recall target, so compression differs by engine on purpose while the accuracy bar stays fixed. The equal-precision figures above remain the headline.

SciFact.

Two bar panels for the vector track on SciFact: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
Narsil (OSQ 4-bit)efSearch 160.99272,782 (2,780 to 2,798) (client-limited)
Qdrant (TurboQuant 4-bit)hnsw_ef 320.99372,464 (2,463 to 2,477) (client-limited)
OpenSearch (SQfp16)ef_search 640.99672,331 (2,330 to 2,332)
Elasticsearch (BBQ)num_candidates 160.99132,177 (2,171 to 2,210)
Weaviate (8-bit RQ)ef 640.99571,139 (1,135 to 1,141)
Line panels for the vector track on SciFact against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

NFCorpus.

Two bar panels for the vector track on NFCorpus: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
Narsil (OSQ 4-bit)efSearch 1280.99262,729 (2,699 to 2,733)
Qdrant (TurboQuant 4-bit)hnsw_ef 640.99412,424 (2,421 to 2,425) (client-limited)
OpenSearch (SQfp16)ef_search 1280.99352,299 (2,295 to 2,301)
Elasticsearch (BBQ)num_candidates 81920.98482,090 (2,083 to 2,094)
Weaviate (8-bit RQ)ef 1280.99201,119 (1,117 to 1,124)
Line panels for the vector track on NFCorpus against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 100K.

One bar panel for the vector track on DBpedia entities 100K: peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
Narsil (OSQ 4-bit)efSearch 1280.99191,366 (1,347 to 1,374)
OpenSearch (1-bit binary)ef_search 1920.99281,339 (1,337 to 1,344) (client-limited)
Qdrant (TurboQuant 4-bit)hnsw_ef 1280.99401,278 (1,272 to 1,283)
Elasticsearch (BBQ)num_candidates 20480.99251,139 (1,137 to 1,141)
Weaviate (8-bit RQ)ef 1920.9927451 (450 to 454)
Line panels for the vector track on DBpedia entities 100K against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 1M.

One bar panel for the vector track on DBpedia entities 1M: peak queries per second per engine with a 95% confidence interval across passes.
EngineSearch effortANN recall@10Peak QPS
Narsil (OSQ 4-bit)efSearch 3840.99421,154 (1,154 to 1,177)
OpenSearch (1-bit binary)ef_search 10240.99161,111 (1,109 to 1,117)
Elasticsearch (BBQ)num_candidates 20480.99171,075 (1,073 to 1,079)
Qdrant (TurboQuant 4-bit)hnsw_ef 1920.99291,073 (1,070 to 1,077)
Weaviate (8-bit RQ)ef 3840.9922406 (404 to 412)
Line panels for the vector track on DBpedia entities 1M against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Server-side latency at p50, p95, p99, p99.9, and the maximum for each engine on the vector track at its peak concurrency level, one panel per dataset, on a logarithmic scale. An engine that reports no server-side time is absent, and a whole-millisecond timer leaves out the points it floors to zero.

SciFact.

Two bar panels for the hybrid track on SciFact: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EnginenDCG@10Recall@100MAPMRRPeak QPS
Qdrant (TurboQuant 4-bit)0.71410.95770.67220.67622,280 (2,265 to 2,289)
Elasticsearch (BBQ)0.70530.96100.65870.66431,734 (1,729 to 1,740)
OpenSearch (SQfp16)0.70530.96100.65870.66431,818 (1,797 to 1,822)
Narsil (OSQ 4-bit)0.70260.96430.65430.66152,565 (2,546 to 2,581) (client-limited)
Weaviate (8-bit RQ)0.68030.95770.63160.6410625 (624 to 627)
Line panels for the hybrid track on SciFact against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

NFCorpus.

Two bar panels for the hybrid track on NFCorpus: nDCG@10 per engine, and peak queries per second per engine with a 95% confidence interval across passes.
EnginenDCG@10Recall@100MAPMRRPeak QPS
Narsil (OSQ 4-bit)0.35600.32390.18780.57452,628 (2,628 to 2,645) (client-limited)
Elasticsearch (BBQ)0.35190.32150.18670.56341,818 (1,817 to 1,825)
OpenSearch (SQfp16)0.35140.32160.18640.56181,999 (1,982 to 2,000)
Qdrant (TurboQuant 4-bit)0.35020.32360.18260.56622,290 (2,286 to 2,294)
Weaviate (8-bit RQ)0.34250.31930.18080.5534709 (708 to 713)
Line panels for the hybrid track on NFCorpus against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 100K.

One bar panel for the hybrid track on DBpedia entities 100K: peak queries per second per engine with a 95% confidence interval across passes.
EnginePeak QPS
Narsil (OSQ 4-bit)1,335 (1,329 to 1,336)
OpenSearch (1-bit binary)1,239 (1,232 to 1,246)
Qdrant (TurboQuant 4-bit)1,194 (1,194 to 1,200)
Elasticsearch (BBQ)1,024 (1,023 to 1,027)
Weaviate (8-bit RQ)358 (357 to 361)
Line panels for the hybrid track on DBpedia entities 100K against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them.

DBpedia entities 1M.

One bar panel for the hybrid track on DBpedia entities 1M: peak queries per second per engine with a 95% confidence interval across passes.
EnginePeak QPS
Narsil (OSQ 4-bit)1,017 (1,014 to 1,018)
OpenSearch (1-bit binary)963 (963 to 967)
Qdrant (TurboQuant 4-bit)912 (909 to 915)
Elasticsearch (BBQ)873 (872 to 884)
Weaviate (8-bit RQ)291 (290 to 294)
Line panels for the hybrid track on DBpedia entities 1M against concurrent clients: queries per second with a confidence band, server-side p99 latency under load on a logarithmic scale for the engines that report their own query time, and the engine container's busy cores where the harness recorded them. Server-side latency at p50, p95, p99, p99.9, and the maximum for each engine on the hybrid track at its peak concurrency level, one panel per dataset, on a logarithmic scale. An engine that reports no server-side time is absent, and a whole-millisecond timer leaves out the points it floors to zero.

A note on latency

Throughput under concurrent load is the headline speed measure here, because single-query latency cannot separate these engines at a few thousand documents. Narsil reports its server-side query time in floating milliseconds, so its sub-millisecond searches are recorded exactly. Elasticsearch, OpenSearch, Meilisearch, and Typesense report whole milliseconds, so their sub-millisecond searches fall below what their own timers can resolve, and the latency charts leave out the points those timers floor to zero. Weaviate exposes no server-side query time, so only its client round-trip is recorded and it is absent from the server-side latency charts. The linked run report carries the full latency tables, both server-side and client round-trip.

Embedded search: in-process against Orama and MiniSearch

The same engine also runs as a library inside one Node.js process, with no server and no network, against Orama and MiniSearch. This is the embedded class, where Narsil indexes and queries in the same process as your application code. The speed tiers run on a BEIR corpus, and ranking quality is scored on BEIR SciFact with its human relevance judgements. All three engines use the same Lucene English stop words and default BM25 parameters. Each one stems English with its own implementation, which no shared setting overrides, so a ranking gap between them carries both the ranking and the stemmer.

  • Run. These figures come from run 20260928T003010Z, recorded on 2026-09-28 from commit 5dc940c70724. The full per-scale tables are in the run report.
  • Engines. The comparison runs Narsil 0.3.0 against Orama 3.1.18 and MiniSearch 7.2.0, all inside one Node.js process.
  • Threads. Every engine answers on one thread. Narsil runs with workers.enabled off, so it holds no worker copies here, and the server comparison above is where its worker threads take part.
  • Vector search path. Narsil searches vector graphs through WebAssembly in this suite, which is the path that it takes in a browser. The suite sets NARSIL_SEARCH_BACKEND=wasm, so these figures exclude the native search core that npm installs with the package on Node.js.
  • Machine. GCP c3-standard-8, us-west1-a hosted this run, and it reports Intel(R) Xeon(R) Platinum 8481C CPU @ 2.70GHz, 31GB of memory, Node.js v24.21.0, and Linux x64.
  • Speed corpus. The indexing and query tiers run on BEIR FiQA, 50,000 documents, measured at 1,000, 10,000, and 50,000 documents.
  • Relevance dataset. Ranking quality is scored on BEIR SciFact, 5,183 documents and 300 judged queries, verified by archive checksum 536e14446a0b.

Ranking quality

Ranking quality on BEIR SciFact, higher is better:

EnginenDCG@10P@10MAPMRR
Narsil0.68400.09030.63550.6476
Orama0.43510.06570.37470.3845
MiniSearch0.25060.03730.21630.2198

Indexing and query speed

The suite records indexing throughput, query latency, and memory at each corpus scale, and it measures filtered search where the engine supports it. Memory counts the JavaScript heap plus the memory the process holds outside it, so a vector an engine keeps in a typed array or in WebAssembly memory counts the same as one it keeps as JavaScript objects. Narsil runs this track with trackPositions: false, because the suite measures no feature that reads term positions, and with workers.enabled off, because Orama and MiniSearch answer on one thread and this table compares one thread. The server comparison above runs Narsil with its defaults, which keep positions on and hold a worker copy on every thread.

Line panels for the embedded engines across corpus size: insert documents per second, search p50 latency on a logarithmic scale, and, where the suite recorded it, heap plus external memory.

Insert throughput at each scale, documents per second:

Engine1,00010,00050,000
Narsil9,2208,8197,944
Orama4,3224,0923,659
MiniSearch7,9626,9506,221

Search latency at each scale, p50 milliseconds:

Engine1,00010,00050,000
Narsil0.0480.0750.125
Orama0.0671.36016.376
MiniSearch0.0710.5954.334

Heap plus external memory at each scale, megabytes:

Engine1,00010,00050,000
Narsil11.357.5214.7
Orama11.587.3398.2
MiniSearch6.741.6175.1

Filtered search latency at 50,000 documents, p50 milliseconds:

EngineFiltered search p50 ms
Narsil0.447
Orama7.913
MiniSearchnot supported

Narsil carries vector search in the same embedded engine. MiniSearch has no vector support, so this tier compares Narsil against Orama.

Embedded vector search on BEIR SciFact:

EngineRecall@10Insert docs/sSearch p50 msHeap plus external MB
Narsil100.0%83,5231.56737.9
Orama100.0%164,0023.72710.5

Embedded vector search on BEIR NFCorpus:

EngineRecall@10Insert docs/sSearch p50 msHeap plus external MB
Narsil100.0%69,7901.09938.5
Orama100.0%164,6012.5867.2

Reproduce these numbers

  • Search servers. The only requirement is Docker. From benchmarks/server/, run ./run-all.sh. The harness builds the Narsil server from this repository, embeds every corpus once into a shared cache, runs each engine one at a time under equal precision and again under its recommended production settings, and writes a fresh run directory under benchmarks/server/results/runs/. The server benchmark README covers the configuration and the large-dataset path.
  • Embedded libraries. From the repository root, run pnpm build, then pnpm --filter benchmarks bench. The in-process benchmark README lists the tiers and the single-tier commands.
  • This page. After a run, python3 benchmarks/writeup/charts.py draws every figure into the run's own charts/ directory, and python3 benchmarks/writeup/generate.py rewrites the tables above from the latest recorded run of each suite. python3 benchmarks/writeup/generate.py --check verifies that the page and its figures match those runs, and continuous integration runs the same check.

Absolute numbers move with the hardware, so the value is in the comparison between engines measured on the same machine in the same run.