Dag Serialization — Webserver 경량화와 3.1 버전 계약
Apache Airflow Official Documentation (in-repo snapshot) — Apache Software Foundation administration-and-deployment/dag-serialization.rst (전체)
이 모듈을 다 읽으면
- Dag Serialization이 Webserver·Scheduler를 Dag 파일 파싱으로부터 어떻게 분리하는지 설명할 수 있다
- min_serialized_dag_update_interval·num_dag_runs_to_retain_rendered_fields·compress_serialized_dags 설정의 트레이드오프를 판단할 수 있다
- Airflow 3.1의 버전 계약(client_defaults, __version)이 Task SDK와 서버 컴포넌트를 어떻게 독립적으로 배포 가능하게 만드는지 설명할 수 있다
- MappedOperator의 partial 인자가 default 값 계층 구조에서 어떻게 처리되는지 설명할 수 있다
Dag Serialization은 Scheduler가 파싱한 Dag를 JSON으로 직렬화해 메타데이터 DB에 저장하고, Webserver는 그 직렬화본만 읽어 Dag 파일 자체에 접근할 필요가 없게 만드는 기능이다(Airflow 2.0+에서는 끌 수 없다). Airflow 3.1부터는 이 직렬화 포맷이 Task SDK와 서버 컴포넌트 사이의 버전 있는 계약으로 확장되어, 클라이언트·서버를 독립적으로 배포·업그레이드할 수 있는 아키텍처의 기반이 된다.
Dag Serialization이 해결하는 문제
Dag Serialization과 DB 영속화는 Airflow 1.10.7부터 지원되었고, Airflow 2.0.0부터는 Scheduler도 스케줄링 결정을 위해 직렬화된 Dag를 사용한다. Serialization이 없다면 Webserver와 Scheduler 둘 다 Dag 파일에 접근해서 각자 파싱해야 한다.
Dag Serialization의 목표는 Webserver를 Dag 파싱으로부터 분리해 아주 가볍게 만드는 것이다. Scheduler 안의 ``DagFileProcessorProcess``가 Dag 파일을 파싱하고, JSON 형식으로 직렬화해 메타데이터 DB에 ``SerializedDagModel``로 저장한다. Webserver는 더 이상 Dag 파일을 다시 파싱할 필요 없이, DB에 저장된 직렬화 Dag를 읽어 역직렬화한 뒤 DagBag을 만들어 UI에 보여준다. Scheduler 역시 스케줄링 결정을 내릴 때 실제 Dag 파일이 아니라 직렬화된 Dag를 사용한다(이는 Scheduler HA 구현의 일부로 이루어졌다).
Serialization이 가져오는 또 다른 핵심 이점은, Webserver가 시작할 때 전체 DagBag을 한 번에 로드하는 대신 Serialized Dag 테이블에서 각 Dag를 요청 시점에(on demand) 로드한다는 점이다. 이는 Webserver의 시작 시간과 메모리 사용량을 줄여주며, Dag 수가 많을수록 그 효과가 두드러진다.
소스 코드 자체도 DB에 저장하도록 설정할 수 있어(``DagCode`` 모델), 이렇게 하면 Webserver가 Dag 파일로부터 완전히 독립적이 된다. 다만 파일이 Docker 이미지에 임베드되어 있거나 다른 방식으로 Webserver에 제공된다면 반드시 필요한 것은 아니다.
마지막 요소는 템플릿 필드 렌더링이다. Serialization이 켜져 있으면 요청 시점에 템플릿을 렌더링하지 않고, 태스크가 worker에서 실행되기 전에 필드 내용의 사본을 저장해둔다 — 이 데이터는 ``RenderedTaskInstanceFields`` 모델에 저장된다. DB의 과도한 증가를 막기 위해 가장 최근 항목들만 유지되고 오래된 항목은 정리(purge)된다. Dag Serialization은 Airflow 2.0+부터는 필수이며 끌 수 없다.
핵심 포인트
- DagFileProcessorProcess가 Dag를 파싱·JSON 직렬화해 SerializedDagModel로 DB에 저장하고, Webserver·Scheduler는 이를 읽어 각각 UI 표시·스케줄링에 사용한다
- Webserver는 시작 시 전체 DagBag을 로드하지 않고 Serialized Dag 테이블에서 Dag를 요청 시점에 로드해 시작 시간·메모리를 절약한다
- DagCode 모델에 소스 코드까지 저장하면 Webserver가 Dag 파일로부터 완전히 독립적이 될 수 있다
- 템플릿 필드는 요청 시점 렌더링 대신 태스크 실행 전 사본이 RenderedTaskInstanceFields에 저장되며, 오래된 항목은 주기적으로 정리된다
- Dag Serialization은 Airflow 2.0.0부터 필수이며 비활성화할 수 없다
설정값과 한계
``airflow.cfg``의 ``[core]`` 섹션에서 세 가지 설정을 조정할 수 있다. ``min_serialized_dag_update_interval``(기본 30초)은 DB에 저장된 직렬화 Dag를 갱신하는 최소 간격으로, DB 쓰기 비율을 낮추는 데 도움이 된다. ``num_dag_runs_to_retain_rendered_fields``(기본 30)는 Rendered Task Instance Fields를 몇 개의 최근 Dag 실행분까지 유지할지를 제어하며, 그보다 오래된 실행의 기록은 태스크 실행 중 삭제된다. ``compress_serialized_dags``(기본 False)는 직렬화된 Dag를 DB에 압축해서 저장할지를 결정하며, 매우 큰 Dag가 있는 클러스터에 유용하지만 True로 설정하면 Dag 의존성(dependencies) 뷰가 비활성화된다.
한계도 있다. 사용자 정의 필터·매크로를 쓰는 경우, 아직 실행되지 않은 태스크 인스턴스에 대한 Rendered View는 Webserver가 접근할 수 없는 외부 모듈을 참조하고 있어 잘못된 결과를 보여줄 수 있다. 이런 상황에서는 ``airflow tasks render`` CLI 명령으로 template_fields 렌더링을 디버깅·테스트할 수 있다. 태스크 실행이 시작되면 Rendered Template Fields가 별도 테이블에 DB로 저장되고, 그 이후에는 Webserver의 Rendered View 탭에 올바른 값이 표시된다.
완전히 stateless한 Webserver를 쓰려면 Airflow 1.10.10 이상이 필요하다 — 1.10.7~1.10.9는 일부 경우에 여전히 Dag 파일 접근이 필요했다.
``json`` 표준 라이브러리 대신 ``ujson`` 같은 다른 JSON 라이브러리를 쓰고 싶다면, ``airflow_local_settings.py``에 ``json`` 변수를 정의해 그 라이브러리를 가리키게 하면 된다.
핵심 포인트
- min_serialized_dag_update_interval(기본 30초)은 DB 쓰기 빈도를, num_dag_runs_to_retain_rendered_fields(기본 30)는 Rendered Field 보존 범위를 제어한다
- compress_serialized_dags=True는 대형 Dag의 DB 저장 공간을 아끼지만 Dag 의존성 뷰를 비활성화하는 트레이드오프가 있다
- 아직 실행되지 않은 태스크의 Rendered View는 사용자 정의 필터/매크로 때문에 부정확할 수 있으며, airflow tasks render CLI로 디버깅한다
Airflow 3.1+ 버전 계약과 클라이언트/서버 분리
Airflow 3.1부터 Dag Serialization은 Task SDK와 Airflow 서버 컴포넌트(Scheduler, API-Server) 사이의 버전 있는 계약(versioned contract)이 된다. 이는 Task Execution API와 결합되어 클라이언트·서버 컴포넌트를 분리하고, 하위 호환을 유지하면서 독립적인 배포·업그레이드와 기본값의 자동 해석을 가능하게 한다.
Airflow가 Dag를 처리할 때 서버 쪽에서는 다음 우선순위(낮은 것부터 높은 것 순)로 기본값을 적용한다: (1) 스키마 기본값(Airflow 내장 기본값, 최하위), (2) 클라이언트 기본값(SDK별 기본값), (3) Dag의 ``default_args``(기존 동작), (4) partial 인자(MappedOperator가 공유하는 값), (5) 태스크 값(명시적으로 설정한 값, 최고 우선순위). 더 구체적인 설정이 더 일반적인 설정을 오버라이드하는 구조다.
직렬화된 Dag는 이제 공통 기본값을 담는 ``client_defaults`` 섹션을 포함한다. 예를 들어 ``client_defaults.tasks``에 ``retry_delay``와 ``owner``를 지정해두면, Dag의 ``default_args``에 명시된 값(예: ``retries``)이 이를 오버라이드하고, 태스크에 명시적으로 지정된 값(예: ``bash_command``, ``owner``)이 다시 그 위에 우선한다. 시스템은 계층을 따라 올라가며 빠진 값을 자동으로 채운다.
MappedOperator(동적 태스크 매핑)의 partial 인자도 이 기본값 체계에 참여한다. 예를 들어 ``BashOperator.partial(task_id=..., retries=2, owner="team_lead").expand(bash_command=[...])``처럼 선언하면, ``expand``로 생성된 각 태스크 인스턴스는 partial에 지정된 ``retries``·``owner``를 물려받고, partial에 지정하지 않은 ``pool`` 같은 값은 client_defaults에서 채워진다.
핵심 포인트
- 서버 쪽 기본값 적용 우선순위는 스키마 기본값 < 클라이언트(SDK) 기본값 < Dag default_args < partial 인자 < 명시적 태스크 값 순이다
- 직렬화된 Dag의 client_defaults.tasks 섹션이 SDK별 공통 기본값을 담고, 더 구체적인 레벨의 설정이 이를 순차적으로 오버라이드한다
- MappedOperator의 partial 인자로 지정한 값은 expand로 생성된 모든 태스크 인스턴스에 상속되며, 지정하지 않은 값은 client_defaults에서 채워진다
서버·클라이언트 분리 아키텍처와 SDK 요구사항
이 계약은 Scheduler·API-Server(서버 컴포넌트, 오케스트레이션 담당·사용자 코드 실행 안 함)와 Task SDK·Dag processor(클라이언트 컴포넌트, 격리된 환경에서 사용자 코드 실행)를 완전히 분리하는 것을 목표로 한다. 이로써 얻는 이점은: 사용자 환경을 건드리지 않고 서버 컴포넌트만 독립적으로 업그레이드, 하나의 서버 버전이 여러 SDK 버전을 동시에 지원하는 버전 호환성, 서버·클라이언트를 독립적으로 배포·확장할 수 있는 유연성, 사용자 코드가 클라이언트 환경에서만 실행되는 보안 격리, 그리고 어떤 언어로든 호환 Task SDK를 구현할 수 있는 다중 언어 지원이다.
Task SDK 구현체가 지켜야 할 요구사항은 세 가지다: (1) 공개된 스키마를 따라야 한다 — Dag 직렬화는 ``https://airflow.apache.org/schemas/dag-serialization/v2.json`` 같은 스키마에 맞는 JSON을 생성해야 하고, 태스크 실행은 ``https://airflow.apache.org/schemas/execution-api/2025-05-20.json`` 같은 Execution API 스키마로 런타임 통신을 지원해야 한다. (2) 선택적으로 ``client_defaults.tasks`` 섹션에 SDK별 기본값을 제공할 수 있다. (3) 직렬화 포맷 버전을 나타내는 ``__version`` 필드를 포함해 적절히 버전을 관리해야 한다. SDK가 두 스키마 계약을 준수하는 한, 서버는 호환 SDK로부터 온 Dag를 올바르게 역직렬화하고, 런타임 태스크 실행 통신을 지원하며, 계층에 따라 적절한 기본값을 적용하고, SDK 버전·언어 전반의 호환성을 유지할 것을 보장한다.
Airflow 3.1 시점의 현재 상태는 이 계약이 클라이언트/서버 분리의 기반을 마련한 단계다. 일부 서버 컴포넌트가 여전히 Task SDK 코드를 포함하고 있고 그 반대도 있지만, 스키마 준수는 컴포넌트가 실제로 분리되었을 때 독립 배포를 가능하게 하고, 버전 호환성은 코드 결합 여부와 무관하게 동작하며, 배포 분리는 아직 완전히 구현되지 않았어도 아키텍처적으로는 이미 지원된다. 완전한 코드 분리는 향후 릴리스에서 계획되어 있으며, 이 스키마 계약이 그 진화 과정에서도 안정적인 인터페이스로 남을 것이다.
핵심 포인트
- 서버 컴포넌트(Scheduler, API-Server)는 오케스트레이션만 담당하고 사용자 코드를 실행하지 않으며, 클라이언트 컴포넌트(Task SDK, Dag processor)가 격리 환경에서 사용자 코드를 실행한다
- Task SDK는 dag-serialization 스키마와 execution-api 스키마를 모두 준수해야 하고, __version 필드로 포맷 버전을 명시해야 한다
- Airflow 3.1 시점에서는 일부 컴포넌트 간 코드 결합이 남아 있지만, 스키마 계약 자체는 완전한 분리를 향한 안정적 기반으로 설계되었다