-
Notifications
You must be signed in to change notification settings - Fork 83
Description
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.25with@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
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.