← 학습 카테고리

Learn

Airflow

151개 모듈 · 현재 49번째

Airflow 모듈 49/151 airflow-learn-49

타임존 처리

Apache Airflow Official Documentation (in-repo snapshot) — Apache Software Foundation authoring-and-scheduling/timezone.rst - Time Zones

이 모듈을 다 읽으면

  • Airflow가 시각을 내부적으로 어떻게 저장하고 Web UI에 어떻게 표시하는지 설명할 수 있다
  • naive datetime과 aware datetime의 차이, 그리고 Airflow가 naive start_date/end_date를 해석하는 규칙을 설명할 수 있다
  • default_timezone 설정과 Dag 자체 타임존의 관계를 설명할 수 있다
  • cron 스케줄과 timedelta 스케줄에서 DST(서머타임) 처리 방식의 차이를 판단할 수 있다

Airflow는 타임존 인식이 기본 활성화되어 있으며 모든 시각을 내부적으로 UTC로 저장한다. Web UI는 기본적으로 UTC로 표시하고 사용자가 로컬/서버 타임존으로 바꿔볼 수 있다. Dag 작성 시에는 항상 타임존을 인식하는(aware) datetime을 써야 하며, cron 스케줄은 DST를 반영해 벽시계 시각을 유지하지만 timedelta 스케줄은 시작 시각 이후로는 DST를 보정하지 않는다.

UTC 저장과 Web UI 표시

Airflow는 타임존 지원이 기본으로 켜져 있으며, datetime 정보를 내부적으로 그리고 DB에 항상 UTC로 저장한다. 사용자별 타임존에 맞춰 Dag를 스케줄할 수는 있지만, 현재로서는 UI에서 이를 최종 사용자의 타임존으로 자동 변환해주지 않는다 — UI는 항상 UTC로 표시하며, Operator에서 쓰는 템플릿 값도 변환되지 않는다. 타임존 정보를 실제로 어떻게 활용할지는 Dag 작성자의 몫이다.

단일 타임존에서만 Airflow를 운영하더라도 DB에는 UTC로 저장하는 것이 권장된다. 많은 국가가 서머타임(DST)을 쓰는데, 로컬 시각으로 작업하면 봄가을 전환 시점마다 오류가 발생하기 쉽기 때문이다(사소한 Dag에는 문제가 안 되지만, 금융권처럼 일별 마감 데드라인이 중요한 경우에는 문제가 된다).

타임존은 ``airflow.cfg``의 ``[core] default_timezone``으로 설정하며 기본값은 ``utc``(권장)이고 ``system`` 또는 ``Europe/Amsterdam`` 같은 IANA 타임존 이름도 지정할 수 있다. Dag는 워커에서도 평가되므로 이 설정은 모든 Airflow 노드에서 동일해야 한다. Airflow는 pytz보다 더 정확한 ``pendulum``에 의존하며, pendulum은 자체 타임존 DB를 쓰므로(IANA DB보다 갱신이 뜸함) 시스템 DB를 쓰고 싶다면 ``PYTZDATA_TZDATADIR`` 환경변수로 시스템 타임존 DB 경로(예: ``/usr/share/zoneinfo``)를 지정할 수 있다.

Web UI는 기본적으로 UTC로 시각을 표시하지만, 우측 상단 시계 아이콘 메뉴로 표시 타임존을 바꿀 수 있다 — 'Local'은 브라우저 타임존에서 감지되고, 'Server'는 ``[core] default_timezone`` 값을 쓴다. 사용자가 고른 타임존은 브라우저별 LocalStorage에 저장되는 개인화 설정이다.

핵심 포인트

  • Airflow는 모든 datetime을 내부적으로/DB에 UTC로 저장하며, Web UI는 기본 UTC로 표시하되 우측 상단 메뉴로 Local(브라우저 감지)/Server(default_timezone) 표시를 선택할 수 있고 이 선택은 브라우저별 LocalStorage에 저장된다
  • 단일 타임존 운영이어도 UTC 저장이 권장되는 이유는 DST 전환 시점의 로컬 시각 오류를 피하기 위해서이며, [core] default_timezone은 모든 Airflow 노드에서 동일하게 맞춰야 한다

Naive와 Aware Datetime

파이썬 ``datetime.datetime`` 객체는 타임존 정보를 담는 ``tzinfo`` 속성을 가질 수 있다. 이 속성이 오프셋을 설명하도록 설정되어 있으면 그 datetime은 'aware'(타임존 인식)이고, 그렇지 않으면 'naive'다. ``timezone.is_localized()``와 ``timezone.is_naive()``로 어느 쪽인지 판별할 수 있다. Airflow는 전체적으로 타임존 인식 datetime을 쓰므로, 사용자 코드에서 datetime을 만든다면 그것도 aware여야 한다(``timezone.utcnow()``, ``timezone.datetime(...)`` — ``airflow.sdk.timezone``에서 제공).

Airflow는 하위 호환을 위해 Dag 정의의 ``start_date``/``end_date``에 naive datetime도 받아들인다. naive 값이 들어오면 기본 타임존이 적용되는데, 이때는 그 naive datetime이 '이미 기본 타임존 기준'이라고 가정한다 — 예를 들어 기본 타임존이 ``Europe/Amsterdam``이고 naive ``datetime(2017, 1, 1)``을 start_date로 주면, 이는 2017년 1월 1일 암스테르담 시각으로 해석된다.

DST 전환 시점에는 일부 시각이 존재하지 않거나 모호할 수 있는데, 이런 경우 pendulum은 예외를 던진다 — 그래서 타임존 지원이 켜져 있을 때는 항상 aware datetime을 만들어야 한다. 다만 실무에서는 이 문제가 잘 발생하지 않는다. Airflow가 모델과 Dag에서 이미 aware datetime을 넘겨주고, 새로운 datetime 대부분은 기존 aware 값에 timedelta 산술을 적용해 만들어지기 때문이다. 애플리케이션 코드에서 자주 만드는 유일한 '진짜 새 datetime'은 현재 시각인데, ``timezone.utcnow()``가 이를 자동으로 올바르게 처리해준다.

핵심 포인트

  • tzinfo가 오프셋을 담고 있으면 aware, 없으면 naive datetime이며, Airflow는 전체적으로 aware datetime을 쓰므로 사용자 코드도 이를 따라야 한다
  • naive start_date/end_date는 하위 호환을 위해 허용되지만 '이미 default_timezone 기준'이라고 가정해 해석되며, DST 전환 구간의 naive datetime은 존재하지 않거나 모호해 pendulum이 예외를 던질 수 있다

Dag 타임존 지정과 스케줄별 DST 동작

타임존을 인식하는 Dag를 만드는 것은 간단하다 — ``pendulum``으로 타임존 인식 ``start_date``를 주기만 하면 된다. 표준 라이브러리의 ``timezone`` 객체는 알려진 한계 때문에 Dag에서 의도적으로 금지되어 있다.

import pendulum

dag = DAG("my_tz_dag", start_date=pendulum.datetime(2016, 1, 1, tz="Europe/Amsterdam"))

태스크에도 개별 ``start_date``/``end_date``를 줄 수는 있지만, data interval 계산에는 항상 Dag의 타임존(또는 전역 타임존)이 쓰인다 — 처음 접할 때 그 값을 연관된 타임존으로 UTC 변환한 뒤에는, 이후 계산에서는 타임존 정보 자체를 버린다. 템플릿에서는 Airflow가 타임존 인식 datetime을 그대로 돌려주지만 로컬 시각으로 변환하지는 않으므로(계속 UTC로 남음), Dag가 직접 ``local_tz.convert(logical_date)`` 같은 방식으로 처리해야 한다. 가능하면 UTC로 Dag를 작성하는 것이 권장되며, 타임존 인식 Dag를 쓴다면 pendulum 같은 타임존 라이브러리가 최신 규정 변경(서머타임 정책 변경 등)을 반영하도록 최신 상태로 유지해야 한다.

DST 처리 방식은 스케줄 종류에 따라 다르다.

* cron 스케줄은 서머타임을 반영한다. 예를 들어 ``US/Eastern`` 타임존의 Dag가 ``0 0 * * *``로 스케줄되어 있으면, 서머타임 기간에는 UTC 04:00에, 아닌 기간에는 UTC 05:00에 매일 실행된다 — 즉 로컬 벽시계 자정을 계속 유지한다. * ``timedelta``/``relativedelta`` 스케줄은 시작 시각에 대해서는 서머타임을 반영하지만, 이후 실행들을 스케줄할 때는 서머타임을 보정하지 않는다. 예를 들어 UTC ``pendulum.datetime(2020, 1, 1, tz="UTC")``를 start_date로 하고 ``timedelta(days=1)``로 스케줄된 Dag는 서머타임 여부와 무관하게 항상 고정된 UTC 05:00에 매일 실행된다.

핵심 포인트

  • pendulum으로 타임존 인식 start_date를 주면 Dag가 타임존을 인식하게 되고, 표준 라이브러리 timezone 객체는 한계 때문에 의도적으로 금지되어 있다
  • data interval 계산은 항상 Dag/전역 타임존을 쓰고, 템플릿의 datetime은 UTC로 유지되므로 로컬 변환은 Dag 작성자가 직접 해야 한다
  • cron 스케줄은 로컬 벽시계 시각을 유지하려고 DST를 반영해 UTC 오프셋이 계절에 따라 바뀌지만, timedelta 스케줄은 시작 시각 이후로는 DST를 보정하지 않고 고정된 UTC 간격으로만 실행된다