vcdb

    High-performance vector database with multiple ANN algorithms

    vector-database
    ann
    hnsw
    ivf
    similarity-search
    Download zip
    Author
    Version
    0.3.2
    License
    Apache-2.0
    Last updated
    3 months ago
    Downloads
    681

    #vcdb

    High-performance vector database with multiple ANN algorithms for MoonBit.

    #Features

    • Multiple ANN Algorithms: HNSW, IVF, and Brute-force search
    • Flexible Storage: Pluggable backends (memory, S3)
    • Persistence: WAL-based durability with segment management
    • Attribute Filtering: Metadata-based vector filtering
    • Gateway Execution Layer: API handling reusable across native and JS runtimes

    #Overview

    vcdb is structured as a layered architecture:

    #Core Layers

    PackagePurpose
    core/annANN algorithms (HNSW, IVF, Bruteforce)
    core/storeVector data storage
    core/storageAbstract storage interface
    core/persistenceWAL and segment management
    core/attrAttribute indexing and filtering

    #API Layers

    PackagePurpose
    gatewayTransport-agnostic API execution core
    httpHTTP transport adapter over gateway
    jsJS runtime adapter, npm distribution, and HTTP server
    cmd/native-gatewayNative gateway execution entrypoint
    cmd/native-serveNative HTTP server over gateway
    cliCommand-line interface
    libLibrary entry point

    #Getting Started

    // Create a collection with HNSW index
    let store = @vcdb.CoreStore::new()
    let collection = store.create_collection("my_vectors", dim=128, ann_type=HNSW)

    // Add vectors with optional attributes
    collection.upsert([
    { id: "vec1", vector: [...], attrs: { "category": "A" } },
    { id: "vec2", vector: [...], attrs: { "category": "B" } },
    ])

    // Search with filtering
    let results = collection.search(
    query_vector,
    top_k=10,
    filter={ "category": "A" }
    )

    #Usage

    #Native Gateway Execution

    Execute gateway requests directly from MoonBit:

    moon run cmd/native-gateway -- healthz moon run cmd/native-gateway -- collections create demo --dim 3

    #HTTP Serving via JS Adapter

    Start the JavaScript transport adapter:

    cd js npm run build node dist/server.js --host 127.0.0.1 --port 6333 --storage ../.local-storage

    #HTTP Serving via Native Adapter

    Start the MoonBit native transport adapter:

    moon run cmd/native-serve -- --host 127.0.0.1 --port 6333

    #Create Collection

    curl -X POST http://localhost:8080/collections/my_collection \ -H "Content-Type: application/json" \ -d '{"dim": 128, "ann_type": "hnsw"}'

    #Upsert Vectors

    curl -X PUT http://localhost:8080/collections/my_collection/points \ -H "Content-Type: application/json" \ -d '{"points": [{"id": "1", "vector": [...]}]}'

    curl -X POST http://localhost:8080/collections/my_collection/search \ -H "Content-Type: application/json" \ -d '{"vector": [...], "top_k": 10}'

    #Installation

    #From Package Manager

    moon add trkbt10/vcdb

    #From Source

    git clone https://github.com/trkbt10/vcdb_mbt cd vcdb_mbt moon build

    #Requirements

    • MoonBit toolchain (moon >= 0.1.0)

    gateway is the API execution core. Long-lived HTTP serving is now available in both the JS adapter and the native async/http adapter.

    #License

    See LICENSE for details.

    PersistentDB

    pub struct PersistentDB[W, S] {
    engine : VectorDB
    wal :
    AsyncWalRuntime
    [W]
    snapshot_storage : S
    base_path : String
    name : String
    checkpoint_threshold : Int
    checkpoint_bytes : Int
    }

    PersistentDB::add

    Add a single vector. Fails if the ID already exists (not tombstoned).

    This is the WAL-backed equivalent of VectorDB::add. Existence check is performed BEFORE WAL append to prevent orphaned WAL records when the ID already exists.

    PersistentDB::checkpoint

    Checkpoint: snapshot to snapshot_storage, then truncate WAL. Crash safe: snapshot-first, WAL truncate second.

    PersistentDB::compact

    Compact HNSW index (removes tombstones). Returns removed count. Triggers checkpoint after compaction.

    PersistentDB::count_filtered

    fn[W, S] PersistentDB::count_filtered(self : PersistentDB[W, S], expr? :
    FilterExpr
    ?) -> Int

    Count vectors matching filter.

    PersistentDB::deserialize

    Deserialize from snapshot bytes, wrapping with in-memory storage.

    Used for loading a serialized database without persistence backends.

    PersistentDB::dim

    fn[W, S] PersistentDB::dim(self : PersistentDB[W, S]) -> Int

    Vector dimension.

    PersistentDB::find

    Find the single best match.

    PersistentDB::from_snapshot

    fn[S] PersistentDB::from_snapshot(data : Bytes, storage : S, base_path : String, name : String, checkpoint_threshold? : Int, checkpoint_bytes? : Int) -> PersistentDB[S, S]

    Load from snapshot bytes with explicit storage backends.

    Used by gateway's load_collection to restore from a serialized snapshot and attach real storage backends for subsequent WAL operations.

    PersistentDB::get

    Get a single vector by ID.

    PersistentDB::has

    fn[W, S] PersistentDB::has(self : PersistentDB[W, S], id :
    VectorId
    ) -> Bool

    Check if a vector exists.

    PersistentDB::in_memory

    Create an in-memory PersistentDB (no durable persistence).

    Uses MemoryStorage for both WAL and snapshot backends. Since MemoryStorage resolves all I/O synchronously, initialization completes immediately — but the function is async to maintain a uniform interface.

    PersistentDB::in_memory_bruteforce

    Create an in-memory PersistentDB with Bruteforce strategy.

    PersistentDB::in_memory_hnsw

    Create an in-memory PersistentDB with HNSW strategy.

    PersistentDB::in_memory_ivf

    Create an in-memory PersistentDB with IVF strategy.

    PersistentDB::init

    async fn[W :
    AsyncStorage
    , S :
    AsyncStorage
    ] PersistentDB::init(wal_storage : W, snapshot_storage : S, base_path : String, name : String, dim : Int, capacity : Int, metric? :
    Metric
    , strategy? :
    Strategy
    , checkpoint_threshold? : Int, checkpoint_bytes? : Int) -> PersistentDB[W, S]

    Initialize from storage: load WAL + snapshot, replay, build engine.

    Call sequence:
    1. Load snapshot from snapshot_storage
    2. Load WAL from wal_storage into in-memory buffer
    3. Deserialize snapshot -> CoreStore (or create empty)
    4. Replay WAL records onto CoreStore
    5. Build VectorDB from CoreStore

    PersistentDB::metric

    Similarity metric.

    PersistentDB::raw_size

    fn[W, S] PersistentDB::raw_size(self : PersistentDB[W, S]) -> Int

    Raw size including tombstones.

    PersistentDB::remove

    Remove a vector with WAL-before-state guarantee.

    PersistentDB::scroll

    Scroll through vectors in ascending ID order.

    PersistentDB::scroll_filtered

    Scroll with filter expression.

    PersistentDB::search

    Search by vector similarity with optional filter expression.

    PersistentDB::search_with_expr

    Search with explicit filter expression, index override, and strategy.

    PersistentDB::search_with_filter

    Search by vector similarity with callback filter.

    PersistentDB::serialize_snapshot

    fn[W, S] PersistentDB::serialize_snapshot(self : PersistentDB[W, S]) -> Bytes

    Serialize current state as snapshot bytes.

    PersistentDB::size

    fn[W, S] PersistentDB::size(self : PersistentDB[W, S]) -> Int

    Database size (excluding tombstones).

    PersistentDB::store

    Access the underlying CoreStore (for advanced/distributed use).

    PersistentDB::strategy

    ANN strategy.

    PersistentDB::train

    fn[W, S] PersistentDB::train(self : PersistentDB[W, S], iterations? : Int) -> Unit

    Train IVF index (IVF strategy only).

    PersistentDB::update_attrs

    Update attributes with WAL-before-state guarantee.

    PersistentDB::upsert

    Upsert points with WAL-before-state guarantee.

    Order:
    1. Encode WAL records (sync)
    2. Persist WAL to storage (async, awaited)
    3. Update engine in-memory (sync)
    4. Auto-checkpoint if thresholds exceeded

    PersistentDB::wal_byte_size

    fn[W, S] PersistentDB::wal_byte_size(self : PersistentDB[W, S]) -> Int

    Current WAL byte size (for diagnostics).

    PersistentDB::wal_record_count

    fn[W, S] PersistentDB::wal_record_count(self : PersistentDB[W, S]) -> Int

    Current WAL record count (for diagnostics).

    VectorDB

    VectorDB - Internal ANN index engine.

    Provides unified access to all ANN strategies: Bruteforce, HNSW, and IVF. The engine automatically handles:
    • Vector normalization for cosine similarity
    • Index updates on add/remove operations
    • Tombstone management for HNSW deletions

    This is an internal implementation detail. The public API is VectorDB[W, S].

    VectorDB::add

    Add a new vector to the database.

    For HNSW mode, allows re-adding tombstoned IDs (clears tombstone and re-indexes). Fails if the ID already exists and is not tombstoned.

    Parameters:
    • id: Unique identifier for the vector
    • vector: The embedding vector (must match database dimension)
    • attrs: Metadata attributes for filtering

    VectorDB::compact

    fn VectorDB::compact(self : VectorDB) -> (VectorDB, Int)

    Compact the database by removing tombstoned vectors (HNSW only).

    HNSW uses soft-delete (tombstones) for remove(). Over time, tombstones accumulate and waste memory. compact() rebuilds the HNSW graph without tombstoned entries.

    For Bruteforce and IVF, this is a no-op (they physically remove).

    Returns: A new VectorDB with the compacted state, and the number of removed tombstones.

    VectorDB::count_filtered

    fn VectorDB::count_filtered(self : VectorDB, expr? :
    FilterExpr
    ?) -> Int

    Count vectors matching an optional filter expression.

    When expr is None, counts all non-tombstoned vectors. When expr is Some, resolves the expression to a candidate set via the B+ tree index, then subtracts tombstoned entries. No vector data is touched.

    Parameters:
    • expr: Optional filter expression to count

    Returns: Number of matching, non-tombstoned vectors

    VectorDB::deserialize

    fn VectorDB::deserialize(data : Bytes) -> VectorDB

    Deserialize a database from bytes.

    Restores all vectors, attributes, metadata index state, and ANN index state.

    Parameters:
    • data: Binary data from serialize()

    Returns: The restored VectorDB instance

    VectorDB::dim

    fn VectorDB::dim(self : VectorDB) -> Int

    Get the dimension of vectors in this database.

    VectorDB::export_jsonl

    fn VectorDB::export_jsonl(self : VectorDB) -> Array[String]

    Export all points as JSONL lines.

    VectorDB::find

    Find the single best match for a query.

    Convenience method equivalent to search(query, 1, filter).

    Parameters:
    • query: The query vector
    • filter: Optional filter predicate

    Returns: The best matching SearchHit, or None if no matches

    VectorDB::from_store

    Create VectorDB from an existing CoreStore (for loading from distributed storage)

    VectorDB::get

    Get a vector and its attributes by ID.

    For HNSW mode, tombstoned IDs return None.

    Parameters:
    • id: The vector ID to retrieve

    Returns: The vector record if found and accessible, None otherwise

    VectorDB::has

    Check if an id exists and is accessible.

    For HNSW mode, tombstoned IDs are considered non-existent.

    Parameters:
    • id: The vector ID to check

    Returns: true if the vector exists and is not tombstoned

    VectorDB::metric

    Get the similarity metric used by this database.

    VectorDB::new

    Create a new VectorDB with the given options.

    Parameters:
    • options: Configuration including dimension, metric, capacity, and strategy

    Returns: A new VectorDB instance configured according to options

    VectorDB::raw_size

    fn VectorDB::raw_size(self : VectorDB) -> Int

    Get the raw size including tombstoned vectors. This is primarily for internal use and serialization.

    VectorDB::remove

    Remove a vector by ID.

    Behavior differs by strategy:
    • Bruteforce/IVF: Physically removes the vector
    • HNSW: Marks as tombstone (soft delete) to maintain graph connectivity

    For HNSW, use compact() to reclaim space from tombstoned vectors.

    Parameters:
    • id: The vector ID to remove

    Returns: true if the vector was removed, false if not found or already deleted

    VectorDB::scroll

    Scroll points in ascending ID order, similar to Qdrant scroll.

    Thin wrapper over scroll_filtered with no filter expression. Offset semantics: returns entries with ID strictly greater than offset. Pass the last ID from previous page as offset to get the next page.

    VectorDB::scroll_filtered

    Scroll points in ascending ID order with optional filter expression.

    When expr is None, returns all (non-tombstoned) entries. When expr is Some, resolves candidates via the B+ tree attribute index — every expression variant is handled by the index with a single execution path.

    Offset semantics: returns entries with ID strictly greater than offset (cursor = last ID seen by caller).

    Parameters:
    • expr: Optional filter expression to match against attributes
    • offset: Cursor — only IDs strictly greater than this value are returned
    • limit: Maximum number of entries to return

    Returns: Array of (VectorId, VectorRecord) sorted by ID ascending

    VectorDB::search

    Search for k nearest neighbors to a query vector.

    Parameters:
    • query: The query vector (must match database dimension)
    • k: Number of results to return
    • filter: Optional predicate to filter results by ID and attributes

    Returns: Array of SearchHit sorted by score (highest/most similar first)

    VectorDB::search_with_expr

    Search with filter expressions and metadata index support.

    Supports two filtering strategies:
    • PreFilter: Use attr index to get candidate IDs, then score only those
    • PostFilter: Score all vectors, then filter results
    • Auto: Automatically choose based on estimated selectivity

    PreFilter is more efficient when the filter is selective (<50% of vectors). PostFilter is better for broad filters or when no attr index is available.

    Parameters:
    • query: The query vector
    • k: Number of results to return
    • expr: Optional filter expression (Must/MustNot/Should/Leaf)
    • attr_index: Optional B+ tree index override for pre-filtering
    • strategy: Filtering strategy (PreFilter/PostFilter/Auto)

    Returns: Array of SearchHit matching the filter criteria

    VectorDB::serialize

    fn VectorDB::serialize(self : VectorDB) -> Bytes

    Serialize the database to bytes for persistence.

    Includes all vectors, attributes, metadata index state, and ANN index state. Use deserialize() to restore.

    Returns: Binary representation of the database

    VectorDB::size

    fn VectorDB::size(self : VectorDB) -> Int

    Get the number of accessible vectors in the database.

    For HNSW, this excludes tombstoned (deleted) vectors. Use raw_size() to get the total count including tombstones.

    VectorDB::store

    Get access to the underlying CoreStore (for distributed storage operations)

    VectorDB::strategy

    Get the ANN strategy used by this database.

    VectorDB::train

    fn VectorDB::train(self : VectorDB, iterations? : Int) -> Unit

    Train the IVF index for optimal search performance.

    Only applicable for IVF strategy. Call this after adding a representative sample of vectors to build the centroid clusters.

    Parameters:
    • iterations: Number of k-means iterations (default: 10)

    VectorDB::update_attrs

    Update only the attributes for a vector (without changing the embedding).

    Parameters:
    • id: The vector ID to update
    • attrs: New attributes to set

    Returns: true if updated, false if ID not found

    VectorDB::upsert

    Add or update a vector (upsert operation).

    If the ID exists, updates the vector and reindexes. If the ID doesn't exist, adds a new vector.

    Parameters:
    • id: Unique identifier for the vector
    • vector: The embedding vector
    • attrs: Metadata attributes

    Returns: true if a new vector was created, false if existing was updated

    VectorDB::with_dim

    fn VectorDB::with_dim(dim : Int) -> VectorDB

    Create a VectorDB with default options (Bruteforce strategy).

    Parameters:
    • dim: Vector dimension

    Returns: A VectorDB with Bruteforce strategy and Cosine metric

    VectorDB::with_hnsw

    fn VectorDB::with_hnsw(dim : Int, metric? :
    Metric
    ) -> VectorDB

    Create a VectorDB with HNSW strategy.

    HNSW (Hierarchical Navigable Small World) is recommended for most use cases. It provides O(log n) search time with high recall.

    Parameters:
    • dim: Vector dimension
    • metric: Similarity metric (default: Cosine)

    Returns: A VectorDB configured with HNSW index

    VectorDB::with_ivf

    fn VectorDB::with_ivf(dim : Int, metric? :
    Metric
    ) -> VectorDB

    Create a VectorDB with IVF strategy.

    IVF (Inverted File Index) is best for very large datasets. Requires training with train() before optimal search performance.

    Parameters:
    • dim: Vector dimension
    • metric: Similarity metric (default: Cosine)

    Returns: A VectorDB configured with IVF index

    empty_attrs

    Re-export factory functions for convenience