DAG 직렬화
DAG 직렬화 (Dag Serialization)
이 페이지는 Airflow Webserver를 stateless(무상태)로 만들기 위한 DAG 직렬화(Dag Serialization)와 DB 영속화를 다뤄요. Scheduler의 DagFileProcessorProcess가 Dag 파일을 파싱해 JSON으로 직렬화하고 Metadata DB에 저장하면, Webserver는 그 직렬화된 DAG를 읽어 UI를 보여줘요. 이렇게 되면 Webserver가 Dag 파일을 직접 파싱할 필요가 없어져 가벼워져요.
출처: 문서
본문
Airflow Webserver를 stateless 로 만들기 위해 Airflow >= 1.10.7은 Dag Serialization(직렬화)과 DB Persistence(영속화)를 지원해요. Airflow 2.0.0부터 Scheduler도 일관성을 위해 직렬화된 DAG를 사용해 스케줄링 결정을 내려요.

Dag Serialization과 DB 영속화가 없으면 Webserver와 Scheduler 모두 Dag 파일에 접근해야 해요. Scheduler와 Webserver 둘 다 Dag 파일을 파싱해야 하죠.
Dag Serialization을 사용하면 Webserver를 Dag 파싱에서 분리(decouple)할 수 있는데, 이를 통해 Webserver를 매우 가볍게 만들 수 있어요.
위 이미지에서 보듯이 이 기능을 사용하면 Scheduler의 DagFileProcessorProcess가 Dag 파일을 파싱해 JSON 형식으로 직렬화하고 Metadata DB에 SerializedDagModel 모델로 저장해요.
이제 Webserver는 Dag 파일을 다시 파싱하는 대신 JSON으로 직렬화된 DAG를 읽어 역직렬화(de-serialize)해 DagBag을 만들고 이를 UI에 보여줘요. 그리고 Scheduler는 스케줄링 결정을 내리기 위해 실제 DAG가 필요하지 않아요. Dag 파일을 사용하는 대신, Airflow 2.0.0부터 DAG를 스케줄링하는 데 필요한 모든 정보를 담은 직렬화된 DAG를 사용해요(이는 Scheduler HA의 일부로 구현됐어요).
Dag Serialization의 일부로 구현된 핵심 기능 중 하나는, Webserver가 시작할 때 전체 DagBag을 로드하는 대신 Serialized Dag 테이블에서 각 DAG를 주문(요청) 시에만 로드한다는 점이에요. 이는 Webserver의 시작 시간과 메모리를 줄이는 데 도움이 돼요. DAG가 많을수록 이 감소 효과가 두드러져요.
소스 코드를 데이터베이스에 저장하도록 활성화하면 Webserver가 Dag 파일과 완전히 독립적이 될 수 있어요. 파일이 Docker 이미지에 내장되어 있거나 다른 방법으로 Webserver에 제공할 수 있다면 이는 필요하지 않아요. 데이터는 DagCode 모델에 저장돼요.
마지막 요소는 템플릿 필드 렌더링이에요. 직렬화가 활성화되면 템플릿이 요청 시 렌더링되지 않고, worker에서 Task가 실행되기 전에 필드 내용의 복사본이 저장돼요. 데이터는 RenderedTaskInstanceFields 모델에 저장돼요. 데이터베이스의 과도한 성장을 제한하기 위해 가장 최근 항목만 유지하고 이전 항목은 제거돼요.
Note
Dag Serialization은 필수이며 Airflow 2.0+에서는 끌 수 없어요.
Dag Serialization 설정
airflow.cfg에 다음 설정을 추가해요:
[core]
# You can also update the following default configurations based on your needs
min_serialized_dag_update_interval = 30
num_dag_runs_to_retain_rendered_fields = 30
compress_serialized_dags = False
min_serialized_dag_update_interval: 이 플래그는 DB의 직렬화된 DAG가 업데이트되어야 하는 최소 간격(초)을 설정해요. 이는 데이터베이스 쓰기 속도를 줄이는 데 도움이 돼요.num_dag_runs_to_retain_rendered_fields: Rendered Task Instance Fields가 유지되는 최근 dag run의 수를 제어해요. 더 오래된 run의 레코드는 Task 실행 중에 삭제돼요.compress_serialized_dags: 이 옵션은 직렬화된 DAG를 데이터베이스에 압축할지 여부를 제어해요. 클러스터에 매우 큰 DAG가 있을 때 유용해요.True로 설정하면 Dag 의존성 뷰가 비활성화돼요.
Airflow < 1.10.7에서 업그레이드하는 경우 airflow db migrate를 실행하는 것을 잊지 마세요.
제한 사항
- 사용자 정의 필터와 매크로를 사용할 때, Webserver의 Rendered View는 아직 실행되지 않은 TaskInstance(TI)에 대해 잘못된 결과를 보여줄 수 있어요. 이는 Webserver가 접근할 수 없는 외부 모듈을 사용할 수 있기 때문이에요. 이런 상황에서는
airflow tasks renderCLI 명령을 사용해template_fields의 렌더링을 디버깅하거나 테스트해요. Task 실행이 시작되면 Rendered Template Fields가 별도 테이블에 DB에 저장되고, 이후에는 Webserver(Rendered View 탭)에 올바른 값이 표시돼요.
Note
완전히 stateless한 Webserver를 위해서는 Airflow >= 1.10.10이 필요해요. Airflow 1.10.7에서 1.10.9는 어떤 경우에는 Dag 파일에 접근해야 했어요. 더 많은 정보: https://airflow.apache.org/docs/1.10.9/dag-serialization.html#limitations
다른 JSON 라이브러리 사용하기
표준 json 라이브러리 대신 ujson 같은 다른 JSON 라이브러리를 사용하려면 로컬 Airflow 설정(airflow_local_settings.py) 파일에 다음과 같이 json 변수를 정의해야 해요:
import ujson
json = ujson
로컬 설정을 구성하는 방법에 대한 자세한 내용은 로컬 설정 구성을 참고해요.
기본값 포함 DAG 직렬화 (Airflow 3.1+)
Airflow 3.1부터 Dag serialization은 Task SDK와 Airflow 서버 컴포넌트(Scheduler & API-Server) 사이의 버전 있는 계약(contract)을 수립해요. Task Execution API와 결합하면 클라이언트와 서버 컴포넌트를 분리(decouple)해서, 하위 호환성과 자동 기본값 해석을 유지하면서 독립적으로 배포·업그레이드할 수 있어요.
기본값이 작동하는 방식
Airflow가 DAG를 처리할 때 서버에 대해 특정 우선순위 순서로 기본값을 적용해요:
- 스키마 기본값 (Schema defaults): Airflow 내장 기본값 (가장 낮은 우선순위)
- 클라이언트 기본값 (Client defaults): SDK 특정 기본값
- Dag default_args: DAG 레벨 설정 (기존 동작)
- 부분 인자 (Partial arguments): MappedOperator 공유 값
- Task 값 (Task values): 명시적 Task 설정 (가장 높은 우선순위)
즉 서로 다른 수준에서 기본값을 설정할 수 있고, 더 구체적인 설정이 더 일반적인 설정을 덮어써요.
JSON 구조
직렬화된 DAG는 이제 공통 기본값을 담은 client_defaults 섹션을 포함해요:
{
"__version": 3,
"client_defaults": {
"tasks": {
"retry_delay": 300.0,
"owner": "data_team"
}
},
"dag": {
"dag_id": "example_dag",
"default_args": {
"retries": 3
},
"tasks": [{
"task_id": "example_task",
"task_type": "BashOperator",
"_task_module": "airflow.operators.bash",
"bash_command": "echo hello",
"owner": "specific_owner"
}]
}
}
값이 적용되는 방식
위 예제에서 example_task는 다음과 같은 최종 값을 갖게 돼요:
- retry_delay: 300.0 (client_defaults.tasks에서)
- owner: "data_team" (client_defaults.tasks에서)
- retries: 3 (dag.default_args에서, client_defaults를 덮어씀)
- bash_command: "echo hello" (명시적 Task 값)
- pool: "default_pool" (스키마 기본값에서)
시스템은 계층을 위로 올라가며 누락된 값을 자동으로 채워요.
MappedOperator 기본값 처리
MappedOperator(동적 Task 매핑)도 기본값 시스템에 참여해요:
# Dag Definition
BashOperator.partial(task_id="mapped_task", retries=2, owner="team_lead").expand(
bash_command=["echo 1", "echo 2", "echo 3"]
)
이 예제에서 생성된 세 개의 Task 인스턴스 각각은 다음을 상속해요:
- retries: 2 (부분 인자에서)
- owner: "team_lead" (부분 인자에서)
- pool: "default_pool" (client_defaults에서, 부분에 지정되지 않았으므로)
- bash_command: "echo 1", "echo 2", "echo 3" 각각 (expand에서)
독립 배포 아키텍처
분리된 컴포넌트: 직렬화 계약은 Task Execution API와 결합해 다음 사이의 완전한 분리를 가능하게 해요:
- 서버 컴포넌트 (Scheduler, API-Server): 오케스트레이션을 처리하고 사용자 코드를 실행하지 않아요.
- 클라이언트 컴포넌트 (Task SDK, Dag processor): 격리된 환경에서 사용자 코드를 실행해요.
주요 이점:
- 독립적 업그레이드: 사용자 환경을 건드리지 않고 서버 컴포넌트를 업그레이드할 수 있어요.
- 버전 호환성: 하나의 서버 버전이 여러 SDK 버전을 동시에 지원해요.
- 배포 유연성: 서버·클라이언트 컴포넌트를 별도로 배포·확장할 수 있어요.
- 보안 격리: 사용자 코드는 클라이언트 환경에서만 실행되고 서버 컴포넌트에서는 절대 실행되지 않아요.
- 다국어 SDK 지원: 어떤 언어로든 호환되는 Task SDK를 구현할 수 있어요.
SDK 요구사항: 모든 Task SDK 구현은 다음을 충족해야 해요:
- 공개 스키마를 따르기:
- Dag 직렬화: 스키마에 대해 검증이 되는 JSON을 생성해요. 예:
https://airflow.apache.org/schemas/dag-serialization/v2.json - Task 실행: Execution API 스키마를 통한 런타임 통신을 지원해요. 예:
https://airflow.apache.org/schemas/execution-api/2025-05-20.json
- Dag 직렬화: 스키마에 대해 검증이 되는 JSON을 생성해요. 예:
- client_defaults 포함: 선택적으로,
client_defaults.tasks섹션에 SDK 특정 기본값을 제공해요. - 올바른 버전 관리: 직렬화 형식을 나타내는
__version필드를 포함해요.
서버 보장: SDK가 두 스키마 계약을 모두 준수하는 한 Airflow 서버 컴포넌트는:
- 호환되는 모든 SDK의 DAG를 올바르게 역직렬화해요.
- 런타임 중 Task 실행 통신을 지원해요.
- 계층에 따라 적절한 기본값을 적용해요.
- SDK 버전과 언어 간 호환성을 유지해요.
구현 상태
현재 상태 (Airflow 3.1): 직렬화 계약은 클라이언트/서버 분리의 기반을 수립해요. 일부 서버 컴포넌트에는 여전히 Task SDK 코드가 포함되어 있지만(그 반대도 마찬가지), 이 계약은 다음을 보장해요:
- 스키마 준수는 컴포넌트가 분리될 때 독립적 배포를 가능하게 해요.
- 버전 호환성은 코드 결합과 무관하게 작동해요.
- 배포 분리는 아직 완전히 구현되지 않았더라도 아키텍처적으로 지원돼요.
향후 진화: 서버와 클라이언트 컴포넌트 간의 완전한 코드 분리는 향후 릴리스에서 계획되어 있어요. 스키마 계약은 이 진화가 계속되는 동안 일관되게 유지될 안정적인 인터페이스를 제공해요.