Blog
airflowarchitecturetask-sdkexecutorscheduler

Airflow 3 아키텍처 해부 — 컴포넌트와 Task Execution API

Airflow 3의 핵심 컴포넌트(Scheduler·API server·DAG processor·Triggerer·Worker·메타DB)와 워커가 메타DB 대신 API server를 경유하는 Task Execution API의 변화를 그림으로 풀어봅니다.

Data Dynamics2026년 6월 25일16 min read

이 글은 Airflow 3 실전 연재의 1편입니다. 직전 편 Airflow 3 개요 & 언제 쓰나에서 "왜 3인가"를 짚었다면, 이번 편은 한 단계 안으로 들어가 무엇이 어떻게 맞물려 돌아가는지를 해부합니다. 다음 편 클러스터로 구성하기에서 이 컴포넌트들을 실제 노드에 배치하기 전에, 먼저 머릿속에 지도를 한 장 그려두는 셈이죠.

Airflow를 처음 운영해 본 분들은 보통 "스케줄러 하나면 다 도는 줄 알았다"고 합니다. 그런데 3에서는 그림이 꽤 또렷하게 나뉩니다. 가장 큰 변화 하나만 먼저 말씀드리면 이렇습니다.

Airflow 3에서 워커(태스크)는 더 이상 메타데이터 DB에 직접 접속하지 않습니다. 모든 상태 읽기·쓰기는 API server의 Task Execution API를 거칩니다.

이 한 줄이 왜 중요한지는 글 후반에 설명하고, 먼저 전체 그림부터 봅시다.

1. 핵심 컴포넌트 한눈에 보기

Airflow 3은 역할이 분명한 여섯 개의 조각으로 이뤄집니다. 핵심은 **메타데이터 DB가 진실의 원천(source of truth)**이고, 그 DB에 직접 손을 대는 컴포넌트는 의외로 적다는 점입니다.

Loading diagram…

각 조각의 역할을 풀어보면 이렇습니다.

컴포넌트하는 일메타DB 직접 접속?
Scheduler어떤 DAG run·태스크를 언제 돌릴지 결정하고, 실행 가능한 태스크를 Executor 큐에 넣음O
API serverUI를 그리고 REST API를 제공하며, 워커가 통신하는 Task Execution API의 관문 역할O
DAG processorDAG 파일을 파싱해 구조를 메타DB에 기록. 3에서 스케줄러와 독립 프로세스로 분리O
Triggererdeferred(지연) 태스크를 워커 슬롯 없이 async로 폴링O
Worker실제 태스크 코드를 실행. 상태는 API server 경유로만 읽고 씀X
Metadata DBDAG·run·태스크 상태·연결정보 등 모든 진실을 저장(자기 자신)

2.x를 기억하는 분이라면 "webserver"가 보이지 않는 게 어색할 겁니다. Airflow 3에서 webserver는 API server로 대체됐습니다. UI와 REST가 한 프로세스로 합쳐졌고, 여기에 Task Execution API까지 얹혔습니다.

DAG processor를 왜 떼어냈나

2.x에서는 스케줄러가 DAG 파싱까지 같이 했습니다. 문제는 DAG 파일이 임의의 파이썬 코드라는 점입니다. 누군가 DAG 안에서 무거운 import를 하거나, 모듈 최상단에서 외부 API를 호출하면 그 부담이 고스란히 스케줄러로 흘러들었죠.

3에서는 DAG processor가 독립 프로세스로 분리됐습니다. 덕분에 (1) 파싱이 스케줄링 루프를 막지 않고, (2) DAG 코드의 임의 실행을 스케줄러 신뢰 경계 밖으로 격리하며, (3) 어디서 DAG를 가져올지는 DAG bundles(git 등)로 따로 정의합니다. 한마디로 "파싱"과 "스케줄링"이라는 서로 다른 일을 서로 다른 프로세스가 맡게 된 겁니다.

2. Task Execution API — 가장 중요한 변화

이제 글머리에서 예고한 그 변화를 봅시다. 2.x에서 워커는 메타데이터 DB에 직접 붙어 태스크 상태를 읽고 썼습니다. 이게 오래 운영하면 두 가지 통증을 만듭니다.

  • 보안: 모든 워커가 메타DB 자격증명을 들고 있어야 합니다. 워커 한 대가 뚫리면 DB 전체가 노출됩니다. 원격·엣지 워커라면 더 곤란하죠 — DB 포트를 외부로 열어줘야 하니까요.
  • 결합도: 태스크 코드가 메타DB 스키마·SQLAlchemy 모델에 묶여, 파이썬 외 언어로 태스크를 짜기 어렵습니다.

Airflow 3은 그 사이에 Task Execution API라는 얇은 HTTP 계층을 끼워 넣습니다. 워커는 Task SDK를 통해 이 API와만 대화합니다.

# Airflow 3: DAG/태스크는 airflow.sdk에서 가져옵니다
from airflow.sdk import dag, task
 
@dag(schedule="@daily", catchup=False)  # catchup 기본값이 False로 바뀐 점에 주의
def etl_pipeline():
    @task
    def extract():
        return {"rows": 1000}
 
    @task
    def transform(payload: dict):
        # 이 코드가 실행되는 워커는 메타DB를 모릅니다.
        # XCom 푸시/풀, 상태 보고 등 모든 통신은 Task SDK → Task Execution API로 흐릅니다.
        return payload["rows"] * 2
 
    transform(extract())
 
etl_pipeline()

핵심 정리.

측면2.xAirflow 3
워커 ↔ 상태 저장소워커가 메타DB 직접 접속워커가 Task Execution API(HTTP) 경유
워커가 가진 자격증명메타DB 접속정보API용 단기 JWT 토큰
원격/엣지 실행DB 포트 노출 필요 → 사실상 어려움HTTP만 열면 됨 → 자연스러움
태스크 언어파이썬에 강하게 결합API 기반이라 언어 비종속의 길이 열림
임포트 경로from airflow import DAGfrom airflow.sdk import dag, task, Asset

한 문장으로: 워커는 이제 DB를 모르는 'API 클라이언트'가 됐고, 그 덕에 원격 실행과 보안 격리가 자연스러워졌습니다.

이 변화가 바로 다음 절의 EdgeExecutor를 가능하게 만든 토대입니다. DB가 아니라 HTTP로만 대화하니, 워커를 데이터센터 밖 어디에 두든 상관없어진 거죠.

3. Executor — 어디서 태스크를 돌릴 것인가

Executor는 "스케줄러가 큐에 넣은 태스크를 실제로 어디서, 어떻게 돌릴지"를 결정하는 부품입니다. Airflow 3에서 고를 수 있는 대표적인 선택지는 다음과 같습니다.

Executor실행 방식적합한 상황주의점
Local스케줄러와 같은 머신의 서브프로세스개발·소규모·단일 노드머신 한 대 자원이 한계, 수평 확장 불가
Celery메시지 브로커(Redis/RabbitMQ) + 워커 풀안정적 상시 워커 풀로 중·대규모 운영브로커 운영 부담, 워커 상시 가동
Kubernetes태스크마다 Pod 생성격리·탄력적 스케일·이질적 자원 요구Pod 기동 지연(latency), K8s 운영 역량 필요
EdgeHTTP로 원격/엣지 워커에 위임온프렘·엣지·여러 망에 흩어진 워커3 신규, Task Execution API 위에서 동작

EdgeExecutor는 Task Execution API라는 토대 위에 세워진 신규 옵션입니다. 워커가 HTTP로만 컨트롤 플레인과 통신하므로, 방화벽 너머·다른 리전·온프렘 현장 같은 곳에도 워커를 둘 수 있습니다.

그리고 3의 또 한 가지 실용적인 변화 — 여러 Executor를 동시에(hybrid) 구성할 수 있습니다. 예를 들어 평상시 가벼운 태스크는 Celery로 받고, GPU가 필요한 무거운 태스크는 Kubernetes로 보내는 식으로, 태스크 단위로 실행처를 나눌 수 있게 됐습니다. 어떤 워크로드에 어떤 Executor를 매핑할지는 다음 편 클러스터 구성에서 실제 설정과 함께 다룹니다.

4. 태스크 한 개의 상태 전이

컴포넌트와 Executor를 봤으니, 이제 태스크 하나가 살아가는 동안 거치는 상태를 봅시다. 스케줄러가 "돌려도 되겠다"고 판단한 순간부터 끝날 때까지의 여정입니다.

Loading diagram…

여기서 눈여겨볼 두 가지.

  • up_for_retry: 태스크가 실패해도 retries가 남아 있으면 곧장 죽지 않고 재시도 대기 상태로 갑니다. 대기가 끝나면 다시 scheduled로 돌아와 큐잉되죠.
  • deferred: deferrable 연산자(예: 외부 작업 완료를 기다리는 센서)는 워커 슬롯을 붙잡고 폴링하지 않습니다. 대신 Triggerer에게 "이 조건이 되면 깨워줘"라고 넘기고 deferred 상태로 빠집니다. 조건이 충족되면 Triggerer가 신호를 보내고, 태스크는 다시 scheduled로 돌아옵니다. 워커 슬롯을 점유하지 않으니 대규모 대기 작업에서 자원을 크게 아낍니다.

5. DAG 한 번 실행의 생명주기

이제 조각들을 시간 축에 올려놓고, DAG run 하나가 실행되는 전 과정을 따라가 봅시다.

Loading diagram…

순서대로 짚으면 이렇습니다.

  1. 파싱 — DAG processor가 DAG 소스(DAG bundle)를 읽어 파싱하고, 구조를 메타DB에 기록합니다.
  2. 스케줄 결정 — Scheduler가 메타DB를 보고 어떤 run·태스크를 실행할지 결정합니다.
  3. 큐잉 — 실행 가능한 태스크를 Executor 큐에 넣습니다(queued).
  4. 배정 & 실행 — Executor가 워커에 태스크를 넘기고, 워커가 코드를 실행합니다(running).
  5. 상태 기록 — 워커는 시작·진행·종료를 전부 Task Execution API로 보고하고, API server가 그 내용을 메타DB에 반영합니다.

2.x였다면 5단계에서 워커가 메타DB에 직접 썼을 자리에, 3에서는 API server가 중간에 들어와 모든 쓰기를 받아 처리한다는 점이 결정적인 차이입니다. 이 덕분에 워커는 DB를 전혀 몰라도 되고, 보안 경계가 컨트롤 플레인 안쪽으로 깔끔하게 모입니다.

마치며

Airflow 3의 아키텍처를 한 문장으로 요약하면 이렇습니다.

메타DB는 진실의 원천이고, 그 DB를 만지는 건 컨트롤 플레인(스케줄러·API server·DAG processor·Triggerer)뿐이며, 워커는 Task Execution API로만 대화하는 클라이언트가 됐다.

이 구조 덕분에 DAG 파싱이 스케줄링을 막지 않고, 원격·엣지 워커가 자연스러워지며, 여러 Executor를 한 클러스터에 섞어 쓸 수 있게 됐습니다. 머릿속에 이 지도가 그려졌다면, 이제 실제 노드에 컴포넌트를 배치할 차례입니다.

다음 편 Airflow 3 클러스터로 구성하기에서 이 컴포넌트들을 어떻게 분리 배포하고, 어떤 Executor 조합을 고를지 구체적인 구성으로 이어가겠습니다.

더 깊은 내용은 Apache Airflow 공식 문서를 참고하세요.