How to Optimize RAGAnything Performance with Concurrent File Processing and max_concurrent_files

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, the RAGAnythingConfig dataclass defines the concurrency limit:

@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, the library logs the effective concurrency setting:

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, the process_folder_complete method creates an asyncio.Semaphore using the configured limit:


# 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:

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:

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:

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

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:

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 defines max_concurrent_files, defaulting to 1 unless overridden by the MAX_CONCURRENT_FILES environment variable.
  • Enforcement mechanism: 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.

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 →