Limitations of the Current Graph Storage Implementation in Codebase-Memory-MCP

The in-memory graph buffer is constrained by memory-bound storage, single-process lifecycle limits, thread-unsafe data structures, and destructive edge deduplication that can lose granular relationship data.

The graph storage implementation in the DeusData/codebase-memory-mcp repository provides fast O(1) lookups and low-overhead deduplication for incremental indexing, but faces significant architectural constraints when scaling to large codebases or long-running analyses. The core CBMHashTable and dynamic array structures defined in src/graph_buffer/graph_buffer.h prioritize speed over persistence, creating bottlenecks in memory usage, concurrency, and data durability. Understanding these limitations is essential when deploying the MCP on massive repositories or integrating it into automated build pipelines.

Memory-Bound Architecture and Scalability Constraints

Linear RAM Growth Without Disk Spillover

The graph buffer (cbm_gbuf_t) stores all nodes, edges, and auxiliary indexes exclusively in heap memory. While the implementation uses string interning to avoid duplication, the total footprint grows linearly with the number of graph elements. In src/graph_buffer/graph_buffer.c, the cbm_gbuf_new function allocates separate heap blocks for each node and edge, with no mechanism to spill data to disk when physical memory is exhausted.

This design makes the tool unusable on machines with modest RAM when processing extremely large codebases. The buffer lacks virtual memory paging or overflow files, meaning the process will terminate with an out-of-memory error once the system's physical limits are reached.

Static Allocation Limits and Fragmentation Risks

Certain internal buffers, such as those storing semantic embeddings, initialize with VEC_INIT_CAP = 1024 and grow exponentially through reallocation. While this amortizes insertion costs, the reallocation logic in src/graph_buffer/graph_buffer.c temporarily requires large contiguous memory blocks.

In 32-bit build environments or systems with fragmented address space, these reallocations can fail even when total free memory is sufficient. The code does not gracefully handle allocation failures for edge cases involving massive graphs or extremely long identifiers.

Lifecycle and Persistence Limitations

Single-Process Ephemeral Design

The buffer exists only for the duration of a single pipeline run. As implemented in cbm_gbuf_free, the entire structure is created, populated, dumped to SQLite via cbm_gbuf_dump_to_sqlite, and then destroyed. There is no on-disk caching or partial persistence between runs.

This forces long-running analyses to re-process the entire project from scratch on each invocation. Incremental builds must rely on re-reading the SQLite database rather than retaining an in-memory snapshot, significantly increasing startup latency for large projects.

Absence of Checkpoint or Snapshot Mechanisms

The graph buffer does not support partial dumps or resumable checkpoints. The only persistence point is cbm_gbuf_dump_to_sqlite, which writes the complete graph in a single blocking pass. If this operation is interrupted by a crash or SQLite timeout, the entire analysis must restart from the beginning.

For large graphs that cause I/O bottlenecks or SQLite write-timeouts, this all-or-nothing approach creates durability risks and prevents incremental backup strategies during multi-hour indexing operations.

Concurrency and Parallel Processing Bottlenecks

Thread-Unsafe Core Data Structures

While the API supports parallel extraction through cbm_gbuf_new_shared_ids for atomic ID generation, the underlying hash tables and dynamic arrays are not thread-safe. The CBMHashTable structures for node and edge indexing lack mutex protection or lock-free algorithms.

Worker threads must operate on independent buffer instances and merge results later using cbm_gbuf_merge. This merge step serializes parallel work and can become a significant bottleneck when combining buffers from many worker processes.

Non-Deterministic Merge Behavior

The cbm_gbuf_merge function handles collisions between worker buffers, but the implementation contains explicit logic for handling "Module" versus "Folder" node ordering that introduces nondeterminism. Comments in src/graph_buffer/graph_buffer.c highlight that merge ordering affects how these special node types are resolved, potentially creating inconsistent graph structures across different runs of the same codebase.

Data Integrity and Deduplication Edge Cases

Destructive Edge Deduplication Semantics

Deduplication occurs on a composite key of srcID:tgtID:type (plus local_name for IMPORTS edges). When cbm_gbuf_insert_edge detects a collision, it overwrites the existing edge and its properties with the new values.

This deliberately collapses multiple call-site edges between the same nodes into a single entry. For analyses requiring per-call granularity—such as distinguishing between different invocation sites of the same function—this data loss is irreversible. The second insertion's JSON properties replace the first, as shown in the deduplication logic:

/* Example: deduplication overwrites previous properties */
int64_t e1 = cbm_gbuf_insert_edge(gbuf, src_id, tgt_id, "CALL", "{\"line\":5}");
int64_t e2 = cbm_gbuf_insert_edge(gbuf, src_id, tgt_id, "CALL", "{\"line\":8}");
/* e2 == e1, but the line number is now 8, losing the line 5 reference */

Fixed-Size Key Buffer Constraints

Edge keys are constructed in a 256-byte stack buffer (EDGE_KEY_BUF). While the code falls back to FNV-1a hashing for keys exceeding this limit, the implementation assumes the hash plus prefix fits within the fixed buffer.

Extremely long identifiers or deeply nested qualified names could cause malformed keys if the hash computation truncates incorrectly, leading to accidental edge loss or hash collisions that break graph integrity.

Operational and Error Handling Constraints

Limited Error Reporting and Silent Failures

Most API functions return 0 on success and -1 (GB_ERR) on failure, but do not propagate detailed error codes for specific failure modes like out-of-memory conditions or SQLite errors. The public API lacks a rich error-reporting mechanism, relying instead on debug logs that require manual inspection.

Consumers of the library cannot programmatically distinguish between allocation failures, disk full errors, or constraint violations, making automated error recovery impossible without parsing log output.

No Built-In Compaction or Garbage Collection

Deletion functions cbm_gbuf_delete_by_label and cbm_gbuf_delete_by_file remove nodes from primary indexes and cascade-delete edges, but the underlying pointer arrays (nodes, edges) retain their capacity. Memory for deleted elements is only reclaimed after a full cbm_gbuf_free.

Repeated incremental indexing may cause the buffer to grow monotonically even after deleting many elements, increasing memory pressure over time until the process is restarted.

Input Validation and Robustness Gaps

The graph buffer assumes well-formed input from all callers. Functions like cbm_gbuf_upsert_node and cbm_gbuf_insert_edge accept raw JSON strings for properties without validating the structure before storage. No schema validation occurs for qualified names, IDs, or node labels.

Corrupt or malicious input can propagate directly into the SQLite database, creating malformed rows that break downstream queries in get_graph_schema or search_graph. The lack of input sanitization makes the system vulnerable to injection-like failures when processing untrusted codebases.

Summary

  • Memory-bound storage: All graph data resides in RAM with no disk overflow, limiting scalability to available physical memory.
  • Ephemeral lifecycle: No persistence between pipeline runs; crashes during cbm_gbuf_dump_to_sqlite require full reprocessing.
  • Concurrency limits: Thread-unsafe hash tables force expensive merges; cbm_gbuf_merge introduces nondeterministic ordering.
  • Destructive deduplication: Duplicate edges overwrite previous properties, losing per-call granularity for identical src/tgt pairs.
  • Fixed key buffers: The 256-byte EDGE_KEY_BUF may truncate or corrupt keys for extremely long identifiers.
  • Poor error handling: Binary return codes (0/GB_ERR) lack granularity for automated failure diagnosis.
  • No compaction: Deleted nodes retain memory capacity until cbm_gbuf_free is called.
  • Assumed valid input: No JSON validation before storage allows corrupt data to reach the database.

Frequently Asked Questions

Why does the graph buffer lose edge data when inserting duplicate relationships?

The cbm_gbuf_insert_edge function deduplicates on srcID:tgtID:type and overwrites existing properties with new values. This design choice optimizes for storage efficiency by collapsing multiple calls between the same functions into a single edge, but sacrifices per-call metadata like specific line numbers. If you need to preserve every call site, you must encode uniqueness into the edge type or properties before insertion.

Can I resume a failed graph dump without restarting the entire pipeline?

No. The current implementation lacks checkpoint or snapshot capabilities. The cbm_gbuf_dump_to_sqlite function writes the entire buffer in one transaction, and interruption requires rebuilding the graph from source. For large codebases, ensure sufficient disk space and SQLite timeout configurations before initiating the dump phase to avoid mid-operation failures.

How does the graph buffer handle memory exhaustion during large codebase indexing?

It does not. The buffer continues allocating memory linearly until the system OOM killer terminates the process or malloc returns NULL. There is no spill-to-disk mechanism or graceful degradation. For massive repositories, monitor memory usage and consider splitting the analysis into smaller sub-projects that can be merged at the SQLite level rather than in the graph buffer.

Is the graph buffer thread-safe for parallel parsing?

No. While cbm_gbuf_new_shared_ids provides atomic ID generation, the hash tables and dynamic arrays are not thread-safe. Workers must use separate buffer instances and merge results via cbm_gbuf_merge, which becomes a serialization bottleneck. The merge logic also handles "Module" and "Folder" node ordering nondeterministically, potentially creating different graph structures across runs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →