ZBSearch vs Orama
Head-to-head performance 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. This page summarizes benchmark results from the benchmarks/ suite in this repository.
Test environment
Results below were collected on July 10, 2026 using Orama 3.1.18 and ZBSearch 3.3.0, run via Benny for throughput and isolated Node.js processes (--expose-gc) for memory. Higher ops/s is better. Percentages show how much slower the loser is relative to the winner in each suite.
Summary
| Area | Verdict |
|---|---|
| Full-text search | ZBSearch is 11–58% faster on search; indexing is effectively equal |
| Facets | ZBSearch is 28–56% faster across all facet scenarios |
| Vector search | ZBSearch is 16–52% faster on flat indexes; IVF pushes gains to ~1,200–650% over Orama |
| Geosearch (BKD) | ZBSearch is up to ~8,400% faster on radius queries at 500m |
| Sorted indexes (AVL) | ZBSearch is faster on most AVL operations (insert, find, range search) |
| Memory footprint | ZBSearch uses ~6–8% less heap and RSS for the same indexed dataset |
| Persistence size | ZBSearch produces a ~4–7% smaller serialized bundle |
Core full-text search
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.
| Benchmark | Orama 3.1.18 | ZBSearch 3.3.0 | Difference |
|---|---|---|---|
| Plain search | 2,252 ops/s | 3,222 ops/s | ZBSearch 30.1% faster |
| Search with filters | 24,957 ops/s | 28,049 ops/s | ZBSearch 11.0% faster |
| Long text + complex filters | 5,252 ops/s | 12,415 ops/s | ZBSearch 57.7% faster |
ZBSearch is faster across all search scenarios tested here, with the largest gap on long-text queries with complex filters. Indexing throughput is effectively identical between the two engines.
Facets
| Benchmark | Orama 3.1.18 | ZBSearch 3.3.0 | Difference |
|---|---|---|---|
| Search with facets | 8,707 ops/s | 12,034 ops/s | ZBSearch 27.6% faster |
| Facets (all documents) | 2,522 ops/s | 4,561 ops/s | ZBSearch 44.7% faster |
| Facets + filters | 4,408 ops/s | 7,592 ops/s | ZBSearch 41.9% faster |
| Facets + long text + filters | 4,623 ops/s | 10,413 ops/s | ZBSearch 55.6% faster |
Vector search
Dataset: 2,000 documents, 128-dimensional vectors.
| Benchmark | Orama 3.1.18 | ZBSearch 3.3.0 (flat) | Difference |
|---|---|---|---|
| Vector search | 2,266 ops/s | 2,702 ops/s | ZBSearch 16.1% faster |
| Strict similarity | 4,532 ops/s | 6,889 ops/s | ZBSearch 34.2% faster |
| Vector + filters | 5,071 ops/s | 5,911 ops/s | ZBSearch 14.2% faster |
| Vector + facets | 1,273 ops/s | 1,895 ops/s | ZBSearch 32.8% 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.
| Benchmark | Orama 3.1.18 (flat) | ZBSearch 3.3.0 (flat) | ZBSearch 3.3.0 (IVF) |
|---|---|---|---|
| Vector search | 2,229 ops/s | 2,715 ops/s | 30,530 ops/s |
| Strict similarity | 4,385 ops/s | 6,742 ops/s | 32,885 ops/s |
| Vector + filters | 5,106 ops/s | 5,915 ops/s | 22,927 ops/s |
With IVF (nlist=179, nprobe=16), ZBSearch vector search is ~350–1,270% faster than Orama's flat index on this dataset.
Geosearch (BKD tree)
Dataset: 10,000 geopoints.
| Benchmark | Orama 3.1.18 | ZBSearch 3.3.0 | Difference |
|---|---|---|---|
| Insert | 266 ops/s | 265 ops/s | ~equal |
| Search by radius (500m) | 3,649 ops/s | 308,894 ops/s | ZBSearch 98.8% faster |
| Search by radius (5km) | 3,047 ops/s | 4,735 ops/s | ZBSearch 35.6% faster |
| Search by radius sorted (5km) | 556 ops/s | 1,571 ops/s | ZBSearch 64.6% faster |
| Search by polygon | 3,227 ops/s | 3,958 ops/s | ZBSearch 18.5% faster |
| Contains (10k lookups) | 913 ops/s | 906 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.
| Benchmark | Orama 3.1.18 | ZBSearch 3.3.0 | Difference |
|---|---|---|---|
| Insert | 411 ops/s | 529 ops/s | ZBSearch 22.3% faster |
| Insert batched (1k threshold) | 435 ops/s | 537 ops/s | ZBSearch 19.0% faster |
| Find (10k lookups) | 1,960 ops/s | 3,979 ops/s | ZBSearch 50.7% faster |
| Contains (10k lookups) | 2,330 ops/s | 3,121 ops/s | ZBSearch 25.3% faster |
| Range search (narrow) | 22,678 ops/s | 48,937 ops/s | ZBSearch 53.7% faster |
| Range search (wide) | 5,477 ops/s | 6,238 ops/s | ZBSearch 12.2% faster |
| Greater than | 6,147 ops/s | 6,683 ops/s | ZBSearch 8.0% faster |
| Less than | 6,776 ops/s | 6,902 ops/s | ZBSearch 1.8% faster |
| Remove (5k keys) | 400 ops/s | 489 ops/s | ZBSearch 18.2% 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.
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.
| Metric | Orama 3.1.18 | ZBSearch 3.3.0 | Difference |
|---|---|---|---|
| Indexed heap delta | 17.12 MB | 15.69 MB | ZBSearch 8.4% lower |
| Indexed RSS delta | 51.13 MB | 47.77 MB | ZBSearch 6.6% lower |
| Search heap delta (100 queries) | 121 KB | 129 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 smaller bundle with ZBSearch 3.3.0:
| Engine | JSON | GZIP |
|---|---|---|
| Orama 3.1.18 | 5,996,443 bytes | 1,425,900 bytes |
| ZBSearch 3.3.0 | 5,775,078 bytes | 1,329,786 bytes |
| Difference | 3.7% smaller | 6.7% smaller |
The index portion of the bundle shrinks the most (~11.6% GZIP on index data alone) thanks to delta-encoded postings and dropping redundant tokenOccurrences from the serialized snapshot.
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 # core insert/search
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 footprintResults are saved as JSON and HTML charts under benchmarks/benchmark/results/.