# How to Optimize RAGAnything Performance with Concurrent File Processing and max_concurrent_files

> Boost RAGAnything performance by tuning max_concurrent_files for simultaneous document parsing. Optimize throughput while managing system resources.

- Repository: [✨Data Intelligence Lab@HKU✨/RAG-Anything](https://github.com/HKUDS/RAG-Anything)
- Tags: performance
- Published: 2026-04-22

---

**Set the `max_concurrent_files` configuration in HKUDS/RAG-Anything to control how many documents parse simultaneously, balancing throughput against memory and CPU limits.**

The HKUDS/RAG-Anything library ingests documents through a batch processing pipeline that can become a bottleneck when handling large collections. To optimize RAGAnything performance with concurrent file processing, you must tune the `max_concurrent_files` parameter, which governs the semaphore-based parallelism in the ingestion engine.

## Where Concurrency Configuration Lives

RAG-Anything centralizes its concurrency settings in the configuration dataclass and propagates them through the batch processing mixin.

### The Configuration Dataclass

In [`raganything/config.py`](https://github.com/HKUDS/RAG-Anything/blob/main/raganything/config.py), the `RAGAnythingConfig` dataclass defines the **concurrency limit**:

```python
@dataclass
class RAGAnythingConfig:
    max_concurrent_files: int = 1  # Reads MAX_CONCURRENT_FILES env var

```

The default value is `1`, meaning files process sequentially unless you explicitly raise the limit. The constructor reads the `MAX_CONCURRENT_FILES` environment variable when present, allowing infrastructure-level tuning without code changes.

### Startup Verification

During initialization in [`raganything/raganything.py`](https://github.com/HKUDS/RAG-Anything/blob/main/raganything/raganything.py), the library logs the effective concurrency setting:

```python
self.logger.info(f"  Max concurrent files: {self.config.max_concurrent_files}")

```

This verification ensures you can confirm the actual limit before batch processing begins.

## How Concurrent Processing Works

The library enforces concurrency limits using Python’s `asyncio` primitives to prevent resource exhaustion while maximizing throughput.

### Semaphore-Based Execution Control

In [`raganything/batch.py`](https://github.com/HKUDS/RAG-Anything/blob/main/raganything/batch.py), the `process_folder_complete` method creates an `asyncio.Semaphore` using the configured limit:

```python

# Inside process_folder_complete

semaphore = asyncio.Semaphore(max_workers)  # max_workers defaults to config.max_concurrent_files

async def process_single_file(file_path: Path):
    async with semaphore:  # Limits simultaneous executions

        await self.process_document_complete(...)

```

This semaphore ensures that at most `max_workers` coroutines run `process_document_complete` concurrently. The `BatchMixin` class propagates this limit through `process_documents_batch` and `process_documents_batch_async`.

### The Document Processing Pipeline

Each concurrent slot handles the complete **document ingestion pipeline**:

1. **Load** the file from disk.
2. **Parse** using the configured backend (MinerU, Docling, PaddleOCR, etc.).
3. **Chunk** and insert into the LightRAG storage backend.

Because these steps mix I/O-bound operations (disk reads, OCR API calls) with CPU-bound work (image processing, table extraction), moderate parallelism improves hardware utilization without overloading the system.

## Tuning max_concurrent_files for Your Hardware

Match the concurrency limit to your hardware capabilities to avoid memory errors or CPU thrashing:

- **Small machines** (single-core CPU, <4 GB RAM): Use **1-2** to keep memory usage minimal.
- **Modern desktops** (4-8 cores, ≥16 GB RAM): Use **4-8** to match physical core counts for CPU-heavy parsing.
- **GPU-accelerated servers**: Use **12-16** to saturate both CPU preprocessing and GPU OCR pipelines.
- **Cloud functions** with strict memory caps: Keep the default **1** or set via environment variable to prevent OOM kills.

Exceeding OS file-descriptor limits (typically several thousand) raises `OSError`, so keep `max_concurrent_files` well below platform maximums.

## Three Methods to Configure Concurrency

RAG-Anything offers three ways to adjust the limit, ordered from global to specific.

### Environment Variable Configuration

Set the limit before process startup to affect all instances:

```bash
export MAX_CONCURRENT_FILES=8
python -m examples.raganything_example

```

This approach works best for containerized deployments where you want infrastructure teams to control resources without touching application code.

### Runtime Configuration Updates

Modify the setting after initialization using the `update_config` method:

```python
from raganything import RAGAnything

rag = RAGAnything(llm_model_func=my_llm, embedding_func=my_embedder)

# Increase concurrency for subsequent operations

rag.update_config(max_concurrent_files=8)

```

Changes take effect immediately for future batch calls but do not interrupt running operations.

### Per-Call Parameter Overrides

Override the configuration for a specific batch operation using the `max_workers` argument:

```python
await rag.process_documents_batch(
    file_paths=["/data/reports"],
    max_workers=12,          # Overrides rag.config.max_concurrent_files for this call only

    show_progress=True,
)

```

This pattern is useful when processing different document types that require varying resource levels, or when temporarily scaling up for large one-time ingestion jobs.

## Complete Implementation Examples

### End-to-End Batch Processing Setup

```python
from raganything import RAGAnything

# Initialize with model functions

rag = RAGAnything(
    llm_model_func=my_llm,
    embedding_func=my_embedder,
    vision_model_func=my_vision,  # Optional for multimodal content

)

# Configure concurrency for this session

rag.update_config(max_concurrent_files=8)

# Process entire folder with parallel execution

await rag.process_folder_complete(
    folder_path="my_documents",
    output_dir="parsed_output",
    recursive=True,
    display_stats=True,
)

```

### Inspect Current Settings

Verify the effective configuration before running expensive operations:

```python
print("Effective concurrency:", rag.config.max_concurrent_files)

# Output: 8

```

## Performance Characteristics and Resource Impact

Understanding the trade-offs helps you optimize RAGAnything performance without destabilizing your system:

- **Throughput** increases roughly linearly with concurrency until you saturate CPU, GPU, or disk I/O.
- **Latency per file** may rise slightly for individual workers due to context switching, but total batch completion time decreases significantly.
- **Memory consumption** grows proportionally with parallel parsers because each holds file buffers, OCR model states, and temporary tensors.
- **Stability** requires keeping the limit below OS thresholds for open file handles and thread counts.

The design deliberately isolates the concurrency knob in `RAGAnythingConfig` and propagates it through the `BatchMixin`, making it straightforward to experiment with different values reproducibly.

## Summary

- **Configuration location**: [`raganything/config.py`](https://github.com/HKUDS/RAG-Anything/blob/main/raganything/config.py) defines `max_concurrent_files`, defaulting to `1` unless overridden by the `MAX_CONCURRENT_FILES` environment variable.
- **Enforcement mechanism**: [`raganything/batch.py`](https://github.com/HKUDS/RAG-Anything/blob/main/raganything/batch.py) uses an `asyncio.Semaphore(max_workers)` to limit concurrent `process_document_complete` calls.
- **Tuning guidance**: Use 1-2 for small machines, 4-8 for desktops, and 12-16 for GPU servers.
- **Configuration methods**: Environment variables (global), `update_config()` (runtime), or `max_workers` argument (per-call).
- **Resource trade-off**: Higher concurrency improves throughput linearly until hardware saturation but increases memory usage linearly with each parallel parser.

## Frequently Asked Questions

### What is the default value of max_concurrent_files in RAG-Anything?

The default value is **1**, meaning files process sequentially. This conservative default prevents memory issues on minimal hardware and cloud functions. You can verify the current value in the startup logs or by inspecting `rag.config.max_concurrent_files` after initialization.

### How does max_concurrent_files differ from max_workers?

The `max_concurrent_files` configuration property sets the global default for the RAGAnything instance. The `max_workers` parameter in batch methods like `process_documents_batch` temporarily overrides this global setting for that specific call. If you omit `max_workers`, the method falls back to `self.config.max_concurrent_files`.

### Can I change the concurrency limit after initializing RAGAnything?

Yes. Call `rag.update_config(max_concurrent_files=8)` to modify the limit at runtime. This change affects all subsequent batch operations but does not alter currently running tasks. For immediate, one-time overrides, use the `max_workers` argument in the specific batch method instead.

### Why does increasing max_concurrent_files cause out-of-memory errors?

Each concurrent file processing slot allocates memory for file buffers, parser models (especially OCR engines like PaddleOCR or Docling), and intermediate tensors. Setting `max_concurrent_files` too high for your RAM capacity causes the system to exhaust available memory. Reduce the value to 1-2 for machines with limited memory, or monitor memory usage during ingestion to find your hardware’s saturation point.