Airflow 파이프라인에서 created_at 컬럼에
datetime.now(KST)를 사용했음에도 불구하고
PostgreSQL 테이블에서는 시간이 UTC처럼 9시간 빠르게 저장되는 문제를 겪었다.
import json
from airflow.providers.postgres.hooks.postgres import PostgresHook
from datetime import datetime, timedelta, timezone
KST = timezone(timedelta(hours=9))
def load_raw_starwars(**context):
ti = context['ti']
data = ti.xcom_pull(task_ids='validate_starwars')
execution_date = context["execution_date"]
hook = PostgresHook(postgres_conn_id='my_postgres_connection')
hook.run(
"""
INSERT INTO raw.raw_starwars (ds, request_hour,request_minute, payload, created_at)
VALUES (%s, %s, %s, %s::jsonb, %s)
""",
parameters=(
execution_date.date(),
execution_date.hour,
execution_date.minute,
json.dumps(data),
datetime.now(KST),
),
)
return data
처음에는 Airflow 실행 시간(execution_date)이나
DB 세션 타임존 설정(Asia/Seoul) 문제를 의심했다.
하지만 세션 타임존을 확인해도 결과는 동일했다.\

문제의 핵심은 PostgreSQL 컬럼 타입이었다.
created_at 컬럼이 TIMESTAMP WITHOUT TIME ZONE으로 정의되어 있었고,
이 타입은 타임존 정보를 저장하지 않는다.
따라서 Python에서 timezone-aware datetime(datetime.now(KST))을 전달해도
PostgreSQL에 적재되는 순간 타임존 정보가 제거되며
클라이언트에서는 UTC처럼 보이게 된다.
해결 방법은 컬럼 타입을 TIMESTAMPTZ로 변경하는 것이었다.
이 타입은 내부적으로 UTC로 저장하고,
조회 시 세션 타임존(Asia/Seoul) 기준으로 변환해 보여준다.
ALTER TABLE raw.raw_starwars
ALTER COLUMN created_at TYPE TIMESTAMPTZ;
컬럼 타입 변경 이후에는
KST 기준 시간으로 정상적으로 저장·조회되는 것을 확인했다.

정리하면
PostgreSQL에서 KST 시간을 정확히 다루려면
datetime.now(KST) + TIMESTAMPTZ 조합이 필수다.
'컨테이너·워크플로우 자동화 > Airflow로 워크플로우 자동화하기' 카테고리의 다른 글
| Airflow PythonOperator에서 params로 값을 전달하고 함수에서 사용하는 방법 (0) | 2026.02.03 |
|---|---|
| trigger_rule="all_done" 이란 무엇인가 (0) | 2026.02.03 |
| [트러블슈팅] Airflow에서 pipeline 패키지 인식하지 못했던 이유와 해결 과정 (0) | 2026.01.23 |
| [트러블슈팅] BranchOperator 이후 Task가 Skipped 되는 이유와 trigger_rule (1) | 2026.01.22 |
| [트러블슈팅] Airflow FileSensor 실행 실패: fs_default Connection이 없을 때 (0) | 2026.01.21 |