Intro
Apache Airflow is a powerful orchestrator, but its performance depends heavily on how you size its underlying infrastructure. Many teams start with a small instance and later hit walls when DAG complexity, task volume, or concurrency grows. Capacity planning is not a one-time event; it is an ongoing practice that balances cost, performance, and reliability.
This guide walks through a practical approach to capacity planning for Airflow. You will learn how to inventory your environment, estimate resource needs, choose safe configuration options, verify performance, handle failure modes, and maintain an operations checklist. Each major step includes concrete commands, configuration snippets, and expected outputs so you can apply the guidance directly. By the end, you will have a repeatable method to keep your Airflow deployment healthy as it scales.
Version and Environment Inventory
Before adjusting any capacity parameter, document your current setup. Knowing your Airflow version, executor type, database, broker, and node topology is essential. This inventory helps you identify bottlenecks and plan changes safely.
Airflow Version and Executor
Run the following command on your Airflow host to check the version:
airflow version
Expected output example:
2.8.1
Next, determine which executor is configured. Look in airflow.cfg or check the environment:
airflow config get-value core executor
Typical values include SequentialExecutor (development only), LocalExecutor, CeleryExecutor, or KubernetesExecutor. For production workloads beyond a few tasks, CeleryExecutor or KubernetesExecutor is common. The executor choice affects how you scale workers and how concurrency is managed.
Topology and Components
A typical Airflow deployment includes:
- Webserver: Serves the UI and API.
- Scheduler: Parses DAGs, schedules tasks, and queues them.
- Worker(s): Execute tasks (for distributed executors).
- Metadata database: Stores state (PostgreSQL or MySQL recommended).
- Message broker: Redis or RabbitMQ for Celery.
Document the number of nodes, their CPU, memory, and disk. Also note network latency between components, especially between scheduler and database, as that can affect scheduler performance.
Prerequisites for Capacity Planning
You need monitoring data to make informed decisions. Ensure you collect:
- System metrics: CPU, memory, disk I/O, network for each Airflow component.
- Airflow metrics: Task duration, queued time, scheduler loop time, DAG parsing time, number of running tasks.
- Database metrics: Connections, query latency, locks, and size.
Airflow exposes metrics to StatsD or Prometheus. If you do not have monitoring, set it up early; capacity planning without data is guesswork.
Example Inventory Table
Create a simple table like this to record your baseline.
| Component | Host | CPU Cores | Memory (GB) | Disk (GB) | Notes |
|---|---|---|---|---|---|
| Webserver | air-web-1 | 2 | 4 | 50 | Behind load balancer |
| Scheduler | air-sched-1 | 4 | 8 | 100 | Single scheduler |
| Worker | air-worker-1 | 4 | 16 | 200 | Celery worker |
| Database | db-1 | 4 | 16 | 500 | PostgreSQL 14 |
| Broker | broker-1 | 2 | 8 | 50 | Redis 7 |
This baseline helps you track changes over time.
Safe Configuration Path
Capacity-related parameters in Airflow control concurrency, parallelism, and resource usage. Adjust them incrementally and test after each change. Avoid large jumps; small, measured steps reduce risk.
Key Configuration Parameters
Open airflow.cfg (or use environment variables) and review these core settings:
parallelism: Maximum number of task instances that can run concurrently across the entire Airflow cluster. Default is 32.dag_concurrency: Maximum number of task instances allowed to run concurrently for a single DAG. Default is 16.max_active_runs_per_dag: Maximum number of active DAG runs per DAG. Default is 16.worker_concurrency(Celery): Number of tasks each Celery worker can execute simultaneously. Default is 16.scheduler_parsing_processes: Number of processes the scheduler uses to parse DAG files. Default is 2.parsing_processes(Airflow 2.3+): Similar to above.dag_dir_list_interval: How often (in seconds) the scheduler scans the DAG directory for new files.
Starting Point for Sizing
For a modest setup with a few dozen DAGs and moderate task durations, starting values often look like:
[core]
parallelism = 32
dag_concurrency = 16
max_active_runs_per_dag = 16
[celery]
worker_concurrency = 8
But these values depend on your hardware and workload. A common approach is to set worker_concurrency based on available CPU and memory per worker. For example, if each task uses about 0.5 CPU core and 1 GB RAM, and your worker has 4 CPU cores and 16 GB RAM, then a safe worker_concurrency would be min(4/0.5, 16/1) = 8 tasks.
Changing Configuration Safely
- Back up your current
airflow.cfg. - Change one parameter at a time.
- Restart the affected component (scheduler, worker, etc.).
- Monitor for errors and performance changes.
- Document the change and its effect.
For example, to increase global parallelism from 32 to 48, edit airflow.cfg:
[core]
parallelism = 48
Then restart the scheduler:
sudo systemctl restart airflow-scheduler
Verify the new value is live:
airflow config get-value core parallelism
Output:
48
Database and Broker Capacity
The metadata database is often the hidden bottleneck. As DAG count and task volume grow, the number of database queries increases. Monitor connection usage and query latency. If you see high CPU or slow queries, consider:
- Increasing database connection pool size (
sql_alchemy_pool_sizein[database]section). Default is 5, but may need to be higher. - Adding indexes (Airflow manages its own schema; avoid manual changes unless necessary).
- Scaling up database resources or moving to a managed service.
For Celery, the broker (Redis/RabbitMQ) must handle message traffic. Monitor broker memory and connection count. If using Redis, set maxmemory and appropriate eviction policy. Ensure broker is not a single point of failure for critical pipelines.
Example: Adjusting Scheduler Parsing Processes
If you have many DAG files and parsing is slow, increase scheduler_parsing_processes. Suppose you currently have 2 parsing processes and DAG parsing takes 60 seconds, but your scheduling loop can tolerate 30 seconds. You can increase to 4 processes:
[scheduler]
parsing_processes = 4
Restart scheduler and observe parsing time:
airflow dags report
Output includes parsing times. You should see reduced parsing duration if CPU is available.
Verification and Diagnostics
After configuration changes, verify that the system behaves as expected. Use observable checks and metrics.
Check Scheduler Health
Run airflow dags list to ensure DAGs are recognized and not broken.
airflow dags list
Look for DAGs with errors. A healthy output shows each DAG with enabled status and no import errors.
Monitor Running Tasks and Queues
Use the UI or CLI to see task states. For CLI:
airflow tasks list <dag_id>
To check the number of queued tasks:
airflow pools list
If tasks are stuck in queued state, it may indicate insufficient worker capacity or pool limits.
Database Connection Check
Airflow provides a command to check database connectivity:
airflow db check
Expected output:
Connection: OK
If not OK, investigate database load and credentials.
Metrics to Watch
Key metrics to verify capacity decisions:
- Task queue time: Time from task scheduling to start. High queue time means insufficient worker slots or high concurrency limits.
- Scheduler loop duration: If consistently above 60 seconds, the scheduler may be overloaded.
- DAG parsing time: Affects scheduler responsiveness.
- Database query time: Slow queries can delay state updates.
- Worker CPU and memory utilization: If workers are pegged, reduce concurrency or scale out.
Example Verification Scenario
Suppose you increased parallelism to 48. After restarting, run a load test with a DAG that launches many tasks. Use airflow tasks test for a single task, or trigger a DAG run:
airflow dags trigger example_dag -r test_run
Then monitor with airflow dags list-runs -d example_dag and check that tasks transition from queued to running to success. In the UI, observe the "Gantt" chart to see task overlap and scheduling gaps.
If you see many tasks queued but few running, you may have hit worker_concurrency limits. Check worker logs for "No free slots" messages.
Common Pitfalls and How to Avoid Them
Many teams encounter similar issues when scaling Airflow. Here are the most frequent pitfalls, why they happen, and how to avoid or recover from them.
1. Overloading the Metadata Database
Why it happens: Increasing parallelism or scheduler parsing processes without considering database capacity leads to a flood of connections and queries. The database becomes a bottleneck, causing scheduler instability and UI slowdowns.
How to avoid: Monitor database CPU, connections, and query latency. Increase sql_alchemy_pool_size cautiously (note the maximum connections your database supports). Use connection pooling and consider read replicas for the webserver if needed.
Recovery: Reduce concurrency parameters, increase database resources, or temporarily disable heavy DAGs. Use airflow db check to verify connectivity.
2. Ignoring Worker Resource Limits
Why it happens: Setting worker_concurrency too high based on CPU alone while ignoring memory leads to out-of-memory kills or severe swapping. Tasks then fail unexpectedly.
How to avoid: Calculate concurrency based on both CPU and memory per task. Use resource-aware task definitions (e.g., resources={'cpu': 0.5, 'memory': '1GB'}) and monitor actual usage. Consider using KubernetesExecutor for fine-grained resource allocation.
Recovery: Lower worker_concurrency, add more workers, or optimize task code to use fewer resources.
3. Underestimating Scheduler Load
Why it happens: As DAG count grows, parsing and scheduling consume more CPU and memory. A single scheduler may become a bottleneck, causing delayed task execution.
How to avoid: Monitor scheduler loop duration and parsing time. Increase parsing_processes if CPU is available, or run multiple schedulers (Airflow 2.3+ supports this) with a shared database.
Recovery: Scale up scheduler resources, reduce DAG parsing complexity (e.g., use DAG serialization), or distribute DAGs across schedulers.
4. Neglecting Broker Capacity
Why it happens: Celery uses a broker (Redis/RabbitMQ) to store messages. High task volume can exhaust broker memory, causing message loss or worker disconnects.
How to avoid: Monitor broker memory and connections. Set appropriate maxmemory for Redis and use result_backend for large payloads. Ensure the broker is clustered for high availability.
Recovery: Scale broker memory, reduce message size, or purge old messages (with caution). Restart workers if connections are dropped.
5. No Monitoring or Baseline
Why it happens: Teams skip monitoring and make configuration changes blindly, leading to unpredictable behavior.
How to avoid: Set up StatsD/Prometheus metrics from the start. Establish baseline metrics for your workload. Use dashboards to visualize key metrics.
Recovery: Implement monitoring immediately. Use historical data from logs to reconstruct baseline if possible.
Operations Checklist
Use this checklist in regular operations reviews and before making changes.
Daily Checks
- [ ] All Airflow components running (webserver, scheduler, workers).
- [ ] No critical errors in logs.
- [ ] Task queue depth within expected range.
- [ ] Database connection count and latency normal.
Weekly Checks
- [ ] Review slow DAGs and tasks; identify bottlenecks.
- [ ] Check DAG parsing times; look for inefficient DAG files.
- [ ] Monitor disk space on workers and database.
- [ ] Purge old logs if necessary (
airflow db clean).
Monthly Capacity Review
- [ ] Collect metrics: average and peak CPU, memory, tasks per day.
- [ ] Compare to previous month; identify trends.
- [ ] Estimate growth: new DAGs, increased data volume.
- [ ] Plan scaling: adjust concurrency, add workers, upgrade hardware.
- [ ] Review configuration parameters against actual usage.
Change Management Checklist
Before changing capacity-related settings:
- [ ] Document current baseline metrics.
- [ ] Identify the specific parameter to change and rationale.
- [ ] Set a clear expected outcome (e.g., reduce queue time by 20%).
- [ ] Plan rollback steps.
- [ ] Make change in a controlled window if possible.
- [ ] After change, monitor for at least 24 hours.
- [ ] Record results and update runbook.
Scaling Decision Framework
When metrics indicate a bottleneck, consider these options:
| Bottleneck | Possible Actions | Risk |
|---|---|---|
| Database CPU high | Increase database resources; optimize queries; reduce scheduler polling interval | Cost increase; potential downtime during migration |
| Worker CPU saturated | Add more workers; reduce worker_concurrency; optimize task code | Requires infrastructure changes |
| Scheduler loop slow | Increase parsing processes; reduce DAG parsing time; use DAG serialization | Higher scheduler CPU usage |
| Broker memory high | Scale broker; reduce message size; use result backend for large payloads | Potential message loss |
Always test changes in a staging environment if possible.
Conclusion
Capacity planning for Apache Airflow is a continuous process that requires understanding your workload, monitoring key metrics, and making incremental adjustments. Start with a thorough inventory of versions, topology, and current configuration. Then, modify capacity parameters in small, measurable steps while monitoring the impact. Verify changes with concrete diagnostic commands and metrics, and prepare for failure modes with rollback plans. Adopt a regular operations checklist to keep your deployment aligned with demand.
By following the practical examples in this guide, you can avoid common pitfalls such as database overload and worker contention, ensuring that your Airflow cluster scales smoothly as your data pipelines grow. Remember, the goal is not to over-provision but to maintain a balanced system that delivers reliable orchestration cost-effectively.