# Impact of Batch Size on Memgraph Performance in Code-Graph-RAG

> Discover the impact of batch size on Memgraph performance in Code-Graph-RAG. Optimize ingestion with our findings on ideal batch sizes for efficient data loading.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: performance
- Published: 2026-08-15

---

**Increasing the `batch_size` parameter in `MemgraphIngestor` reduces network round-trips and amortizes query planning costs, with an optimal range of 100–250 nodes per batch for most workloads.**

The `batch_size` setting directly controls how many graph entities are buffered before a single Cypher transaction flushes them to Memgraph. This article examines the implementation details, performance trade-offs, and configuration guidance based on the [vitali87/code-graph-rag](https://github.com/vitali87/code-graph-rag) source code.

---

## How Batching Works in MemgraphIngestor

In [`codebase_rag/services/graph_service.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/graph_service.py), the `MemgraphIngestor` class manages node and relationship ingestion through an internal buffer. The `batch_size` parameter determines when this buffer triggers a database write.

The buffering mechanism works as follows:

- **Add operations** (`add_node()`, `add_edge()`) append Cypher fragments to internal lists
- **Flush trigger** occurs when the buffer count reaches `self.batch_size`
- **Single transaction** executes via `_execute_query()`, merging all buffered items
- **Buffer reset** clears pending items after successful commit

```python
from codebase_rag.services.graph_service import MemgraphIngestor

# Default batch_size is 100

ingestor = MemgraphIngestor(host="localhost", port=7687, batch_size=200)

```

The `_execute_query()` method is only invoked at flush boundaries, making `batch_size` the primary lever for throughput tuning.

---

## Performance Impact by Batch Size Range

The test suite in [`codebase_rag/tests/test_memgraph_batching.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tests/test_memgraph_batching.py) systematically measures ingestion times across different batch configurations. Results demonstrate three distinct performance regions:

| Range | Behavior | Recommendation |
|-------|----------|----------------|
| **< 10** | Excessive network overhead; each node triggers a round-trip | Avoid entirely |
| **10–50** | Moderate improvement; still suboptimal for large datasets | Use only for memory-constrained environments |
| **100–250** | **Sweet spot**: minimal round-trips without excessive memory use | **Recommended default** |
| **> 500** | Diminishing returns; large Cypher strings increase parsing latency and client memory pressure | Monitor memory before using |

The performance gains stem from two database-level effects:

1. **Amortized query planning** — Memgraph parses and optimizes the Cypher statement once per batch rather than per entity
2. **Bulk operation primitives** — larger transactions allow Memgraph to apply internal batching optimizations for index updates and storage engine writes

---

## Configuring Batch Size for Your Workload

### Basic Configuration

Pass `batch_size` during `MemgraphIngestor` initialization:

```python
from codebase_rag.services.graph_service import MemgraphIngestor

# Balanced setting for typical repositories

ingestor = MemgraphIngestor(
    host="localhost",
    port=7687,
    batch_size=150
)

```

### Large Repository Ingestion

For high-volume scenarios, increase batch size to minimize transaction overhead:

```python

# Aggressive batching for bulk loads

ingestor = MemgraphIngestor(
    host="localhost", 
    port=7687,
    batch_size=250
)

for node in parsed_ast_nodes:  # Potentially millions of nodes

    ingestor.add_node(node)

# Ensure final partial batch is written

ingestor.flush_all()

```

### Memory-Constrained Environments

Reduce batch size when client memory is limited:

```python

# Conservative setting for containers or edge deployments

ingestor = MemgraphIngestor(
    host="localhost",
    port=7687, 
    batch_size=50
)

```

---

## Benchmarking Batch Size Performance

The following pattern from the test suite demonstrates how to empirically determine optimal batch size for your specific dataset:

```python
import time
from codebase_rag.services.graph_service import MemgraphIngestor

def benchmark_ingestion(batch_size: int, node_count: int = 10_000) -> float:
    """Measure time to ingest nodes with specified batch size."""
    ingestor = MemgraphIngestor(
        host="localhost",
        port=7687,
        batch_size=batch_size
    )
    
    start = time.perf_counter()
    for i in range(node_count):
        ingestor.add_node({
            "id": f"node_{i}",
            "type": "Function",
            "file": "example.py"
        })
    ingestor.flush_all()
    
    return time.perf_counter() - start

# Compare configurations

results = {}
for batch in [10, 50, 100, 200, 500]:
    elapsed = benchmark_ingestion(batch)
    results[batch] = elapsed
    print(f"Batch size {batch:3d}: {elapsed:.2f}s")

```

Typical output shows **100–200** achieving the lowest elapsed time before memory effects dominate.

---

## Implementation Details

### Key Source Files

- **[`codebase_rag/services/graph_service.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/graph_service.py)** — Implements `MemgraphIngestor` with `batch_size` parameter and `_execute_query()` flush logic ([source](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/graph_service.py))

- **[`codebase_rag/tests/test_memgraph_batching.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tests/test_memgraph_batching.py)** — Contains performance tests validating batch size impact across multiple configurations ([source](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tests/test_memgraph_batching.py))

### Critical Method Signatures

```python
class MemgraphIngestor:
    def __init__(
        self,
        host: str,
        port: int,
        batch_size: int = 100  # Default sweet-spot value

    ) -> None: ...

    def add_node(self, properties: dict) -> None: ...
    def add_edge(self, start_id: str, end_id: str, rel_type: str) -> None: ...
    def flush_nodes(self) -> None:      # Internal batch flush

    def flush_all(self) -> None:        # Force complete sync

    def _execute_query(self, query: str) -> None:  # Direct DB execute

```

The `flush_nodes()` method constructs batched Cypher by concatenating buffered `MERGE` statements, then delegates to `_execute_query()` for atomic execution.

---

## Best Practices for Production

1. **Start with 150** and adjust based on observed memory and latency
2. **Enable appropriate indexes** on properties used in `MERGE` clauses—batching amplifies the benefit of indexed lookups
3. **Monitor client heap** during `flush_all()` operations; if memory pressure occurs, reduce `batch_size`
4. **Use explicit `flush_all()`** at workflow boundaries to prevent data loss from partial batches
5. **Validate with representative data**—AST node counts vary significantly across programming languages and codebase sizes

---

## Summary

- The **Memgraph batch size** parameter in `MemgraphIngestor` controls transaction granularity and directly impacts ingestion throughput
- **100–250 nodes per batch** provides optimal performance for most hardware configurations according to the test suite
- **Very small batches** (< 10) waste network resources; **very large batches** (> 500) risk memory exhaustion and parsing overhead
- Configuration occurs at `MemgraphIngestor` initialization via the `batch_size` argument
- Empirical benchmarking with [`test_memgraph_batching.py`](https://github.com/vitali87/code-graph-rag/blob/main/test_memgraph_batching.py) patterns yields workload-specific optimal values

---

## Frequently Asked Questions

### What is the default batch size in MemgraphIngestor?

The default `batch_size` is **100**, defined in the `MemgraphIngestor.__init__()` method. This value represents a conservative starting point that performs adequately across diverse repository sizes without excessive memory consumption.

### How does batch size affect memory usage in Code-Graph-RAG?

Larger batch sizes increase client-side memory because node and relationship dictionaries accumulate in the internal buffer before flush. The generated Cypher string also grows proportionally. If you observe `MemoryError` exceptions or elevated garbage collection pauses, reduce `batch_size` to 50–100.

### Can I change batch size dynamically during ingestion?

No—`batch_size` is fixed at `MemgraphIngestor` initialization. To use different batch sizes for different ingestion phases, instantiate separate `MemgraphIngestor` instances with appropriate configurations, or implement custom buffering logic wrapping multiple ingestors.

### Does batch size impact query correctness or data integrity?

No. Batching is purely a performance optimization; the eventual Cypher `MERGE` operations produce identical graph state regardless of batch size. However, larger batches do increase the scope of potential rollback if a transaction fails mid-batch, so appropriate error handling around `flush_all()` remains important.