ZBSearch vs Orama

Head-to-head performance and search-quality benchmarks comparing ZBSearch and Orama on the same workloads.

ZBSearch is a fork of Orama maintained by the original engineering team. The API is compatible, but the internals have been optimized - including an explicit postings list inverted index that lowers memory use and serialized bundle size while keeping search fast, a faster full-text insert path, and an optional IVF vector index. This page summarizes benchmark results from the benchmarks/ suite in this repository.

Test environment

Results below were collected using Orama 3.1.18 and ZBSearch 4.0.0 on August 11, 2026 (Node v24.16.0, Apple Silicon), via Benny for throughput, isolated Node.js processes (--expose-gc) for memory, and the BEIR harness for search quality. Higher ops/s is better. Percentages show how much slower the loser is relative to the winner in each suite.

Summary

AreaVerdict
Full-text searchZBSearch is 7–56% faster on search; indexing is ~42–43% faster
Search quality (BEIR)ZBSearch scores 2× Orama's macro nDCG@10 (0.453 vs 0.222) at 3× lower query latency
FacetsZBSearch is 27–57% faster across all facet scenarios
Vector searchZBSearch is 13–33% faster on flat indexes; IVF reaches 3.2–5.3× Orama's throughput
Geosearch (BKD)ZBSearch is up to ~75× faster on tight-radius queries
Sorted indexes (AVL)ZBSearch is faster on every AVL operation (2–54%)
Memory footprintZBSearch uses ~20% less heap and ~41% less RSS for the same indexed dataset
Persistence sizeZBSearch produces a 42% smaller serialized index (36% smaller gzipped)
JS bundle sizeOrama wins: 22.20 KB vs 27.12 KB min+gzip

Head-to-head: 20 metrics, one run

The benchmark:compare suite runs both engines through the same 20 workloads in a single process. ZBSearch 4.0.0 wins 18; Orama wins the two bundle-size metrics.

MetricOrama 3.1.18ZBSearch 4.0.0Δ (ZB / Orama)
Indexing (insert one-by-one)68.66 ms40.23 ms-41.4%
Indexing (insertMultiple)59.73 ms39.94 ms-33.1%
Prefix search (simple)130.84k/s165.13k/s+26.2%
Exact match search338.01k/s528.29k/s+56.3%
Plain full-text search33.51k/s83.98k/s+150.6%
Typo-tolerant search7.10k/s12.86k/s+81.1%
Search with filters23.99k/s28.82k/s+20.1%
Complex query + filters7.36k/s14.29k/s+94.2%
Search with facets8.61k/s11.87k/s+37.9%
Facets + filters4.29k/s7.15k/s+66.7%
Field boosting32.25k/s41.81k/s+29.6%
Search with results pinning32.71k/s35.19k/s+7.6%
Geosearch (radius)5.08k/s16.10k/s+217.1%
Geosearch + text + filters5.27k/s16.96k/s+222.1%
Remove 100 documents68.39 ms39.23 ms-42.6%
Memory footprint (heap delta)14.69 MB11.68 MB-20.4%
Memory footprint (RSS delta)22.27 MB12.20 MB-45.2%
Serialized index size (JSON)4.98 MB2.88 MB-42.1%
JS bundle size (minified)66.21 KB82.18 KB+24.1%
JS bundle size (min+gzip)22.20 KB27.12 KB+22.2%

Speed metrics are ops/sec (higher is better); indexing/remove are median latency and memory/bundle are bytes (lower is better).

Dataset: 1,512 video-game records with title, description, rating, and genres fields. Search benchmarks run against a database populated once before timing; insert benchmarks create a fresh database on each iteration.

BenchmarkOrama 3.1.18ZBSearch 4.0.0Difference
Insert15.2 ops/s26.1 ops/sZBSearch 41.8% faster
Insert multiple14.5 ops/s25.3 ops/sZBSearch 42.7% faster
Plain search31,928 ops/s73,090 ops/sZBSearch 56.3% faster
Search with filters24,649 ops/s26,575 ops/sZBSearch 7.2% faster
Long text + complex filters7,157 ops/s14,405 ops/sZBSearch 50.3% faster

ZBSearch is faster across all search scenarios tested here, with the largest gaps on plain search and long-text queries with complex filters. Indexing is also substantially faster (~1.7× Orama) thanks to node-local postings caches and a tighter tokenize/insert path.

Search quality (BEIR)

Throughput without relevance is meaningless, so ranking quality is measured on standard BEIR collections with official relevance judgments, using exact trec_eval semantics. Both engines run with their own ecosystem's stopwords and stemmer. The published Lucene BM25 nDCG@10 is shown as a reference point.

DatasetLucene refZBSearch nDCG@10Orama nDCG@10ZBSearch ms/queryOrama ms/query
SciFact0.6650.6750.3241.13.7
NFCorpus0.3250.3070.2120.20.6
ArguAna0.4140.3780.13012.439.2
Macro average-0.4530.2228.827.8

ZBSearch's BM25 lands in the same band as the Lucene reference on every dataset (and above it on SciFact), while Orama scores less than half of ZBSearch's macro average - at roughly 3× the per-query latency. Full per-engine tables (including MiniSearch, FlexSearch, Lunr, and Fuse.js) are on the benchmarks page.

Facets

BenchmarkOrama 3.1.18ZBSearch 4.0.0Difference
Search with facets8,600 ops/s11,782 ops/sZBSearch 27.0% faster
Facets (all documents)2,644 ops/s4,741 ops/sZBSearch 44.2% faster
Facets + filters4,471 ops/s7,869 ops/sZBSearch 43.2% faster
Facets + long text + filters4,558 ops/s10,662 ops/sZBSearch 57.2% faster

Dataset: 2,000 documents, 128-dimensional vectors.

BenchmarkOrama 3.1.18ZBSearch 4.0.0 (flat)Difference
Vector search2,185 ops/s2,639 ops/sZBSearch 17.2% faster
Strict similarity4,133 ops/s6,128 ops/sZBSearch 32.6% faster
Vector + filters5,071 ops/s5,860 ops/sZBSearch 13.5% faster
Vector + facets1,285 ops/s1,867 ops/sZBSearch 31.2% faster

ZBSearch IVF vs Orama flat

ZBSearch adds an optional IVF index for approximate nearest-neighbor search. Orama does not ship an equivalent in the JS core.

BenchmarkOrama 3.1.18 (flat)ZBSearch 4.0.0 (flat)ZBSearch 4.0.0 (IVF)
Vector search2,215 ops/s2,674 ops/s11,810 ops/s
Strict similarity4,476 ops/s6,907 ops/s16,441 ops/s
Vector + filters5,216 ops/s5,935 ops/s16,621 ops/s

With IVF (nlist=179, nprobe=16), ZBSearch vector search runs at 3.2–5.3× the throughput of Orama's flat index on this dataset.

Geosearch (BKD tree)

Dataset: 10,000 geopoints.

BenchmarkOrama 3.1.18ZBSearch 4.0.0Difference
Insert258 ops/s264 ops/s~equal
Search by radius (500m)3,702 ops/s275,793 ops/sZBSearch 98.7% faster (~75×)
Search by radius (5km)3,090 ops/s5,020 ops/sZBSearch 38.4% faster
Search by radius sorted (5km)582 ops/s1,640 ops/sZBSearch 64.5% faster
Search by polygon3,471 ops/s4,181 ops/sZBSearch 17.0% faster
Contains (10k lookups)921 ops/s922 ops/s~equal

The largest geosearch gain is on tight-radius queries, where ZBSearch's BKD implementation avoids scanning the full point set.

Sorted indexes (AVL tree)

Dataset: 10,000 numeric keys.

BenchmarkOrama 3.1.18ZBSearch 4.0.0Difference
Insert458 ops/s567 ops/sZBSearch 19.2% faster
Insert batched (1k threshold)456 ops/s585 ops/sZBSearch 22.0% faster
Find (10k lookups)2,454 ops/s3,784 ops/sZBSearch 35.2% faster
Contains (10k lookups)2,503 ops/s3,361 ops/sZBSearch 25.5% faster
Range search (narrow)23,475 ops/s50,993 ops/sZBSearch 54.0% faster
Range search (wide)6,004 ops/s6,568 ops/sZBSearch 8.6% faster
Greater than7,280 ops/s7,714 ops/sZBSearch 5.6% faster
Less than7,454 ops/s7,579 ops/sZBSearch 1.7% faster
Remove (5k keys)450 ops/s518 ops/sZBSearch 13.1% faster

Inverted index (postings)

Starting in 3.3.0, ZBSearch stores full-text doc IDs in explicit posting lists outside the radix tree dictionary. The radix tree handles prefix navigation and typo tolerance; postings map each token to its document IDs.

This removes duplication that existed between inline radix Sets, tokenOccurrences, and scattered serialization of doc ID arrays. On save, posting lists are delta-encoded (sorted doc IDs stored as gaps), which shrinks the bundle without changing search semantics.

At runtime, document frequency for BM25 is derived directly from posting list length - no separate counter map is maintained. Leaf nodes also keep a direct reference to their posting array so inserts can append without a map lookup on every token.

Memory footprint

Measured in isolated Node.js processes with --expose-gc, after indexing all 1,512 records and running 100 warmup searches. Heap delta is the increase over a GC'd baseline after the index is built - the most apples-to-apples comparison.

MetricOrama 3.1.18ZBSearch 4.0.0Difference
Indexed heap delta14.69 MB11.68 MBZBSearch 20.4% lower
Indexed RSS delta21.88 MB12.81 MBZBSearch 41.4% lower
Search heap delta (100 queries)146 KB158 KB~equal

Search working-set memory is effectively the same; the savings come from the index structure itself.

Persistence size

Serializing the same indexed dataset produces a much smaller bundle with ZBSearch 4.0.0:

EngineJSONGZIP
Orama 3.1.185,226,285 bytes1,300,371 bytes
ZBSearch 4.0.03,025,127 bytes835,855 bytes
Difference42.1% smaller35.7% smaller

Delta-encoded postings and dropping redundant tokenOccurrences from the serialized snapshot account for most of the reduction. v4 also adds a chunked persistence format for indexes that outgrow a single string.

Run the benchmarks yourself

From the repository root, build ZBSearch first, then run any suite in benchmarks/:

cd packages/zbsearch && npm run build
cd ../../benchmarks && npm install

npm run benchmark:compare      # Orama vs ZBSearch, 20 metrics
npm run benchmark              # core insert/search
npm run benchmark:algorithms   # BM25 vs QPS vs PT15
npm run benchmark:facets       # facet workloads
npm run benchmark:vector       # vector search (flat)
npm run benchmark:vector-ivf   # vector flat vs IVF
npm run benchmark:bkd          # geosearch BKD tree
npm run benchmark:avl          # sorted AVL tree
npm run benchmark:bundle-size  # serialized DB size
npm run benchmark:memory       # in-memory heap/RSS footprint
npm run benchmark:search-quality # BEIR ranking quality

Results are saved as JSON and HTML charts under benchmarks/benchmark/results/.

On this page