← 학습 카테고리

Learn

Airflow

151개 모듈 · 현재 130번째

Airflow 모듈 130/151 airflow-learn-130

동적 Dag 생성과 Airflow Variables/Timetable의 top-level 성능 함정

Apache Airflow Official Documentation (in-repo snapshot) — Apache Software Foundation best-practices.rst - "Dynamic Dag Generation" ~ "Timetables" 섹션 (약 369-495행)

이 모듈을 다 읽으면

  • 동적 Dag 생성을 구현하는 세 가지 방식을 설명할 수 있다
  • Airflow Variables를 top-level 코드에서 사용하면 안 되는 이유와, 불가피할 때 성능 저하를 완화하는 방법을 설명할 수 있다
  • Variable.get()과 Jinja 템플릿({{ var.value.<name> }})이 값을 읽는 시점이 어떻게 다른지 설명할 수 있다
  • 커스텀 Timetable 코드에서 Variable/Connection 접근을 __init__이 아닌 다른 지점으로 옮겨야 하는 이유를 설명할 수 있다

비슷한 Dag를 다수 유지해야 하거나 테이블 목록이 자주 바뀌는 경우 Dag를 수동으로 작성하기보다 동적으로 생성하는 것이 유리하다. 이때도 top-level 코드 성능 원칙이 그대로 적용된다. 특히 Airflow Variables는 네트워크·DB 호출을 유발하므로 top-level에서 직접 Variable.get()을 호출하면 파싱마다 요청이 발생해 Dag 파싱이 느려지거나 타임아웃날 수 있다. Jinja 템플릿을 쓰면 값 조회가 태스크 실행 시점까지 지연된다. 커스텀 Timetable도 같은 원칙으로 __init__ 등 top-level 실행 경로에서 DB 접근을 피해야 한다.

동적 Dag 생성의 세 가지 방식

Dag를 수작업으로 작성하는 것이 비현실적인 경우가 있다 - 파라미터 하나만 다르고 비슷한 일을 하는 Dag가 많거나, 테이블이 바뀔 때마다 Dag를 일일이 수정하고 싶지 않은 테이블 적재 작업 세트가 있는 경우 등이다. 이럴 때는 Dag를 동적으로 생성하는 것이 더 유용할 수 있다.

동적 Dag 설정은 크게 세 가지 방식 중 하나로 구성할 수 있다: 첫째, (Airflow Variables와는 다른) 환경 변수를 통한 방식, 둘째, Dag 폴더 안에 메타데이터를 담은 형태로 외부에서 생성되어 제공되는 Python 코드를 통한 방식, 셋째, Dag 폴더 안의 외부에서 생성된 설정 메타데이터 파일을 통한 방식이다.

어떤 방식을 쓰든, 앞서 다룬 top-level 코드에서의 과도한 처리를 피하는 원칙은 동적 Dag 구성에서 특히 더 중요하다.

핵심 포인트

  • 동적 Dag 생성은 (1) 환경 변수, (2) 외부 생성 Python 코드, (3) 외부 생성 설정 메타데이터 파일 중 하나로 구성한다
  • 여기서 말하는 '환경 변수'는 Airflow Variables와는 별개의 개념이다
  • 동적 Dag 생성에서도 top-level 코드 과다 처리를 피하는 원칙이 특히 중요하다

Airflow Variables - top-level에서 쓰면 안 되는 이유와 캐싱

Airflow Variables를 사용하면 네트워크 호출과 DB 접근이 발생하므로, Dag의 top-level Python 코드에서 이를 사용하는 것은 최대한 피해야 한다(앞서 다룬 top-level 코드 원칙과 동일한 이유). 부득이하게 top-level Dag 코드에서 Airflow Variables를 써야 한다면, :ref:`실험적 캐시<config:secrets__use_cache>`를 활성화하고 적절한 :ref:`ttl<config:secrets__cache_ttl_seconds>`을 설정해 그 영향을 완화할 수 있다.

Operator의 ``execute()`` 메서드 안에서는 Airflow Variables를 자유롭게 사용할 수 있다. 또한 기존 오퍼레이터에 Jinja 템플릿을 통해 Airflow Variables를 전달할 수도 있는데, 이 경우 값을 읽는 시점이 태스크 실행 시점까지 지연된다. 템플릿 문법은 ``{{ var.value.<variable_name> }}``이며, Variable 값이 JSON이라면 ``{{ var.json.<variable_name> }}``으로 역직렬화할 수 있다.

top-level 코드에서 Jinja 템플릿으로 변수를 사용하면 태스크가 실행되기 전까지는 요청이 발생하지 않는 반면, ``Variable.get()``은 캐싱이 활성화되어 있지 않으면 스케줄러가 Dag 파일을 파싱할 때마다 매번 요청을 발생시킨다. 캐싱 없이 ``Variable.get()``을 사용하면 Dag 파일 처리 성능이 나빠지고, 심한 경우 Dag 파일이 완전히 파싱되기 전에 타임아웃날 수도 있다.

나쁜 예시는 ``foo_var = Variable.get("foo")``처럼 top-level에서 직접 호출하거나, ``bash_command`` f-string 안에서 ``Variable.get('foo')``를 호출하는 것이다. 좋은 예시는 ``env={"foo_env": "{{ var.value.get('foo') }}"}``처럼 Jinja 템플릿으로 위임하거나, ``@task`` 함수 내부(파싱이 아닌 태스크 실행 시에만 호출됨)에서 ``Variable.get("foo")``를 호출하는 것이다.

보안을 위해서는 민감한 데이터를 담은 Variable에 대해 :ref:`Secrets Backend<secrets_backend_configuration>`를 사용하는 것이 권장된다.

핵심 포인트

  • Airflow Variables 사용은 네트워크/DB 호출을 유발하므로 top-level Dag 코드에서는 최대한 피해야 한다
  • 부득이하면 secrets.use_cache와 secrets.cache_ttl_seconds로 실험적 캐시를 활성화해 완화한다
  • Jinja 템플릿({{ var.value.<name> }}, {{ var.json.<name> }})은 값 조회를 태스크 실행 시점까지 지연시킨다
  • 캐싱 없는 Variable.get()을 top-level에서 쓰면 Dag 파일 파싱마다 요청이 발생해 파싱 성능 저하나 타임아웃까지 이어질 수 있다
  • @task 함수 내부에서의 Variable.get() 호출은 파싱 시가 아니라 태스크 실행 시에만 호출되므로 안전하다

Timetables - __init__에서 DB 접근을 피해야 하는 이유

커스텀 Timetable 코드의 top-level에서 Airflow Variables/Connections를 사용하거나 Airflow 데이터베이스에 접근하는 것도 피해야 한다. DB 접근은 Dag의 실행 시점까지 지연되어야 하며, 이는 Timetable 클래스의 초기화 인자로 variable/connection 조회 결과를 넘기거나, 커스텀 Timetable 모듈의 top-level에서 Variable/Connection을 조회하면 안 된다는 뜻이다.

나쁜 예시는 ``__init__(self, *args, something=Variable.get("something"), **kwargs)``처럼 기본값 표현식 자체에서 ``Variable.get()``을 호출하는 것이다 - 기본값 표현식은 클래스가 정의되는 시점(모듈 로드 시, 즉 top-level)에 평가되기 때문이다. 좋은 예시는 ``__init__(self, *args, something="something", **kwargs)``로 받은 뒤, ``__init__`` 본문 안에서 ``self._something = Variable.get(something)``처럼 실제 조회를 지연시키는 것이다.

핵심 포인트

  • 커스텀 Timetable의 top-level(모듈 레벨, __init__ 기본값 표현식 포함)에서 Variable/Connection/DB 접근을 하면 안 된다
  • __init__ 기본값 표현식(something=Variable.get(...))은 클래스 정의 시점에 평가되므로 사실상 top-level 코드다
  • 값 조회는 기본값 표현식이 아니라 __init__ 본문 안에서 수행해 실행 시점까지 지연시켜야 한다