SeekStorm Documentation

repository·main·Indexed 23 days ago

https://github.com/seekstorm/seekstorm

An open-source, sub-millisecond vector and lexical search server featuring a dual-engine architecture. It provides hybrid search capabilities combining a BM25/Boolean lexical engine and a Leveled-IVF vector engine via a Query Planner and Result Fusion. Includes a Rust client (seekstorm_client_rs), a REST API for index and document management, an ingest console command for various file types (PDF, JSON, CSV), and an embedded Web UI for debugging.

Tokens
27.6K
Snippets
49
Records
135
Agent score
83%

What's inside SeekStorm

  1. Use Numerical Range Facets for counting, filtering, and sorting

    main

    Numerical range facets allow you to group documents into explicitly defined numeric buckets (e.g., Price ranges like 0..10, 10..20).

    • Supported Types: u8, u16, u32, u64, i8, i16, i32, i64, f32, f64.
    • Counting: You must define the ranges and labels at query time. Use RangeType to specify how to count:
      • CountWithinRange: Count values strictly within the specified range.
      • CountAboveRange: Count values within the range and all ranges above.
      • CountBelowRange: Count values within the range and all ranges below.
    • Filtering: Use FacetFilter with a range (e.g., 21..65) to restrict results.
    • Sorting: Results can be sorted by the numerical field value.
    use seekstorm::search::{QueryFacet, RangeType};
    
    let query_facets = vec![QueryFacet::U8 {
        field: "age".into(),
        range_type: RangeType::CountWithinRange,
        ranges: vec![
            ("0-20".into(), 0),
            ("20-40".into(), 20),
            ("40-60".into(), 40),
            ("60-80".into(), 60),
            ("80-100".into(), 80),
        ],
    }];
  2. Understand the SeekStorm server index directory structure

    main

    The server organizes data in a specific hierarchy. To perform manual backups or restores, you should shut down the server first, then copy/move the directories.

    Hierarchy Levels:

    1. API Keys: Top level.
    2. Indices: One directory per API key.
    3. Shards: An index is divided into shards.
    4. Levels: Each shard is divided into levels (one level = 64k documents). Once a level reaches 64k documents, it is committed to disk and becomes immutable.

    Directory Example:

    seekstorm_index/
    ├─ 0/ (API Key)
    │  ├─ 0 (Index)
    │  │  ├─ 0 (Shard)
    │  │  │  ├─ 0 (Level)
    │  │  │  ├─ 1
    │  │  │  └─ 2
    │  │  └─ 1
    │  └─ 1
    └─ apikey.json (Contains API key hash and quotas)
    seekstorm_index/  
    ├─ 0/  
    │  ├─ 0  
    │  ├─ 1  
    │  ├─ 2  
    ├─ 1/  
    │  ├─ 0  
    │  ├─ 1 ─ shards ─ ├─ 0  
    │  │               ├─ 1  
    │  │               ├─ 2
  3. N-gram compatibility with BM25 ranking

    main

    To ensure N-gram indexing provides the same top-k results as single-term indexing, SeekStorm implements measures to maintain BM25 score accuracy.

    BM25 Scoring Modes:

    • LexicalSimilarity::Bm25f: Provides scores almost identical to single-term indexing. It achieves this by storing the document frequency (DF) and term frequency (TF) of each partial term within the N-gram. Note that small differences may occur due to lossy logarithmic compression used for N-gram posting counts.
    • LexicalSimilarity::Bm25fProximity: An alternative score that uses the DF and TF of the N-gram itself rather than its partial terms. This honors the proximity of terms within the N-gram for scoring purposes, but it cannot independently score the posting list length and position count of the individual N-gram terms.
  4. How N-gram indexing improves phrase search performance

    main

    N-gram indexing accelerates phrase searches by moving the computation of term intersection and phrase matching from query time to indexing time.

    How it works:

    • Traditional Phrase Search: Intersects single-term posting lists and then performs a proximity check to see if terms are adjacent. This is expensive for frequent terms (like stopwords) with long posting lists.
    • N-gram Phrase Search: Pre-calculates N-grams (bigrams and trigrams) of adjacent terms during indexing. At query time, the engine can directly access the N-gram posting list, which is significantly shorter than the individual term lists, eliminating the need for expensive intersections and proximity checks.

    Performance Impact:

    • Improves mean query latency by ~2.18x.
    • Improves p99.9 tail latency by ~7.63x.
    • Can accelerate specific queries (e.g., those containing frequent terms like "the who") by up to 3 orders of magnitude.
  5. Understand the Lexical Inverted Index modes

    main

    The lexical engine uses an inverted index that can be accessed in two modes. The index file format is identical for both, allowing you to switch modes for an existing index at any time:

    • RAM mode: The entire index is preloaded into RAM. This provides minimal latency (no disk access during search), even after a cold start, but results in higher RAM consumption and longer initial load times.
    • Mmap mode: The index is accessed via memory-mapped files. This provides minimal RAM consumption and minimal load times, as the OS handles caching. It is highly scalable and the cache is persistent between program starts until a reboot.
  6. Configure the Document Schema and Store

    main

    SeekStorm uses a schema-based approach for documents. Every document can contain arbitrary fields of different types, which can be searched or filtered individually or globally.

    • schema.json: Defines the fields, their types, and whether they are stored or indexed.
    • Document Store: Documents are stored in JSON format and compressed using Zstandard. Only fields marked as stored in the schema.json will be included in the document store (docstore.bin) and can be returned in search results.
  7. How SeekStorm's Dual Engine Architecture works

    main

    SeekStorm uses a dual-engine architecture to provide hybrid search capabilities. Instead of a single engine attempting to handle all query types, it runs two native, first-class engines in parallel:

    1. Lexical Engine: An inverted index optimized for keyword relevance using BM25 or Boolean logic.
    2. Vector Engine: A native ANN (Approximate Nearest Neighbor) index using a Leveled-IVF architecture optimized for vector similarity.

    A Query Planner sits between the user and these engines. It determines the search intent and strategy (automatically or manually) and coordinates how to combine results from both engines using Result Fusion (such as Reciprocal Rank Fusion - RRF) to produce a single, final ranked result list. This allows for pure lexical, pure vector, or true hybrid search.

  8. Use String Facets for counting, filtering, and sorting

    main

    String facets allow you to cluster documents by distinct string values (e.g., Language, Brand).

    • Supported Types: String16, String32, StringSet16, and StringSet32 (for multiple values per document).
    • Counting: Returns distinct values and their occurrence counts.
    • Filtering: Use FacetFilter::String16 to restrict results to documents containing specific values.
    • Sorting: Results can be sorted by the string facet field in ascending or descending order.
    • Limits: String16 supports up to 65,535 unique values; String32 supports up to 4,294,967,295. String values are limited to 100 characters.
  9. Index PDF files

    main

    SeekStorm can automatically convert PDF files to text and index them.

    Features:

    • Extracts title from metatags, the first line of text, or the filename.
    • Extracts creation date from metatags or file creation date (Unix timestamp).
    • Copies ingested PDFs to a files subdirectory within the index.

    Required Schema: To index PDFs, your index schema must include the following fields:

    • title (Text, stored, index_lexical: true, boost: 10)
    • body (Text, stored, index_lexical: true)
    • url (Text, stored, index_lexical: false)
    • date (Timestamp, stored, index_lexical: false, facet: true)

    Note for Windows users: Use curl.exe and escape double quotes in the JSON payload.

  10. Set up an asynchronous Rust runtime for SeekStorm

    main

    The SeekStorm client requires an asynchronous runtime. It is recommended to use tokio. You can wrap your SeekStorm logic within a #[tokio::main] async function.

    use std::error::Error;
    
    #[tokio::main]
    async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
        // your SeekStorm code here
        Ok(())
    }