Airflow 운영 심화: scheduler, executor, metadata DB, pool, queue
Airflow가 task를 실행하는 실제 경로
Airflow에서 "DAG를 등록하면 자동으로 실행된다"는 경험은 익숙하지만, 그 사이에 어떤 일이 벌어지는지 모르면 왜 task가 scheduled 상태에서 수 분간 머무는지, 왜 pool이 가득 찬 것처럼 보이는데 실제로는 아무 task도 실행 중이 아닌지 이해하기 어렵다. 이 편은 scheduler가 task를 어떻게 발견하고 executor에게 넘기는지, metadata DB와 pool이 그 과정에서 어떤 역할을 하는지, 그리고 운영자가 손댈 수 있는 파라미터가 무엇인지를 처음부터 끝까지 짚는다.
DAG 디렉터리 감시·분배
개별 .py 파일 파싱
(PostgreSQL)
루프: DagRun 생성·TI 전환
pool·concurrency 체크 후 queued
LocalExecutor / CeleryExecutor / K8s
실제 task 프로세스 실행
1. Scheduler 내부 루프
1.1 두 가지 프로세스: 파싱과 스케줄링
Airflow scheduler는 실제로 두 가지 별개의 일을 한다.
DAG 파싱: DagFileProcessorManager가 DAG 디렉터리를 감시하며 파일 목록을 DagFileProcessor 프로세스 풀에 분배한다. 각 프로세스는 Python 파일을 임포트해서 DAG 객체를 만들고 직렬화(SerializedDag)해서 metadata DB에 저장한다. 파싱 주기는 dag_dir_list_interval(기본 300초)와 min_file_process_interval이 제어한다.
Airflow 2.3+부터는 AIRFLOW__SCHEDULER__STANDALONE_DAG_PROCESSOR=True로 파싱 프로세스를 별도 호스트에서 실행할 수 있다. DAG 수가 많을 때 파싱 CPU 부하를 스케줄러 노드에서 분리하는 방법이다.
스케줄링 루프: 파싱과 독립적으로 실행되는 루프로, scheduler_heartbeat_sec(기본 5초) 주기로 다음 작업을 수행한다.
1. 새 DagRun을 생성해야 하는 DAG 확인 (schedule 도래)
2. 실행 가능한 TaskInstance 상태 전환 (none → scheduled)
3. Critical Section: pool·concurrency 한도 확인 후 scheduled → queued
4. Executor에 queued TI 목록 전달
5. Executor 콜백 처리: running/failed/success 상태 반영1.2 Critical Section: pool과 concurrency의 교차점
스케줄링 루프에서 가장 중요한 단계가 3번이다. Airflow 공식 문서는 이 단계를 "Critical Section"이라고 부른다. 이 시점에 scheduler는 slot_pool 테이블 전체에 row-level write lock을 걸고, 다음 기준을 모두 충족하는 TI만 queued 상태로 전환한다.
- pool의 남은 슬롯 수 ≥ task의
pool_slots - DAG 전체
max_active_tasks한도 미초과 - 전역
parallelism한도 미초과
이 lock 덕분에 여러 scheduler가 동시에 실행되더라도(HA 모드) 슬롯을 중복으로 소비하지 않는다. 반대로 말하면, pool 슬롯이 가득 찬 상태에서는 이 단계에서 새 task가 queued로 넘어가지 않는다. UI에서 task가 scheduled 상태에 오래 머무는 원인의 상당수는 이 단계에서의 블로킹이다.
1.3 HA 스케줄러: 다중 인스턴스 운영
Airflow 2.0+에서 여러 scheduler를 동시에 실행할 수 있다. PostgreSQL 12+ 또는 MySQL 8.0+ 환경에서는 추가 설정 없이 scheduler 프로세스를 여러 개 띄우기만 하면 된다.
HA 스케줄러는 Critical Section의 row-level locking으로 중복 실행을 막는다. DAG 파싱은 각 scheduler가 독립적으로 수행하므로, scheduler 수가 늘어나면 파싱 처리량도 함께 늘어난다. 스케줄링 루프 자체는 분산 처리되지만 Critical Section은 직렬화된다.
실무에서 HA 스케줄러를 2~3개 운영하면 파싱 속도가 개선되고 scheduler 장애 시 자동 페일오버가 이루어진다. 다만 scheduler 수를 과도하게 늘리면 Critical Section 경합이 늘어 오히려 성능이 저하될 수 있다.
2. Executor: task를 실제로 실행하는 방법
Executor는 scheduler가 "이 task를 실행하라"고 넘긴 TI를 실제로 프로세스/컨테이너/워커에서 실행하는 컴포넌트다. Executor 종류 선택은 Airflow 배포 아키텍처의 핵심 결정이다.
subprocess로 task 실행
parallelism으로 병렬 제어
→ 분산 worker 풀
queue 라우팅 지원
완전한 리소스 격리
K8s 환경에서 권장
2.1 LocalExecutor
scheduler 프로세스와 같은 호스트에서 subprocess를 생성해 task를 실행한다. parallelism 설정으로 동시 실행 subprocess 수를 제한한다. 단일 노드이므로 노드 장애 시 모든 task가 멈추는 단점이 있지만, 운영 복잡도가 가장 낮다. 소규모 파이프라인(동시 task < 50)이나 개발 환경에 적합하다.
2.2 CeleryExecutor
분산 task queue 라이브러리인 Celery를 통해 task를 여러 worker 노드에 분배한다. 구성 요소는 세 가지다.
- Broker (Redis 또는 RabbitMQ): scheduler가 queued TI를 메시지로 전송하는 큐
- Result backend (Redis 또는 DB): worker가 task 결과 상태를 저장하는 저장소
- Celery worker: broker에서 메시지를 꺼내 task를 실행하는 프로세스
Queue 라우팅이 CeleryExecutor의 핵심 기능이다. task에 queue="heavy_queue"를 지정하면, 해당 worker에서만 그 task가 실행된다. 예를 들어 Spark job은 spark_worker 큐를, 경량 Python 작업은 default 큐를 쓰도록 분리할 수 있다. Worker는 airflow celery worker -q spark_worker,default처럼 여러 큐를 구독할 수 있다.
# worker 시작 예: spark_queue만 처리하는 전용 worker
airflow celery worker -q spark_queue --concurrency 4
# default 큐와 etl 큐 모두 처리하는 worker
airflow celery worker -q default,etl --concurrency 16worker_concurrency 설정은 worker 하나가 동시에 실행할 수 있는 task 수다. CPU와 메모리를 고려해서 지정한다. 메모리 집약적 task라면 낮게, IO 대기 중심이라면 높게 설정한다.
2.3 KubernetesExecutor
task 하나당 K8s Pod를 생성해서 실행한다. 완전한 환경 격리가 장점이지만 Pod 생성·종료 오버헤드(보통 수~수십 초)가 있으므로 수초 내에 완료되는 경량 task에는 비효율적이다.
task별로 리소스 요청(CPU, 메모리), 환경변수, 이미지를 다르게 지정할 수 있다. executor_config 파라미터로 task 단위 Pod 설정을 오버라이드한다.
Airflow 2.7+에서는 LocalKubernetesExecutor가 추가되어, 경량 task는 LocalExecutor로, 무거운 task는 KubernetesExecutor로 라우팅할 수 있다.
3. Metadata DB: Airflow의 상태 저장소
Metadata DB는 Airflow의 단일 상태 저장소다. Scheduler, Executor, Webserver 모두 이 DB를 통해 상태를 읽고 쓴다.
3.1 핵심 테이블
| 테이블 | 용도 |
|---|---|
dag | DAG 메타정보, 활성화 상태, max_active_runs, is_paused |
serialized_dag | 파싱된 DAG의 직렬화 형태 — webserver가 코드 파싱 없이 구조 표시 |
dag_run | 개별 DagRun 레코드: run_id, logical_date, data_interval_start/end, state |
task_instance | 개별 TI: 상태, 시작/종료 시각, 재시도 횟수, pool, queue, priority_weight |
job | scheduler·worker job 상태 — HA scheduler의 생존 확인에 사용 |
slot_pool | pool 이름, 전체 슬롯 수 (Critical Section에서 row lock) |
log | task 실행 로그 메타정보 (실제 로그는 파일 또는 원격 저장소) |
3.2 DB 선택과 연결 풀
Airflow 공식 문서는 PostgreSQL을 운영 환경의 표준으로 권장한다. SQLite는 단일 프로세스 제한으로 운영에 부적합하다. MySQL은 지원되지만 PostgreSQL이 더 넓게 검증됐다.
PGBouncer 사용은 PostgreSQL 환경에서 강력히 권장된다. Airflow의 scheduler·worker는 DB 연결을 많이 열고 짧게 사용하는 패턴이다. PostgreSQL은 연결마다 프로세스를 생성하므로, 연결이 많으면 PostgreSQL 서버 메모리와 CPU가 급증한다. PGBouncer의 transaction 모드 pooling이 이 문제를 해결한다.
권장 연결 설정 예:
[scheduler]
sql_alchemy_pool_size = 5 # SQLAlchemy 연결 풀 크기
sql_alchemy_max_overflow = 10 # 초과 허용 연결 수
sql_alchemy_pool_recycle = 1800 # 유휴 연결 재활용 간격(초)
PGBouncer:
pool_mode = transaction # transaction 완료 후 연결 반환
max_client_conn = 500
default_pool_size = 203.3 Metadata DB 유지 관리
Metadata DB는 시간이 지나면서 오래된 DagRun, TaskInstance, Log 레코드가 쌓여 성능이 저하된다. Airflow는 airflow db clean 명령을 제공한다.
# 30일 이전 run 기록 정리
airflow db clean --clean-before-timestamp "$(date -d '30 days ago' +%Y-%m-%dT%H:%M:%S)"
# PostgreSQL: 주기적으로 VACUUM ANALYZE 실행 필요
VACUUM ANALYZE task_instance;
VACUUM ANALYZE dag_run;운영 규모에 따라 다르지만 일반적으로 30~90일치 이력만 DB에 유지하고, 더 오래된 감사 로그는 별도 cold storage에 보관하는 것이 좋다.
4. Pool: 리소스 격리의 기본 도구
Pool은 "동시에 실행할 수 있는 task 수를 자원 단위로 제한"하는 메커니즘이다. DB 연결, API 호출 TPS, 메모리 집약적 작업 등을 보호하는 용도로 쓴다.
4.1 default_pool
모든 task는 명시적으로 pool을 지정하지 않으면 default_pool에 배정된다. 초기 슬롯 수는 128이고, UI(Admin > Pools)나 CLI에서 수정할 수 있다.
# default_pool 슬롯 확인
airflow pools list
# 새 pool 생성 (외부 API 동시 호출 10개 제한)
airflow pools set api_pool 10 "external API rate limit"4.2 Pool 설계 패턴
task에 pool_slots 파라미터를 사용하면 슬롯 가중치를 조정할 수 있다. 예를 들어 Spark job이 일반 task보다 리소스를 4배 쓴다면 pool_slots=4로 지정해서, 5슬롯 pool에서 최대 1개만 동시에 실행되도록 제어한다.
spark_task = SparkSubmitOperator(
task_id="run_spark_job",
pool="spark_pool",
pool_slots=4, # spark_pool 슬롯 4개 점유
...
)5. 핵심 파라미터 한눈에 보기
Airflow 운영에서 가장 자주 조정하는 파라미터를 계층별로 정리한다.
| 파라미터 | 기본값 | 의미 | 튜닝 방향 |
|---|---|---|---|
parallelism | 32 | 전역 동시 task 수 | worker 총 CPU/메모리에 맞게 확장 |
max_active_tasks_per_dag | 16 | DAG당 최대 동시 task | 특정 DAG의 DB/API 과부하 방지 |
max_active_runs_per_dag | 16 | DAG당 최대 동시 DagRun | catchup·backfill 시 급증 방지 |
scheduler_heartbeat_sec | 5 | 스케줄러 루프 주기 | 줄이면 응답성↑·CPU↑ |
dag_dir_list_interval | 300 | DAG 디렉터리 스캔 주기 | DAG 수 < 200이면 30으로 줄여도 무방 |
parsing_processes | 2 | DAG 파싱 병렬 프로세스 수 | vCPU × 2 권장 |
worker_concurrency (Celery) | 16 | worker당 동시 task 수 | task 성격(CPU/IO)에 맞게 |
6. Task 상태 전환 전체 흐름
운영 시 task가 특정 상태에서 막히는 상황을 자주 마주친다. 상태 전환 경로를 알면 원인을 빠르게 좁힐 수 있다.
scheduled에서 수 분간 머무는 경우: pool 슬롯 소진,parallelism한도 초과, 또는 scheduler 루프 지연이 원인.queued에서 머무는 경우: Celery worker가 메시지를 처리하지 못하는 것이 원인. worker 수나worker_concurrency확인.running상태가 너무 오래 지속되는 경우: task 자체의 지연 또는 zombie task(worker 프로세스 사망 후 상태 미갱신).airflow tasks clear로 초기화한다.
마무리: 운영 지식이 설계 결정으로 이어진다
Scheduler 루프, executor 선택, pool 설계는 각각 독립된 주제처럼 보이지만 실제로는 하나의 흐름이다. "이 DAG에서 task가 왜 느리게 시작하는가"라는 질문은 pool 슬롯 부족인지, parallelism 한도인지, Celery worker queue 지연인지, metadata DB 응답 속도인지 순서대로 확인해야 한다. 다음 편에서는 이 흐름 위에서 backfill이 어떻게 작동하고, 어떻게 안전하게 재처리할 수 있는지를 다룬다.
References
- Apache Airflow Documentation, "Scheduler": https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/scheduler.html
- Apache Airflow Documentation, "Executor": https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/executor/index.html
- Apache Airflow Documentation, "Pools": https://airflow.apache.org/docs/apache-airflow/stable/administration-and-deployment/pools.html
- Apache Airflow Documentation, "Set up a Database Backend": https://airflow.apache.org/docs/apache-airflow/stable/howto/set-up-database.html
- Astronomer Documentation, "Airflow Executors Explained": https://www.astronomer.io/docs/learn/airflow-executors-explained
- Astronomer Documentation, "Scaling Airflow to Optimize Performance": https://www.astronomer.io/docs/learn/airflow-scaling-workers
- Astronomer Documentation, "Understanding the Airflow metadata database": https://www.astronomer.io/docs/learn/airflow-database
- AWS MWAA Documentation, "Performance tuning for Apache Airflow": https://docs.aws.amazon.com/mwaa/latest/userguide/best-practices-tuning.html
- Apache Airflow Wiki, "AIP-15 Support Multiple-Schedulers for HA": https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=103092651
- DeepWiki, "Airflow Executor System": https://deepwiki.com/apache/airflow/13-executor-system