Apache Airflow를 단순히 DAG 작성 도구로만 접근하면 워크플로우 규모가 커질 때 스케줄러 지연(Scheduler lag), 메타스토어 병목, 좀비 태스크(Zombie task) 등 다양한 인프라 레벨의 장애에 직면하게 됩니다.
엔터프라이즈 환경에서 대용량 데이터 파이프라인과 RDBMS 마이그레이션 오케스트레이션을 안정적으로 운영하기 위해서는 Airflow를 구성하는 핵심 컴포넌트의 역할과 상호작용 매커니즘을 명확히 이해해야 합니다. Airflow의 5대 핵심 아키텍처와 내부 동작 흐름, 그리고 실무 운영 최적화 포인트를 정리합니다.
1. Airflow 5대 핵심 컴포넌트 구조
+-------------------------------------------------------------+
| Webserver |
| (Flask UI, State Inspection, Trigger DAG/Task) |
+-------------------------------------------------------------+
| Read / Write
+-------------------------------------------------------------+
| Metadata Database |
| (MySQL / PostgreSQL - Single Source of Truth) |
+-------------------------------------------------------------+
^ ^
| Read / Write | Read / Write
+-------------------+ +------------------------------+
| Scheduler | | Executor / Workers |
| (DAG Parsing Loop)|--------->| (Local / Celery / K8s) |
| (Heartbeat Engine)| Queue/Msg| (Task Instance Execution) |
+-------------------+ +------------------------------+
^
| (Deferred Task Triggering)
+-------------------+
| Triggerer |
| (asyncio-based) |
+-------------------+
| 컴포넌트 | 주요 역할 | 실무 고려사항 |
| Scheduler | DAG 파싱, 실행 주기 계산, 태스크 큐 적재 | I/O 및 CPU 부하 집중. 멀티 스케줄러 구성 권장 |
| Metadata DB | DAG, TaskInstance, Connection, XCom 메타 저장 | Connection Pool 고갈 및 Lock 경합 주의 (InnoDB 필수) |
| Executor & Worker | 큐에 적재된 실제 태스크 로직 구동 | Celery/K8s 전환 기준 설정 및 Concurrency 관리 |
| Webserver | UI 제공, 파이프라인 모니터링, 수동 실행 제어 | 무거운 연산 금지, Read-mostly 트래픽 유지 |
| Triggerer | 센서/비동기 태스크의 리소스 낭비 방지 (Event-driven) | 단일 프로세스에서 수천 개의 비동기 대기 처리 가능 |
2. 컴포넌트별 상세 동작 원리와 내부 매커니즘
(1) Scheduler: DAG Parsing Loop와 State Transition
Airflow Scheduler는 멀티프로세스로 구동되며 내부적으로 크게 두 가지 루프를 반복합니다.
- DAG Parsing Loop (DagFileProcessorProcess): dag_dir_list_interval 주기로 파이썬 파일들을 동적 파싱하여 메모리에 로드하고 Metadata DB의 dag, serialized_dag 테이블에 직렬화하여 저장합니다.
- Scheduling Loop: DB를 폴링하여 실행 가능한 상태(scheduled)의 TaskInstance를 식별하고, 의존성(Upstream, Trigger Rule)이 충족되면 queued 상태로 변경한 뒤 Executor로 전달합니다.
(2) Metadata Database: 트랜잭션과 격리 수준
Metadata DB는 시스템의 Single Source of Truth입니다.
- MySQL 구성 시 주의점: Airflow는 8.0 이상을 공식 지원하며, 기본 트랜잭션 격리 수준(REPEATABLE-READ)에서 데드락이 발생할 확률이 높으므로 READ COMMITTED 설정을 권장합니다.
- 주요 테이블: dag_run, task_instance, log, xcom, variable, serialized_dag.
(3) Executor: 분산 처리 엔진의 선택
- LocalExecutor: 단일 노드 멀티프로세스 환경에 적합. 소규모 배치 파이프라인이나 마이그레이션 전용 점프박스 인스턴스에 구축할 때 오버헤드가 적습니다.
- CeleryExecutor / KubernetesExecutor: 분산 워커 클러스터 환경. Celery는 Redis/RabbitMQ와 같은 브로커가 추가로 필요하며, K8sExecutor는 태스크마다 Pod를 생성하여 완전한 리소스 격리를 보장합니다.
(4) Triggerer: Deferrable Operator를 통한 리소스 효율화
전통적인 Sensor(예: SqlSensor, FileSensor)는 조건이 만족될 때까지 워커 슬롯 1개를 점유(worker slot holding)하여 리소스를 낭비합니다. Airflow 2.2+의 Triggerer는 Python asyncio 기반으로 태스크 상태를 DB에 deferred로 저장한 뒤 슬롯을 반환하고, 이벤트가 감지되었을 때만 다시 워커에 스케줄링합니다.
3. 태스크 라이프사이클과 상태 변화 흐름
[None]
│
▼ (Scheduler 식별)
[Scheduled]
│
▼ (Dependencies Checked & Queue Insert)
[Queued]
│
▼ (Worker Slot 할당 & 실행 시작)
[Running] ──(Deferrable Operator 트리거)──> [Deferred] ──(Event 완료)──┐
│ │
├─────────────────────────────────────────────────────────────────────┘
│
├───► [Success]
│
├───► [Failed] ──► (Retries 남아있을 경우) ──► [Up_for_retry]
│
└───► [Upstream_failed]
태스크 실행 중 워커 프로세스가 갑자기 종료되거나 네트워크가 단절되면 메타스토어와 실제 프로세스 상태 간 불일치가 발생합니다. Scheduler는 주기적인 Heartbeat 점검을 통해 일정 시간(scheduler_zombie_task_threshold) 응답이 없는 태스크를 Zombie Task로 판정하고 failed 처리 후 재시도 루틴으로 넘깁니다.
4. 대규모 파이프라인을 위한 airflow.cfg 튜닝 포인트
대용량 ETL 및 DB 마이그레이션 작업 시 스케줄러 멈춤이나 DB Lock 병목을 방지하기 위한 핵심 파라미터 세팅입니다.
[core]
# 전체 Airflow 인스턴스에서 동시에 실행할 수 있는 최대 태스크 수
parallelism = 64
# DAG 파일 파싱 시 최상단 전역 코드가 무거울 경우 타임아웃 방지 (기본 30초 -> 60초)
dagbag_import_timeout = 60
# XCom 백엔드에 대용량 데이터를 올리지 않도록 주의 (필요시 커스텀 S3/GCS XCom 백엔드 구성)
max_active_tasks_per_dag = 16
[scheduler]
# DAG 폴더 파싱 주기(초). 파일이 많다면 30~60초로 늘려 Disk I/O 및 CPU 부하 경감
min_file_process_interval = 30
dag_dir_list_interval = 60
# 스케줄러 1회 루프당 메타 DB에서 큐로 넘길 TaskInstance 개수
max_tis_per_query = 32
# 워커 심장박동 점검 주기 및 좀비 감지 주기
scheduler_heartbeat_sec = 5
scheduler_zombie_task_threshold = 300
[database]
# 메타스토어 Connection Pool 관리 (SQLAlchemy 레벨)
sql_alchemy_pool_size = 20
sql_alchemy_max_overflow = 10
sql_alchemy_pool_recycle = 1800
5. DB 마이그레이션 DAG 모범 사례
스케줄러의 부하를 줄이기 위해서는 DAG 파일 내 Top-level(함수 밖)에서 DB 커넥션을 맺거나 무거운 계산을 수행하지 않아야 합니다.
from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.providers.mysql.hooks.mysql import MySqlHook
default_args = {
'owner': 'data_platform',
'depends_on_past': False,
'retries': 2,
'retry_delay': timedelta(minutes=3),
}
def extract_and_load_chunk(**context):
"""
Top-level이 아닌 Task 실행 시점에 Hook을 호출하여
Scheduler의 DAG 파싱 부하 및 DB Connection 누수를 차단합니다.
"""
source_hook = MySqlHook(mysql_conn_id='source_legacy_db')
target_hook = MySqlHook(mysql_conn_id='target_cloud_db')
logical_date = context['logical_date']
sql = """
SELECT id, order_no, amount, created_at
FROM source_orders
WHERE created_at >= %s AND created_at < %s
"""
start_dt = logical_date.strftime('%Y-%m-%d 00:00:00')
end_dt = (logical_date + timedelta(days=1)).strftime('%Y-%m-%d 00:00:00')
records = source_hook.get_records(sql=sql, parameters=(start_dt, end_dt))
if records:
target_hook.insert_rows(
table='target_orders',
rows=records,
target_fields=['id', 'order_no', 'amount', 'created_at'],
commit_every=5000
)
with DAG(
dag_id='migration_mysql_orders_chunk',
default_args=default_args,
description='Chunk-based daily partition migration pipeline',
schedule_interval='@daily',
start_date=datetime(2026, 1, 1),
catchup=False,
max_active_runs=3, # 동시 실행 DAGRun 제한으로 Target DB I/O 보호
tags=['migration', 'mysql', 'orders']
) as dag:
migrate_task = PythonOperator(
task_id='task_chunk_migration',
python_callable=extract_and_load_chunk,
pool='db_migration_pool', # Concurrency Pool 제어로 Connection 고갈 방지
)
아키텍처 관점의 핵심 체크리스트
- Top-level 코드 분리: DAG 파일의 루트 레벨에서 DB 조회나 API 호출을 실행하면, 스케줄러가 파싱할 때마다(수십 초 주기) 해당 코드가 실행되어 스케줄러 전체가 다운될 수 있습니다.
- Airflow Pool 활용: 타겟 DB의 max_connections 및 I/O 성능에 맞춰 Airflow UI에서 전용 Pool 슬롯 수를 지정하고 태스크에 할당하세요.
- XCom 남용 금지: 대용량 데이터를 태스크 간에 넘길 때 XCom을 사용하면 메타 DB의 용량이 폭증하고 락이 발생합니다. XCom은 실행 메타데이터(카운트, 파티션 키) 전달에만 국한해야 합니다.
'Database > Airflow' 카테고리의 다른 글
| [airflow]스케줄러 내부 동작부터 Celery vs K8s Executor 비교, 그리고 MySQL 메타스토어 튜닝 (0) | 2026.10.09 |
|---|---|
| [airflow]통합 데이터 이행(ETL) 기술 검토: 왜 레거시 스크립트 대신 Apache Airflow를 선택했는가? (0) | 2026.10.09 |
| [Airflow]Apache Airflow의 본질과 엔터프라이즈 파이프라인 활용 전략 (0) | 2026.10.08 |
