Skip to content

VectorDb hangs on second insert() call in @ruvector/router #134

@ruvnet

Description

@ruvnet

Bug Description

The native VectorDb in @ruvector/router hangs indefinitely on the second insert() call. The first insert completes successfully but any subsequent insert blocks forever.

Environment

  • Package: @ruvector/router@0.1.25 with @ruvector/router-linux-x64-gnu@0.1.25
  • Platform: Linux x64 (Ubuntu/Codespaces)
  • Node.js: v22.21.1
  • npm: v10.9.4

Reproduction

const { VectorDb, DistanceMetric } = require('@ruvector/router');

const db = new VectorDb({
  dimensions: 384,
  distanceMetric: DistanceMetric.Cosine,
});

// First insert - works
const vec1 = new Float32Array(384);
for (let i = 0; i < 384; i++) vec1[i] = Math.random();
db.insert('test_0', vec1);  // ✓ Completes in ~5ms
console.log('Count:', db.count());  // Shows 2 (should be 1?)

// Second insert - HANGS FOREVER
const vec2 = new Float32Array(384);
for (let i = 0; i < 384; i++) vec2[i] = Math.random();
db.insert('test_1', vec2);  // ❌ Never returns
console.log('This never prints');

Observed Behavior

Insert # Time Count After Notes
1st ~5ms 2 (wrong) Completes but count is incorrect
2nd N/A Hangs indefinitely

Root Cause Analysis

After reviewing the source code, the issue appears to be in the Storage layer:

1. Storage Layer (storage.rs)

pub struct Storage {
    db: Arc<Database>,  // redb database
    vector_cache: Arc<RwLock<HashMap<String, Vec<f32>>>>,
}

The insert() method uses self.db.begin_write() which creates a write transaction. The transaction may not be properly released after the first insert, causing subsequent inserts to block waiting for the lock.

2. Index Layer (index.rs)

self.vectors.write().insert(id.clone(), vector.clone());
let mut graph = self.graph.write();
// ... KNN search called while holding write lock ...
let mut entry_point = self.entry_point.write();

The search_knn_internal() is called within the write lock scope, which acquires read locks on the same data structures. This could cause lock contention issues.

3. Count Discrepancy

The count returning 2 after first insert suggests either:

  • Double insertion occurring
  • Index not properly synchronized with storage

Suggested Fixes

Option 1: Ensure proper transaction scope

pub fn insert(&self, entry: VectorEntry) -> Result<String> {
    let write_txn = self.db.begin_write()?;
    {
        let mut table = write_txn.open_table(VECTORS_TABLE)?;
        table.insert(&entry.id, entry.to_bytes())?;
    } // Scope ensures table is dropped before commit
    write_txn.commit()?;
    Ok(entry.id)
}

Option 2: Use Mutex for Database access

pub struct Storage {
    db: Arc<Mutex<Database>>,  // Serialize all database access
    ...
}

Option 3: Move KNN search outside write lock

// Collect neighbors first (read-only)
let neighbors = self.search_knn_internal(&vector, self.config.m)?;

// Then acquire write lock for mutation only
let mut graph = self.graph.write();
// ... update graph ...

Workaround

We've implemented a pure JavaScript SemanticRouter as a workaround that provides:

  • 34,798 routes/s throughput
  • 0.029ms average latency
  • Cosine similarity matching
  • No native dependencies

See: https://github.com/ruvnet/claude-flow/blob/main/v3/%40claude-flow/cli/src/ruvector/semantic-router.ts

Impact

This bug affects anyone using @ruvector/router for vector storage with more than one vector. The SemanticRouter wrapper (added in v0.1.26) has a fallback mode, but the native performance benefits are lost.

Version Info

@ruvector/router: 0.1.25, 0.1.26
@ruvector/router-linux-x64-gnu: 0.1.25 (latest available)

Note: v0.1.26 of the main package was published but the platform binary is still at v0.1.25, which may indicate the bug was discovered but not fully fixed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions