Architecture

How SeekStorm is built

SeekStorm is an open-source, sub-millisecond vector & lexical search library and multi-tenancy server, implemented in Rust. Scalability and performance are the two fundamental design goals: index size and latency grow linearly with the number of indexed documents, while RAM consumption stays constant.

Five architectural decisions get it there: a dual-engine core for hybrid search, a sharded index that turns every processor core into its own mini search engine, N-gram indexing that moves phrase matching from query time to index time, a clustered IVF index for vector search, and a simple, inspectable on-disk layout.

Dual engine architecture for hybrid search

SeekStorm doesn't retrofit vector search onto a lexical index, or bolt keyword search onto a vector database. It runs two separate, first-class, native index architectures under one roof — an inverted index for lexical relevance and an ANN index for vector similarity — and lets a query planner decide how to combine them.

Both engines share a document store and a document ID space, but keep separate storage layouts, indexing, search, and scoring. The query planner exposes 6 dedicated QueryModes and FusionTypes, selectable automatically or manually, and returns the active mode for explainability. Results from both engines are merged with Reciprocal Rank Fusion (RRF) — the user only ever sees a single, unified index.

+----------------+ | User / API | | (hybrid query) | +----------------+ | v +---------------------+ | Query Planner | | (intent + strategy) | +---------------------+ | +--------------+--------------+ v v +------------------+ +------------------+ | Lexical Engine | | Vector Engine | | Inverted Index | | Native ANN Index | | (BM25 / Boolean) | | (Leveled IVF) | +------------------+ +------------------+ | | v v Ranked Results L Ranked Results V | | +--------------+--------------+ | v +---------------------------+ | Result Fusion | | (RRF / rerank strategies) | +---------------------------+ | v Final Ranked Results
Lexical search (sparse)

Fast, precise, exact, language independent — the right tool for proper names, numbers, license plates, and phrase matching. Struggles with meaning and synonymy.

Vector search (dense)

Understands similarity and topic — but is language dependent, slower, and can't handle terms it wasn't trained on.

Sharded index: a processor as a miniature data center

Search engines have long used sharding to split an index across multiple servers — a shard per machine — to scale index size, indexing speed, and query throughput past the limit of one box. SeekStorm moves the same principle down a level: instead of a shard per server, it uses a shard per processor core, enabling intra-query parallelism.

This concept is lock-free and prevents synchronization losses. It allows to fully utilize every core on a CPU during indexing and search.

Documents are partitioned across shards during indexing; a query touches every shard in parallel, and partial results are aggregated into the final top-k. Because each shard is independent, there's no cross-core synchronization or locking — every core stays fully utilized. Both the lexical and vector indices use this leveled, sharded layout: documents accumulate in RAM and, once a shard reaches 64k documents, that level is committed to disk and becomes immutable.

Query | +-----------------+--------+--------+-----------------+ v v v v +------------+ +------------+ +------------+ +------------+ | Core 0 | | Core 1 | | Core 2 | | Core 3 | | Shard 0 | | Shard 1 | | Shard 2 | | Shard 3 | | (index | | (index | | (index | | (index | | partition) | | partition) | | partition) | | partition) | +------------+ +------------+ +------------+ +------------+ | | | | v v v v top-k L0 top-k L1 top-k L2 top-k L3 | | | | +-----------------+--------+--------+-----------------+ | v +-----------------------+ | Aggregate top-k | | (re-score BM25 across | | shards) | +-----------------------+ | v Final Results

Sharding raises one subtlety: BM25 scores depend on values averaged across an entire shard (document count, average document length), so raw scores aren't directly comparable between shards. SeekStorm re-scores the aggregated top-k using global document frequencies and average document length collected from every shard, so ranking stays consistent regardless of how documents were distributed.

The result, measured indexing 5 million Wikipedia documents: roughly 4–6× faster indexing and 3× shorter query latency compared to a non-sharded index, reaching 40,000 docs/sec indexed and 10,000 queries/sec at 0.25 ms average latency on a single laptop CPU.

N-gram indexing for faster phrase search

A phrase query like "the who" is expensive with a plain inverted index: both terms are frequent, their posting lists are long, and the engine has to intersect two long lists and then check that the positions are actually adjacent. SeekStorm instead indexes bigrams and trigrams of frequent terms directly, moving that intersection and phrase check from query time to index time.

Traditional phrase search -- query time ------------------------------------------------ "the" -> [d1,d2,d4,d7,d9, ... ] (long posting list) "who" -> [d2,d3,d4,d8,d9, ... ] (long posting list) | v intersect (galloping / SIMD) [d2, d4, d9, ...] | v phrase check: are positions adjacent? [d4, d9] <-- final matches N-gram indexing -- precomputed at index time ------------------------------------------------ "the who" (bigram) -> [d4, d9, ...] (short posting list) | v direct lookup, no intersect, no phrase check [d4, d9, ...] <-- final matches

Frequent terms — what other engines call stop words — are the ones worth combining into N-grams: there are only a handful of them, so the extra index size stays small, while their posting lists are the longest and most expensive to intersect. Restricting N-grams to adjacent frequent terms keeps the index cost negligible while accelerating exactly the queries — like "the who" or "to be or not to be" — that would otherwise be slowest.

Measured impact: phrase queries saw mean latency improve by 2.18×, p99.9 tail latency by 7.63×, and some individual phrase queries sped up by up to 3 orders of magnitude — with BM25 scores staying almost identical to single-term indexing.

Lexical search

The steps below cover the lexical engine specifically — the inverted-index side of the dual engine. The vector engine has its own, separate search path, covered in Vector search below.

On the lexical side, SeekStorm processes posting lists Document-at-a-Time (DaaT) rather than Term-at-a-Time, so it never materializes long intermediate result lists in RAM and can stream results — which is what makes huge indices tractable. Intersection and union of roaring-bitmap posting lists are SIMD-accelerated (AVX2 / NEON), combined with galloping intersection and a faster-than-Block-Max WAND for early termination, plus N-gram lookups for frequent-term phrases.

Query | v +------------------------+ | Tokenize + rewrite | single terms + N-grams | (query planner) | QueryMode selection +------------------------+ | v +------------------------+ | Per-shard DaaT walk | SIMD roaring-bitmap ops | galloping intersect | faster-than-Block-Max WAND pruning | N-gram short-circuit | N-gram short-circuit +------------------------+ | v +------------------------+ | Per-shard top-k | BM25 / BM25f scoring +------------------------+ | v +------------------------+ | Cross-shard re-score | global DF + avg doc len | + aggregate top-k | +------------------------+ | v Final ranked results

On-disk storage layout

Every SeekStorm index is a self-contained directory tree, with no external dependencies or database engines. At every commit (auto or manual), the index is serialized to disk for persistence. Every index directory follows the same predictable hierarchy: API keys at the top, indices below each key, shards below each index, and levels (64k-document batches) below each shard. Levels are collected in RAM and committed to disk — becoming immutable — once the threshold is reached. It's a plain directory tree, so backup and restore is just a file copy.

seekstorm_index/ -- <api_key>/ API key directory, one per user (tenant) | +-- apikey.json API key hash + quotas | +-- <index_0>/ index directory, one per index | | +-- index.json similarity, tokenizer, access mode | | +-- schema.json field definitions, types | | +-- synonyms.json user-defined synonyms | | +-- shard_0/ shards are per-core, document partitions (round-robin) for parallelism | | | +-- index.bin inverted index posting lists (roaring bitmaps) + term positions (delta + VINT) | | | | +-- level_0 64k-document batch, immutable | | | | +-- level_1 ... | | | | +-- level_2 ... | | | +-- vector.bin IVF (Inverted File) vectors and clusters | | | | +-- level_0 64k-document batch, immutable | | | | +-- level_1 ... | | | | +-- level_2 ... | | | +-- facet.bin facet field values (serialized) | | | | +-- level_0 64k-document batch, immutable | | | | +-- level_1 ... | | | | +-- level_2 ... | | | +-- facet.json facet field unique values | | | +-- docstore.bin documents, JSON + Zstandard | | | | +-- level_0 64k-document batch, immutable | | | | +-- level_1 ... | | | | +-- level_2 ... | | | +-- delete.bin deleted document IDs | | +-- shard_1/ ... | | +-- shard_2/ ... | +-- <index_1>/ ... +-- <api_key_2>/ ...

The index can be kept fully in RAM for zero disk access at search time, or memory-mapped for minimal RAM use at slightly higher latency — both modes share the identical file format, so an existing index can switch access mode at any time. Posting lists are roaring-bitmap compressed; term positions are delta-compressed and VINT-encoded; documents are Zstandard-compressed JSON.