Sonic Search Backend

repository·master·Indexed 12 days ago

https://github.com/valeriansaliou/sonic

A high-performance, lightweight, and schema-less search backend that functions as an identifier index. Designed as a fast alternative to engines like Elasticsearch, Sonic maps search terms to object IDs with microsecond response times and minimal RAM usage. It supports natural language normalization, typo correction, real-time auto-completion, and 80+ languages. Interaction is handled via the TCP-based Sonic Channel protocol.

Tokens
24.2K
Snippets
76
Records
118
Agent score
97%

What's inside Sonic

  1. What is Sonic?

    master

    Sonic is a fast, lightweight, and schema-less search backend designed for high performance. Unlike full-featured search engines like Elasticsearch, Sonic acts as an identifier index rather than a document index. It ingests search texts and identifier tuples, returning only the IDs of matched items. These IDs can then be used to retrieve the actual documents from an external database.

    Key Characteristics:

    • Performance: Responds to queries in the microsecond (μs) range with a low CPU footprint and minimal RAM usage (~30MB).
    • Functionality: Supports natural language normalization, typo correction, and real-time auto-completion.
    • Data Model: Search terms are organized into collections, which are further organized into buckets. This allows for multi-tenancy (e.g., a separate bucket per user).
    • Unicode Support: Full compatibility with 80+ languages, automatically removing stop words after language detection.
  2. Sonic Protocol Overview and Connection Rules

    master

    The Sonic Channel protocol is used for searching, ingesting index data, and administration via TCP (default port 1491).

    Integration Requirements:

    1. Command Termination: Every command sent to the server must be terminated with a newline character (\n) to commit the command.
    2. Buffer Management: Upon a successful START command, the server returns a STARTED response containing a buffer(N) parameter. This value (in bytes) indicates the maximum size for a single command. Libraries should use this value to truncate or split large data payloads into multiple sub-commands to prevent buffer overflows.
    telnet ::1 1491
  3. Understand the Sonic Channel Protocol

    master

    Sonic communicates with clients using the Sonic Channel protocol over a raw TCP socket. It does not expose an HTTP API to minimize network and processing overhead.

    Key characteristics of the protocol:

    • Raw TCP: Clients interact directly with a TCP socket.
    • Synchronous by default: Most commands are synchronous. On a single connection, you must wait for a command to return before issuing the next one. To achieve parallelism, open multiple Sonic Channel connections.
    • Asynchronous support: Certain commands (like search queries) can be handled asynchronously using a special eventing protocol format, allowing them to be processed by a dedicated thread pool.

    Example command/response pattern:

    • Request: QUERY collection bucket "search query"
    • Response: EVENT QUERY result_id result_1 result_2
  4. How Sonic handles object identifiers (OID vs IID)

    master

    To minimize disk space, Sonic uses two types of identifiers:

    1. OID (Object Identifier): The original, user-provided identifier (e.g., a UUID or string like session_77f2e05e...). These are used for user-facing input and output.
    2. IID (Internal Identifier): A compact 32-bit integer representation of the OID. Sonic uses IIDs for internal storage and mapping to indexed words to significantly reduce the index size.

    Sonic internally maps OID <-> IID using a RocksDB-powered key-value store.

  5. Understand Sonic limitations

    master

    When using Sonic, be aware of the following architectural limitations:

    • Indexed Data Limits: Sonic uses 32-bit IIDs (Internal-IDs), allowing up to ~4.2 billion objects per bucket. It also uses a sliding window for word results, keeping only the $N$ most recently pushed results for a given word.
    • Search Query Limits: The NLP system works at the word-level, not the sentence-level. It can predict a word based on input but cannot predict the next word in a sentence.
    • Real-time Limits: The FST (Finite State Transducer) is rebuilt in batches. If a newly pushed word doesn't appear in SUGGEST immediately, wait for the next rebuild cycle or force it using the control channel with the command TRIGGER consolidate.
    • Hardware Requirements: Sonic performs searches directly on the file system rather than keeping the entire index in RAM. You must use SSD-backed file systems; performance on traditional HDDs will be significantly slower due to random disk access.
  6. Understand the Sonic Tasker system

    master

    The tasker module performs periodic background maintenance tasks to optimize performance and resource usage:

    1. Janitor: Closes cached collection and bucket stores that haven't been used recently to free up RAM.
    2. Consolidate: Writes in-memory FST changes to the on-disk FST data structure.

    Note on Latency: Because the tasker performs heavy-duty work on KV or FST stores, you may experience higher-than-expected latency during these periods due to locking. The system is optimized to minimize this contention.

  7. Understand the Sonic search index data model

    master

    Sonic organizes indexed data into a two-layer hierarchy: Collections contain Buckets, and Buckets contain Indexed Objects.

    • Collections: Top-level grouping for your data.
    • Buckets: A second layer of organization within a collection. If your use case doesn't require buckets, you can use a generic value like default.
    • Objects: The actual search results.

    Important Design Choice: Sonic does not store full documents. It only stores identifiers (OIDs) that refer to primary keys in your external database. This keeps the index compact and lightweight. You should use the identifiers returned by Sonic to fetch the actual documents from your own database.

  8. Understand Sonic's search results ranking algorithm

    master

    Since v1.7.0, Sonic uses a custom ranking algorithm designed for speed and low memory usage. Unlike traditional engines that use tf-idf or BM25, Sonic avoids storing term frequency data to keep the index extremely small.

    How scoring works

    1. Exact Matches: For each term in a query, Sonic first finds documents containing that exact term (score = 0).
    2. Prefix Matching: If more results are needed, it looks for words starting with the term. The score is the difference in length between the suggested term and the query term.
    3. Fuzzy Matching: If more results are still needed, it performs fuzzy matching using Levenshtein distance. The score is the Levenshtein distance between the query term and the matched word.
    4. Final Score: A document's total score is the sum of its scores for each individual term.
    5. Tie-breaking: If multiple documents have the same score, they are sorted by reverse ingestion order (most recent documents first).

    Key Properties

    • Integer-only math: Scoring uses only integer operations (no floating point, division, or square roots) to maintain high performance.
    • Result Limits: The number of results is capped by the store.kv.retain_word_objects configuration.
    • Fuzzy Limits: Fuzzy matching is constrained by the search.query_alternates_try configuration to prevent excessive computation.
    // Simplified logic of the ranking process:
    // 1. Exact matches get score 0
    // 2. Prefix matches get score: len(suggested_term) - len(term)
    // 3. Fuzzy matches get score: Levenshtein distance
    // 4. Final score = sum(term_scores)
  9. Interact with Sonic via the Sonic Channel protocol

    master

    Sonic does not provide an HTTP endpoint. All interactions, including performing searches and managing objects (data ingestion), must be handled via the Sonic Channel protocol (a TCP-based protocol).

    To interact with the database, you must use a Sonic Channel client library. If you are building a client for an unsupported language, you can refer to the detailed protocol documentation or the node-sonic-channel implementation.

  10. Use Environment Variable Interpolation in Config

    master

    Certain sensitive or environment-specific configuration keys support interpolation using the ${env.VAR_NAME} syntax. This allows you to keep secrets out of your static TOML files.

    Supported keys for interpolation:

    • server.log_level
    • channel.inet
    • channel.auth_password
    • store.kv.path
    • store.fst.path
    [channel]
    auth_password = "${env.SONIC_PASSWORD}"
    
    [server]
    log_level = "${env.SONIC_LOG_LEVEL}"
  11. How text is processed by the Sonic lexer

    master

    Before text is indexed, it passes through a lexer (tokenizer) that performs the following:

    1. Normalization: e.g., converting text to lower-case.
    2. Cleaning: Removing stopwords (common words like 'the' that add little search value).
    3. Tokenization: Splitting text into individual words using an iterator pattern.

    Language Detection: Since stopwords are language-specific, Sonic detects the text language using a hybrid approach:

    • Fast Method: Counting stopwords in the text (reliable for long texts).
    • Slow Method: Performing an n-gram pass (more accurate for small texts).

    Sonic uses ISO 639-3 codes for language references.

  12. How typo correction and word suggestions work

    master

    Sonic provides typo tolerance and autocomplete using a Finite-State Transducer (FST).

    • Mechanism: The FST acts as a graph of characters where nodes are characters and edges connect them to form words. This allows Sonic to find matches even if the user inputs englich instead of english, or provides an incomplete word like eng.
    • Storage: Sonic stores one FST file per bucket. These files are memory-mapped and read directly from the disk.
    • Immutability & Updates: FSTs are immutable once built. To handle new words without rebuilding the entire index constantly, Sonic uses an FST consolidation tasker that buffers changes in memory and periodically commits them to disk.