Airflow 날짜, 왜 이렇게 헷갈릴까

2sky·2026년 2월 12일

AirFlow

목록 보기
8/8

솔직히 말하면 Airflow 날짜는 몇 번을 써도 헷갈린다.

이번에 제대로 당했다. 2월 12일 01:10에 DAG이 실행됐다.

그래서 당연히 2월 11일 데이터를 넣는 줄 알았다.

그런데 DB에 들어간 날짜는 20260210.

하루가 밀려 있었다. 쿼리를 의심했다. 파티션을 의심했다. Spark를 의심했다.

결론은 전부 아니었다. 로그를 다시 봤다.

execution_date    : 2026-02-10 16:10:00+00:00
logical_date      : 2026-02-10 16:10:00+00:00
ds_nodash         : 20260210
data_interval_end : 2026-02-11 16:10:00+00:00

그 순간 깨달았다. 아, 이거 UTC구나.


내가 착각하고 있었던 것

나는 이렇게 생각했다.

“12일 새벽 1시 10분에 돌았으니까

11일 데이터를 처리하겠지.”

이 생각 자체가 잘못이었다.

Airflow는 실행 시각 기준으로 실행되지 않는다.

Airflow가 보는 것은 단 하나다.

이 DAG Run이 책임지는 시간 구간은 무엇인가?

Airflow는 모든 timestamp를 내부적으로 UTC로 저장한다.

하지만 데이터 interval은 DAG에 설정한 timezone 기준으로 계산된다.

찾아보니 이런 방식이였다

항목의미기준
execution_dateDAG 실행 타임스탬프alias, UTC
logical_dateinterval 시작 시점UTC
data_interval_start처리 interval 시작UTC
data_interval_end처리 interval 끝UTC
ds_nodashlogical_date 문자열UTC

DEFAULT_TIMEZONE을 믿으면 안 되는 이유

나는 이렇게 설정해두었다.

AIRFLOW__CORE__DEFAULT_TIMEZONE=Asia/Seoul

그래서 당연히 KST 기준으로 스케줄이 계산된다고 생각했다.

아니였다. 정확히 말하면

  • Airflow는 모든 시간을 UTC로 저장하지만, 스케줄 계산은 DAG에 설정한 timezone 기준으로 수행된다.
  • logical_date는 timezone-aware 객체이며, 내부적으로 UTC로 저장된다.
  • DEFAULT_TIMEZONE은 UI 표시 및 naive datetime 해석에만 영향
  • DAG에 timezone을 명시하면 해당 timezone 기준으로 interval이 계산된다. 다만 내부 저장은 항상 UTC로 변환된다.

core.default_timezone은 DAG에 timezone을 명시하지 않았을 때 적용되는 기본 timezone이다.

UI 표시만 바꾸는 옵션은 아니며, DAG에 timezone을 지정하지 않으면 이 값을 기준으로 스케줄이 계산된다.


문제의 핵심: ds_nodash

내가 이렇게 썼다.

op_kwargs={"target_date":"{{ ds_nodash }}" }

여기서 사고가 터졌다.

ds_nodash는 무엇인가?

logical_date 기반 문자열

→ 그리고 logical_date는 UTC

12일 01:10 KST 실행

= 11일 16:10 UTC

그리고 중요한 사실 하나:

logical_date“이 DAG Run이 책임지는 구간의 시작 시점”이다.

그래서 결과는 이렇게 된다.

ds_nodash =20260210

하루가 밀린 게 아니다. 내가 Airflow의 시간 모델을 잘못 이해한 거였다.


Airflow 스케줄을 정확히 뜯어보면

DAG:

schedule ="10 1 * * *"
timezone = Asia/Seoul

12일 01:10 KST 실행된 DAG의 내부 값:

logical_date        = 2026-02-1016:10:00+00:00
data_interval_start = 2026-02-1016:10:00+00:00
data_interval_end   = 2026-02-1116:10:00+00:00

이를 KST로 변환하면:

data_interval_start  = 2026-02-1101:10 
KSTdata_interval_end = 2026-02-1201:10 KST

즉 이 DAG Run은

11일 01:10 ~ 12일 01:10 구간을 책임진다.

여기서 핵심:

Airflow는

“지금 실행한다”가 아니라

“이 구간을 마감한다”라는 개념이다.


실행 시간 != 처리 날짜

이게 진짜 함정이다. DAG 실행 시간은 데이터 처리 날짜가 아니다.

Airflow의 사고방식은 항상 이 구조다.

[data_interval_start ~ data_interval_end)

구간 중심 사고다. 시간 중심 사고로 접근하면 계속 하루 밀린다.


내가 고친 방식

이제 ds_nodash를 거의 쓰지 않는다.

interval 기준으로 명시적으로 계산한다.

target_date = (
    context["data_interval_end"]
    .in_timezone("Asia/Seoul")
    .strftime("%Y%m%d")
)

전날 데이터가 필요하면:

target_date = (
    context["data_interval_end"]
    .in_timezone("Asia/Seoul")
    .subtract(days=1)
    .strftime("%Y%m%d")
)

이제 날짜 계산을 코드로 명시한다.

암묵적 추론을 하지 않는다.


재발 방지 설계

이 이슈 이후 바꾼 것:

  • ds_nodash 사용 최소화
  • 날짜 계산은 Python 함수 내부에서 통일
  • 모든 DAG에서 interval 기반 날짜 계산
  • execution_date, logical_date, data_interval_end를 항상 로그에 출력
  • UTC와 KST를 혼용하지 않음 (명시적 변환)

이제 날짜가 밀리는 일은 없다.

추가로 Airflow 2.x에서는 execution_datelogical_date의 alias이며,

실제 처리 구간은 data_interval_start ~ data_interval_end로 정의된다.


이번에 느낀 것

Airflow는 친절하지 않다.. 특히 날짜 관련해서는

“네가 정확히 이해하고 써”라는 시스템같다.

DEFAULT_TIMEZONE만 믿고 있으면 언젠가 반드시 한 번은 틀린다.

그래서 이번 기회를 통해 알아간다!


정리

  • Airflow는 내부적으로 모든 시간을 UTC로 저장한다. 하지만 스케줄 계산은 DAG에 설정된 timezone 기준으로 수행된다.
  • logical_date는 실행 시각이 아니라 책임 구간의 시작이다.
  • ds_nodash는 단순 문자열 매크로일 뿐이다.
  • 데이터 처리는 execution time이 아니라 interval 기준으로 설계해야 한다.
  • 한국 서비스라면 KST 변환을 명시적으로 처리해야 한다.

한 줄 결론

Airflow 날짜는

“지금 몇 시에 돌았는가”가 아니라

“어떤 구간을 마감하는가”를 이해해야 풀린다.

profile
하늘하늘한 하늘

0개의 댓글