Airflow DAG 서버 이관하기

한빈수윤·2025년 9월 9일

파이프라인

목록 보기
3/3

그냥 DAG만 옮긴 게 아니라, 왜 이렇게 돌아가는지를 이해하려고 일부러 돌아갔다 오기도 했다. 노가다였지만, 그 과정에서 에어플로우가 뭘 중요하게 보는지 몸으로 배웠다.


내가 처음 헷갈렸던 것들

  • 스케줄 vs 실행 데이터
    schedule이 “언제 돌릴지”만 말하는 줄 알았는데, 실제로는 그 시간 구간의 데이터를 대표하는 실행을 만든다는 걸 뒤늦게 이해했다. {{ ds }} 하나로 때우던 걸 {{ data_interval_start }} / {{ data_interval_end }}로 바꾸니 백필·재실행이 덜 꼬였다.
  • start_date와 catchup
    start_date를 과거로 두면 첫 배포 날에 과거 전부가 한꺼번에 달려드는 걸 실제로 봤다. 운영에선 기본을 catchup=False로 뒀고, 백필할 땐 범위를 정해서 backfill로 별도 처리했다.
  • UTC/KST 착시
    Airflow 기본이 UTC라 KST에서 하루 밀리는 사고를 한 번 냈다. DAG마다 tz="Asia/Seoul"을 명시했고, 파이썬 내부 시간 계산도 전부 펜듈럼으로 통일했다. 이걸 안 하면 리포트가 “어제자”로 들어간다.

의존성 설계에서 배운 것

  • 팬인(Fan-in)이 필요한 이유
    같은 리포트가 트리상 여러 갈래를 종합해야 하는 케이스가 있었다. 처음엔 두 군데에서 같은 테이블을 각자 만들게 했다가 덮어쓰기가 났다. 결국 단일 태스크가 두 부모를 기다리는 구조로 바꿨다. 부분 집계가 들어가면 그날 데이터는 끝까지 삐걱댔다.
  • 중복 task_id 금지의 의미
    “같은 테이블이면 같은 task_id”라고 단순화했다가, 트리 두 곳에 같은 태스크가 나타나는 순간 Airflow가 바로 막았다. 이후로는 태스크는 유일, 데이터 통합은 의존성으로 조립하는 방향으로 고정했다.
  • 아이템포턴시(멱등성)
    재시도로 인서트가 두 번 들어가는 걸 실제로 겪었다. 그 뒤로는 MERGE·REPLACE·파티션 단위 삭제 후 적재 중 하나로 일관되게 정했다. DAG가 실패해도 데이터가 중복되지 않게 만드는 게 제일 중요했다.

팩토리 패턴을 쓰면서 느낀 점

  • 장점: 선언형 메타데이터만 바꾸면 DAG가 나온다. 스케줄·시작일·태스크 경로·의존성까지 한 곳에서 관리됐다. 대규모 이관에선 이게 체력 아꼈다.
  • 단점: 추상화가 한 번 삐끗하면 전 DAG에 같은 버그가 복제된다. 특히 경로 오타, 매크로 인자 규격 미스매치 같은 게 그렇다. 결과적으로 “메타데이터 생성” 자체를 린트/테스트하는 스텝을 따로 만들었다.

찾아보면서 알게 된 팁이랄까

  • Retry는 ‘재시도해도 되는가’부터
    5xx/429는 재시도, 4xx는 바로 실패. exponential_backoff를 켰고, 외부 API엔 풀(pool) 로 동시 호출을 제한했다.
  • 알림은 실패뿐 아니라 ‘지연’도
    태스크가 실패하지 않았는데 오래 걸리는 상황도 있었다. SLA를 달고, on_failure_callback 외에 SLA miss 알림을 추가했다.
  • 리소스 상한
    max_active_runs=1, DAG·태스크 concurrency를 낮춰서 스파이크 때 워커를 지키는 게 낫다고 느꼈다. 한 번 터지면 큐가 눈덩이처럼 불었다.
  • 변수/비밀 관리
    키를 Variables에 두는 실수를 했다가 바로 Connections/Secret Backend로 옮겼다. 파이썬 코드에 비밀이 섞이면 PR 리뷰가 불편해진다.

테스트 시도

  • airflow tasks test로 단일 태스크부터
    리소스 적게 쓰고 실패 지점을 바로 본다. 외부 호출이 있으면 모킹해서 함수 단위 테스트도 같이 돌렸다.
  • 백필은 좁게
    한 달 백필을 한 번에 걸었다가 워커가 난리가 났다.. 이후로는 3~5일 단위로, 야간에 나눠서 넣었다.
  • BashOperator 남발 줄이기
    로그가 산개되고, 리턴 값 전달이 불편했다. 가능하면 PythonOperator/TaskFlow로 바꾸니까 디버깅이 쉬워졌다. 그래도 간단한 래퍼 스크립트는 Bash가 빠르긴 했다.

스키마 드리프트, 결국 탐지로 풀었다

API가 조용히 컬럼을 바꿨다. 처음엔 쿼리가 터지고 나서 알았다. 이후로는 적재 전 스키마 비교를 붙였다. 차이가 나면 바로 슬랙으로 알렸다. “깨지고 알기”에서 “바뀌면 알기”로 바꿨다.


처음엔 “DAG 옮기는 단순 작업”이라고 생각했다가, 의외로 많은 것을 배울 수 있었다.
스케줄, 의존성, 타임존, 멱등성, 리소스 상한, 알림… 이런 기본기가 맞아야 파이프라인이 오래 갔다.
결국 에어플로우는 “코드로 쓰는 운영 약속”이라는 생각이 들었다. 이관이 끝났고, 다음 번엔 처음부터 이 약속을 더 잘 써보겠다고 다짐했다.

profile
할 수 있다 방돌!!!

0개의 댓글