Skip to content

Implement new storage to improve performance - #3

Merged
hungpham10 merged 4 commits into
mainfrom
feature/add-new-storage
Aug 7, 2026
Merged

Implement new storage to improve performance#3
hungpham10 merged 4 commits into
mainfrom
feature/add-new-storage

Conversation

@hungpham10

@hungpham10 hungpham10 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added LMDB as a persistent storage option alongside SQLite, Redis, and in-memory storage.
    • Added configurable storage backends and DSNs through .codegraph/config.toml.
    • Added support for searching across both functions and methods.
    • Added storage performance benchmarking across available backends.
  • Bug Fixes

    • Prevented duplicate node IDs during concurrent SQLite transactions.
    • Improved symbol resolution and preservation of long paths and call names.
  • Documentation

    • Added storage backend performance comparisons and usage recommendations.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This change adds LMDB persistence, DSN-based storage selection, configurable storage backends, backend-aware CLI and MCP integration, symbol search improvements, concurrency-safe SQLite ID allocation, and storage performance benchmarks.

Changes

Storage backend integration

Layer / File(s) Summary
Storage configuration and feature wiring
Cargo.toml, crates/codegraph-extract/src/config.rs, crates/codegraph-graph/Cargo.toml, crates/codegraph/Cargo.toml, crates/codegraph-bench/Cargo.toml
Adds LMDB dependencies and features. Adds storage configuration, DSN generation, default configuration, and benchmark registration.
LMDB storage implementation
crates/codegraph-graph/src/storage/lmdb.rs, crates/codegraph-graph/src/storage.rs
Adds LMDB storage and transaction implementations with persistence, atomic commits, version probing, and long-key support.
Graph DSN dispatch and search behavior
crates/codegraph-graph/src/lib.rs, crates/codegraph-graph/src/shared.rs
Adds scheme-based backend selection and DSN handling. Adds multi-kind symbol search and tighter call resolution filtering.
CLI, MCP, watcher, and benchmark DSN flow
crates/codegraph/src/main.rs, crates/codegraph/src/watcher.rs, crates/codegraph-mcp/src/lib.rs, crates/codegraph-mcp/src/tools.rs, crates/codegraph-bench/src/lib.rs
Routes configured DSNs through indexing, shared indexes, MCP tools, watchers, and benchmark indexing.
Backend validation and benchmarks
crates/codegraph-graph/src/storage/sqlite.rs, crates/codegraph-graph/tests/*.rs, crates/codegraph-api/tests/api.rs, crates/codegraph-bench/benches/storage.rs, crates/codegraph-bench/STORAGE_PERF.md
Adds SQLite concurrent-ID regression coverage, LMDB integration tests, SQLite DSN test updates, and storage benchmark code and results.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

A rabbit checks each DSN path,
LMDB stores the graph’s craft.
SQLite IDs now stay unique,
Methods search with results true.
Benchmarks measure every track.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a new storage backend to improve performance.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/add-new-storage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 12 untouched benchmarks
🆕 16 new benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
🆕 open+ingest N/A 22.7 ms N/A
🆕 open+ingest N/A 67.5 ms N/A
🆕 sample N/A 13.5 ms N/A
🆕 open+ingest N/A 214.9 ms N/A
🆕 open+ingest N/A 85.1 ms N/A
🆕 open+ingest N/A 244.9 ms N/A
🆕 sample N/A 129.5 ms N/A
🆕 open+ingest N/A 1 s N/A
🆕 open+ingest N/A 13.2 ms N/A
🆕 open+ingest N/A 35.4 ms N/A
🆕 sample N/A 4.3 ms N/A
🆕 open+ingest N/A 113.4 ms N/A
🆕 open+ingest N/A 76 ms N/A
🆕 open+ingest N/A 221.2 ms N/A
🆕 sample N/A 55.2 ms N/A
🆕 open+ingest N/A 979.9 ms N/A

Comparing feature/add-new-storage (c261121) with main (249f9a8)

Open in CodSpeed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (1)
crates/codegraph-graph/tests/lmdb.rs (1)

281-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test the long call name directly.

search_flow(&[SYMBOL_BASE]) does not read long_call. A regression that truncates or drops the call name can still pass this test. Assert the returned flow call name equals long_call, and add a production call-name lookup assertion if that index is part of the contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-graph/tests/lmdb.rs` around lines 281 - 284, Update the test
around search_flow to assert the returned flow call name matches the long_call
fixture value, rather than only checking function_name. Also add a production
call-name lookup assertion using the relevant lookup API if the call-name index
is part of the tested contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/codegraph-bench/benches/storage.rs`:
- Around line 23-41: Update the repository-list loading flow around
CODEGRAPH_BENCH_REPOS_LIST so a read_to_string failure is not silently converted
into an empty benchmark set. Return the read error if the surrounding function
supports errors, or log it and continue with the existing crates fallback; only
return the parsed list after a successful read.
- Line 109: Resolve the clippy::type_complexity warning for mk_backends by
introducing a descriptive type alias for the boxed backend factory closure, or
by replacing the boxed closures with a small backend enum. Update the
mk_backends declaration and its usages consistently while preserving the
existing factory behavior.
- Around line 70-78: Update the SQLite and LMDB setup flows around index_at so
indexing errors are not silently discarded. Propagate failures from both
persistent backend calls, or explicitly represent each backend as unavailable
and skip its size calculation; do not report an empty directory as a successful
zero-byte backend.
- Around line 70-78: In the benchmark storage setup around index_at and
dir_size, stop calling TempDir::keep() for both SQLite and LMDB directories;
retain the TempDir values, use their paths to build storage URLs and measure
sizes, then let them drop after measurement. Apply the same cleanup change at
crates/codegraph-bench/benches/storage.rs lines 70-78 and 114-123.

In `@crates/codegraph-extract/src/config.rs`:
- Around line 139-143: Update the storage backend resolution around the match on
StorageKind in the configuration path to reject StorageKind::Redis when no DSN
is configured, rather than returning None. Make the resolution fallible or
validate the configuration during loading, and ensure invalid Redis settings
propagate an explicit configuration error instead of being interpreted by CLI or
MCP consumers as GraphIndex::in_memory().

In `@crates/codegraph-graph/src/lib.rs`:
- Around line 178-183: Update GraphIndex::open and split_dsn to recognize the
redis scheme and dispatch redis:// DSNs through a feature-gated Redis opener,
alongside the existing SQLite and LMDB routes. Keep the plain-DSN fallback for
unrecognized schemes and preserve behavior when Redis support is disabled.

In `@crates/codegraph-graph/src/shared.rs`:
- Around line 82-104: Update current_version to probe Redis using the same
version key and retrieval/parsing behavior as RedisStorage::version(), returning
the stored u64 instead of None. Keep is_fresh’s version comparison unchanged,
and add an integration test covering two unchanged ensure_fresh calls that
confirms the second call reuses the existing snapshot.

In `@crates/codegraph-graph/src/storage/lmdb.rs`:
- Around line 353-374: Move all LMDB write operations in LmdbStorage mutation
methods, including Storage::new_node, LmdbTx::commit, and every other
begin_rw_txn/tx.commit path, off Tokio worker threads by executing them through
spawn_blocking or the existing dedicated blocking storage thread. Preserve
transaction ordering, error propagation, and returned values while ensuring no
LMDB write transaction is opened or committed directly in async worker-thread
code.

In `@crates/codegraph/src/main.rs`:
- Around line 549-554: Update the entry-point descriptions and failure messages
used by the lookup flow in crates/codegraph/src/main.rs (lines 549-554 and the
related error at line 558) and crates/codegraph-mcp/src/tools.rs (lines 746-748
and the related error at line 751) to consistently refer to both functions and
methods, while preserving the existing Function/Method search behavior.
- Around line 217-228: Preserve indexed graph state when storage_dsn returns
None instead of ingesting into a temporary dropped index. In
crates/codegraph/src/main.rs lines 217-228, distinguish explicit in-memory
storage from Redis lacking a DSN and reject unsupported CLI/MCP modes or retain
shared state; in crates/codegraph/src/main.rs lines 269-277, ensure the index
used by the indexing command remains available to later operations; apply the
same policy in crates/codegraph-mcp/src/tools.rs lines 638-645 for
codegraph_index.
- Line 146: Update is_initialized and the cmd_init/cmd_default readiness flow to
distinguish workspace directory creation from completed indexing: require the
workspace path to pass is_dir(), and use a separate index-ready marker that is
created only after indexing succeeds. Have cmd_default report “Workspace Active
& Indexed” only when that marker exists, while allowing cmd_init to create the
directory without marking the index ready.

---

Nitpick comments:
In `@crates/codegraph-graph/tests/lmdb.rs`:
- Around line 281-284: Update the test around search_flow to assert the returned
flow call name matches the long_call fixture value, rather than only checking
function_name. Also add a production call-name lookup assertion using the
relevant lookup API if the call-name index is part of the tested contract.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8736dc6-ce18-409e-bfdb-df9e1aac380b

📥 Commits

Reviewing files that changed from the base of the PR and between 249f9a8 and b0413bb.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • Cargo.toml
  • crates/codegraph-api/tests/api.rs
  • crates/codegraph-bench/Cargo.toml
  • crates/codegraph-bench/STORAGE_PERF.md
  • crates/codegraph-bench/benches/storage.rs
  • crates/codegraph-bench/src/lib.rs
  • crates/codegraph-extract/src/config.rs
  • crates/codegraph-extract/src/walker.rs
  • crates/codegraph-graph/Cargo.toml
  • crates/codegraph-graph/src/lib.rs
  • crates/codegraph-graph/src/shared.rs
  • crates/codegraph-graph/src/storage.rs
  • crates/codegraph-graph/src/storage/lmdb.rs
  • crates/codegraph-graph/src/storage/sqlite.rs
  • crates/codegraph-graph/tests/lmdb.rs
  • crates/codegraph-graph/tests/sqlite.rs
  • crates/codegraph-mcp/src/lib.rs
  • crates/codegraph-mcp/src/tools.rs
  • crates/codegraph/Cargo.toml
  • crates/codegraph/src/main.rs
  • crates/codegraph/src/watcher.rs

Comment on lines +23 to +41
if let Ok(list_file) = std::env::var("CODEGRAPH_BENCH_REPOS_LIST") {
if let Ok(body) = std::fs::read_to_string(&list_file) {
for line in body.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let name = std::path::Path::new(line)
.file_name()
.and_then(|s| s.to_str())
.map(String::from)
.unwrap_or_else(|| line.to_string());
out.push(Repo {
name,
root: line.into(),
});
}
}
return out;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not silently skip all benchmarks after a list-file read error.

If CODEGRAPH_BENCH_REPOS_LIST exists but read_to_string fails, this function returns an empty list. Criterion can then finish successfully without benchmark results. Return an error, or log the error and use the crates fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-bench/benches/storage.rs` around lines 23 - 41, Update the
repository-list loading flow around CODEGRAPH_BENCH_REPOS_LIST so a
read_to_string failure is not silently converted into an empty benchmark set.
Return the read error if the surrounding function supports errors, or log it and
continue with the existing crates fallback; only return the parsed list after a
successful read.

Comment on lines +70 to +78
let sqlite_dir = tempfile::tempdir().unwrap().keep();
let sqlite = format!("sqlite://{}/db.sqlite", sqlite_dir.to_string_lossy());
if let Ok(_idx) = index_at(parsed, Some(&sqlite)) {}
let sqlite_bytes = dir_size(&sqlite_dir);

let lmdb_dir = tempfile::tempdir().unwrap().keep();
let lmdb = format!("lmdb://{}", lmdb_dir.to_string_lossy());
if let Ok(_idx) = index_at(parsed, Some(&lmdb)) {}
let lmdb_bytes = dir_size(&lmdb_dir);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fail when persistent backend setup fails.

The empty if let Ok(...) blocks discard indexing errors. The following size calculation can report an empty directory as a valid zero-byte backend. Propagate the error or explicitly mark that backend as unavailable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-bench/benches/storage.rs` around lines 70 - 78, Update the
SQLite and LMDB setup flows around index_at so indexing errors are not silently
discarded. Propagate failures from both persistent backend calls, or explicitly
represent each backend as unavailable and skip its size calculation; do not
report an empty directory as a successful zero-byte backend.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Repository files matching storage.rs:\n'
fd -a 'storage\.rs$' . | sed 's#^\./##'

printf '\nTarget file outline:\n'
ast-grep outline crates/codegraph-bench/benches/storage.rs || true

printf '\nRelevant sections:\n'
sed -n '1,180p' crates/codegraph-bench/benches/storage.rs

printf '\nDisk-related benches/search:\n'
rg -n "tempfile::tempdir|TempDir::keep|keep\(|index_at|dir_size|GraphIndex|bench|Criterion|storage" crates/codegraph-bench/benches/storage.rs

Repository: hungpham10/codegraph-rs

Length of output: 8097


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check tempfile crate docs/source availability via Cargo.lock/Cargo.toml and installed docs if present.
printf 'tempfile declarations:\n'
rg -n '^\[.*tempfile|^\[package\]|name = "tempfile"|version = ' Cargo.lock crates/codegraph-bench/Cargo.toml Cargo.toml || true

printf '\nCargo docs availability:\n'
if [ -d "$CARGO_HOME/doc/tempfile" ]; then
  find "$CARGO_HOME/doc/tempfile" -maxdepth 2 -type f | sed -n '1,40p'
else
  echo "cargo doc for tempfile not found"
fi

Repository: hungpham10/codegraph-rs

Length of output: 14298


🌐 Web query:

tempfile Rust 3.27 TempDir keep documentation

💡 Result:

In the Rust tempfile crate (version 3.27.0), the TempDir::keep method is used to persist a temporary directory on the disk [1]. Calling TempDir::keep consumes the TempDir instance and prevents the directory from being automatically deleted when it goes out of scope (i.e., when the object is dropped) [1]. It returns a PathBuf representing the path to the directory, allowing you to manage it manually [1]. Key points regarding keeping or persisting temporary directories: - TempDir::keep: This is the preferred method for turning a temporary directory into a permanent one [1]. It consumes the TempDir object [1]. - disable_cleanup: If you want to prevent automatic deletion but keep the TempDir object available for further use (rather than consuming it), you can call disable_cleanup(true) [1]. This is typically recommended for testing or debugging scenarios rather than simply persisting a directory [1]. - Deprecation Note: The method into_path has been deprecated in favor of TempDir::keep [1].

Citations:


Clean up benchmark temporary storage after measurement.

All these sites call TempDir::keep(), which disables automatic cleanup. Let the SQLite/LMDB temp directories remain a normal TempDir and drop them after indexing or size measurement so repeated benchmark iterations do not create permanent storage directories.

📍 Affects 1 file
  • crates/codegraph-bench/benches/storage.rs#L70-L78 (this comment)
  • crates/codegraph-bench/benches/storage.rs#L114-L123
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-bench/benches/storage.rs` around lines 70 - 78, In the
benchmark storage setup around index_at and dir_size, stop calling
TempDir::keep() for both SQLite and LMDB directories; retain the TempDir values,
use their paths to build storage URLs and measure sizes, then let them drop
after measurement. Apply the same cleanup change at
crates/codegraph-bench/benches/storage.rs lines 70-78 and 114-123.

Comment thread crates/codegraph-bench/benches/storage.rs Outdated
Comment on lines +139 to +143
match self.storage.kind {
StorageKind::Sqlite => Some(format!("sqlite://{}", project_db_path(root))),
StorageKind::Lmdb => Some(format!("lmdb://{}", project_dir(root).join("db.lmdb"))),
StorageKind::Redis => None,
StorageKind::Memory => None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject a Redis configuration without a DSN.

StorageKind::Redis returns None here. The CLI and MCP consumers interpret None as GraphIndex::in_memory(). A project configured with type = "redis" therefore indexes into ephemeral memory instead of reporting invalid configuration.

Make backend resolution fallible, or validate Redis during config loading. Do not use None for this configuration error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-extract/src/config.rs` around lines 139 - 143, Update the
storage backend resolution around the match on StorageKind in the configuration
path to reject StorageKind::Redis when no DSN is configured, rather than
returning None. Make the resolution fallible or validate the configuration
during loading, and ensure invalid Redis settings propagate an explicit
configuration error instead of being interpreted by CLI or MCP consumers as
GraphIndex::in_memory().

Comment on lines 178 to +183
pub async fn open(dsn: &str) -> Result<Self> {
#[cfg(feature = "sqlite")]
#[allow(unreachable_code)]
return Self::open_sqlite(dsn).await;
match Self::split_dsn(dsn) {
Some(("sqlite", path)) => Self::open_sqlite_dispatch(path).await,
Some(("lmdb", path)) => Self::open_lmdb_dispatch(path).await,
_ => Self::open_default(dsn).await,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Dispatch redis:// before the plain-DSN fallback.

split_dsn() does not recognize redis://. With Redis and SQLite or LMDB enabled, GraphIndex::open("redis://...") enters open_default() and returns the “DSN does not specify scheme” error.

Add Redis to split_dsn() and route it through a feature-gated Redis dispatch method.

Proposed fix
     match Self::split_dsn(dsn) {
         Some(("sqlite", path)) => Self::open_sqlite_dispatch(path).await,
         Some(("lmdb", path)) => Self::open_lmdb_dispatch(path).await,
+        Some(("redis", dsn)) => Self::open_redis_dispatch(dsn).await,
         _ => Self::open_default(dsn).await,
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-graph/src/lib.rs` around lines 178 - 183, Update
GraphIndex::open and split_dsn to recognize the redis scheme and dispatch
redis:// DSNs through a feature-gated Redis opener, alongside the existing
SQLite and LMDB routes. Keep the plain-DSN fallback for unrecognized schemes and
preserve behavior when Redis support is disabled.

Comment on lines 82 to +104
async fn current_version(&self) -> Option<u64> {
let p = self.path.as_ref()?;
crate::storage::sqlite::SqliteStorage::probe_version(&p.display().to_string())
.await
.ok()
let dsn = self.dsn.as_ref()?;
let path = trim_scheme(dsn);
match self.scheme() {
#[cfg(feature = "sqlite")]
Some("sqlite") => crate::storage::sqlite::SqliteStorage::probe_version(path)
.await
.ok(),
#[cfg(feature = "lmdb")]
Some("lmdb") => crate::storage::lmdb::probe_version(path).await.ok(),
// redis không có probe file ngoài — không đo được → stale.
_ => None,
}
}

/// Snapshot hiện tại có khớp version trên đĩa không. In-memory (không file)
/// → không có writer ngoài → luôn fresh.
/// → không có writer ngoài → luôn fresh. Backend không probe được (redis/
/// unknown scheme) → coi là stale để rebuilt lại.
async fn is_fresh(&self, version: u64) -> bool {
#[cfg(feature = "sqlite")]
{
if self.path.is_none() {
return true;
}
matches!(self.current_version().await, Some(v) if v == version)
}
#[cfg(not(feature = "sqlite"))]
{
let _ = version;
true
if self.dsn.is_none() {
return true;
}
matches!(self.current_version().await, Some(v) if v == version)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Probe the Redis version instead of rebuilding on every request.

current_version() returns None for Redis. is_fresh() then always returns false. Every ensure_fresh() call rebuilds the Redis-backed graph snapshot.

Add a Redis version probe using the same version key as RedisStorage::version(). Add an integration test that verifies two unchanged ensure_fresh() calls reuse the same snapshot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-graph/src/shared.rs` around lines 82 - 104, Update
current_version to probe Redis using the same version key and retrieval/parsing
behavior as RedisStorage::version(), returning the stored u64 instead of None.
Keep is_fresh’s version comparison unchanged, and add an integration test
covering two unchanged ensure_fresh calls that confirms the second call reuses
the existing snapshot.

Comment on lines +353 to +374
#[async_trait]
impl Storage for LmdbStorage {
async fn new_node(&mut self, prefix: Vec<u8>, record: usize) -> Result<usize> {
let mut tx = self.env.begin_rw_txn().map_err(e)?;
// Không có RETURNING — đọc-rồi-ghi counter trong cùng write tx; an toàn
// vì GraphIndex tuần tự hoá mọi writer qua RwLock.
let next = match self.get_opt(&tx, self.counter, &KEY_ONE)? {
Some(v) => de_u64(v),
None => 1,
};
let id = next as usize;
tx.put(self.counter, &KEY_ONE, &ku64(next + 1), WriteFlags::empty())
.map_err(e)?;
tx.put(
self.nodes,
&k8(id),
&node_val(&prefix, record),
WriteFlags::empty(),
)
.map_err(e)?;
tx.commit().map_err(e)?;
Ok(id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

For lmdb-rkv 0.14, confirm the behavior of Environment::begin_rw_txn when another write transaction is active.

💡 Result:

In lmdb-rkv 0.14, the Environment::begin_rw_txn method is explicitly documented to block when another write transaction is active [1]. Because LMDB enforces serialized write access, only one write transaction may be active at any given time [2][3]. Consequently, if a thread calls begin_rw_txn while another write transaction is currently in progress, it will block until that active write transaction either commits or aborts [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- files ---\n'
fd -a 'lmdb\.rs|Cargo\.(toml|lock)' . | sed 's#^\./##' | head -100

printf '\n--- lmdb file outline/counts ---\n'
if [ -f crates/codegraph-graph/src/storage/lmdb.rs ]; then
  wc -l crates/codegraph-graph/src/storage/lmdb.rs
  ast-grep outline crates/codegraph-graph/src/storage/lmdb.rs --view compact | head -200 || true
fi

printf '\n--- relevant write methods ---\n'
if [ -f crates/codegraph-graph/src/storage/lmdb.rs ]; then
  rg -n "begin_rw_txn|spawn_blocking|Storage|new_node|commit_storage|commit|update|delete|write" crates/codegraph-graph/src/storage/lmdb.rs -C 3
fi

printf '\n--- dependency versions ---\n'
rg -n 'lmdb-rkv|rkv|tokio' Cargo.toml crates -g 'Cargo.toml' Cargo.lock -C 2

Repository: hungpham10/codegraph-rs

Length of output: 252


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- lmdb file outlines/counts ---\n'
fd -a 'lmdb\.rs|Cargo\.(toml|lock)' . | sed 's#^\./##' | head -100

printf '\n--- relevant lmdb.rs sections ---\n'
if [ -f crates/codegraph-graph/src/storage/lmdb.rs ]; then
  wc -l crates/codegraph-graph/src/storage/lmdb.rs
  ast-grep outline crates/codegraph-graph/src/storage/lmdb.rs --view compact | head -200 || true
  sed -n '1,520p' crates/codegraph-graph/src/storage/lmdb.rs | nl -ba | sed -n '320,410p'
fi

printf '\n--- dependency versions ---\n'
rg -n 'lmdb-rkv|rkv|tokio|async-trait|parking_lot' Cargo.toml crates -g 'Cargo.toml' Cargo.lock -C 2

printf '\n--- async storage call sites with new_node/write names ---\n'
rg -n "new_node\(|commit_storage\(|begin_rw_txn\(|spawn_blocking" crates -C 2

Repository: hungpham10/codegraph-rs

Length of output: 252


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate files ---'
fd 'lmdb|Cargo\.(toml|lock)' . | sed 's#^\./##' | head -100

printf '%s\n' '--- lmdb.rs references ---'
if [ -f crates/codegraph-graph/src/storage/lmdb.rs ]; then
  wc -l crates/codegraph-graph/src/storage/lmdb.rs
  ast-grep outline crates/codegraph-graph/src/storage/lmdb.rs --view compact | head -200 || true
  sed -n '300,400p' crates/codegraph-graph/src/storage/lmdb.rs | nl -ba
  printf '%s\n' '--- all write-related methods ---'
  rg -n "begin_rw_txn|spawn_blocking|Storage|new_node|commit_storage|commit|update|delete|create" crates/codegraph-graph/src/storage/lmdb.rs -C 3
fi

printf '%s\n' '--- dependency versions ---'
rg -n 'lmdb-rkv|rkv|tokio|async-trait|parking_lot' Cargo.toml crates -g 'Cargo.toml' Cargo.lock -C 2 || true

printf '%s\n' '--- async storage call sites ---'
rg -n "new_node\(|commit_storage\(|begin_rw_txn\(|spawn_blocking" crates -C 2 || true

Repository: hungpham10/codegraph-rs

Length of output: 881


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- lmdb.rs lines 300-390 ---'
sed -n '300,390p' crates/codegraph-graph/src/storage/lmdb.rs

printf '%s\n' '--- lmdb.rs all begin_rw_txn/write-related lines ---'
rg -n "begin_rw_txn|commit\b|Storage|new_node|commit_storage|set|delete|write" crates/codegraph-graph/src/storage/lmdb.rs -C 3

printf '%s\n' '--- async storage call sites ---'
rg -n "new_node\(|commit_storage\(|begin_rw_txn\(|spawn_blocking" crates -C 2 || true

printf '%s\n' '--- dependency versions ---'
rg -n 'lmdb-rkv|rkv|tokio|async-trait|parking_lot|mdbx' Cargo.toml crates -g 'Cargo.toml' Cargo.toml.lock || true

Repository: hungpham10/codegraph-rs

Length of output: 50379


Do not run LMDB write transactions on Tokio worker threads.

Environment::begin_rw_txn() and tx.commit() can block for an active write transaction, so LmdbStorage mutation methods and LmdbTx::commit should run behind spawn_blocking or a dedicated blocking storage thread. Apply this to all LMDB write paths such as new_node, commit, and the other write methods.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-graph/src/storage/lmdb.rs` around lines 353 - 374, Move all
LMDB write operations in LmdbStorage mutation methods, including
Storage::new_node, LmdbTx::commit, and every other begin_rw_txn/tx.commit path,
off Tokio worker threads by executing them through spawn_blocking or the
existing dedicated blocking storage thread. Preserve transaction ordering, error
propagation, and returned values while ensuring no LMDB write transaction is
opened or committed directly in async worker-thread code.


fn cmd_default(root: &Utf8Path) -> Result<()> {
if !db_path(root).exists() {
if !is_initialized(root) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Track workspace initialization separately from index readiness.

cmd_init can create .codegraph without indexing. is_initialized then returns true, and cmd_default can print “Workspace Active & Indexed” after opening an empty backend. A regular file also passes exists(). Use a separate index-ready marker and use is_dir() for the directory check.

Also applies to: 231-236

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph/src/main.rs` at line 146, Update is_initialized and the
cmd_init/cmd_default readiness flow to distinguish workspace directory creation
from completed indexing: require the workspace path to pass is_dir(), and use a
separate index-ready marker that is created only after indexing succeeds. Have
cmd_default report “Workspace Active & Indexed” only when that marker exists,
while allowing cmd_init to create the directory without marking the index ready.

Comment thread crates/codegraph/src/main.rs
Comment thread crates/codegraph/src/main.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/codegraph-graph/src/storage/lmdb.rs (1)

1083-1100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert environment identity instead of only returned versions.

The current assertions only exercise probe_version; an implementation that opens a new Environment for every probe call and reads the same version would still pass. Call probe_env twice per path and assert Arc::ptr_eq on the returned values while keeping the concurrent probe checks as separate functional coverage.

Proposed test addition
+        let env1 = probe_env(&path).unwrap();
+        let env1_again = probe_env(&path).unwrap();
+        assert!(Arc::ptr_eq(&env1, &env1_again));
+
+        let env2 = probe_env(&path2).unwrap();
+        let env2_again = probe_env(&path2).unwrap();
+        assert!(Arc::ptr_eq(&env2, &env2_again));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codegraph-graph/src/storage/lmdb.rs` around lines 1083 - 1100, Update
the concurrent test around probe_version to separately call probe_env twice for
each path and assert the returned Arcs are identical with Arc::ptr_eq, verifying
shared environment caching. Preserve the existing probe_version loops and
version assertions as separate functional coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/codegraph-graph/src/storage/lmdb.rs`:
- Around line 1083-1100: Update the concurrent test around probe_version to
separately call probe_env twice for each path and assert the returned Arcs are
identical with Arc::ptr_eq, verifying shared environment caching. Preserve the
existing probe_version loops and version assertions as separate functional
coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fdefcbb6-15d3-4065-b0e5-90e092d48fe2

📥 Commits

Reviewing files that changed from the base of the PR and between a1a0665 and c261121.

📒 Files selected for processing (2)
  • crates/codegraph-bench/benches/storage.rs
  • crates/codegraph-graph/src/storage/lmdb.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/codegraph-bench/benches/storage.rs

@hungpham10
hungpham10 merged commit a029afd into main Aug 7, 2026
10 of 11 checks passed
@hungpham10
hungpham10 deleted the feature/add-new-storage branch August 9, 2026 11:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant