23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
저만 그런진 모르겠지만 저는 처음에 Dag 가 파싱되는 것을 최적화 한다는 것이 어떤 것인지 이해가 어려웠습니다.
그래서 거기서 부터 설명을 하면 좋을 것 같아서 끄적여보았습니다.
Dag 파싱은 Airflow의 dag-processor(2.x는 scheduler 내부에서 진행됩니다.)가 주기적으로 Dag 폴더의 모든 .py 파일을 import 해서 Dag 객체를 만들고,
직렬화해서 메타DB(serialized_dag 테이블)에 저장하는 과정입니다.
사용하시는 세팅값마다 다르겠지만 기본적으로는 아래와 같이 세팅되어 있습니다.
min_file_process_interval (기본 30s) 마다 각 파일을 다시 파싱
dag_dir_list_interval (기본 5분) 마다 파일 목록 재스캔
즉, Dag 파일은 한 번 실행되는 게 아니라 수십 초마다 반복 import 된다고 보시면 됩니다.
그래서 파싱이 느리면 아래와 같은 문제들이 생깁니다.
트리거 -> 실행 지연: Asset 트리거하거나 schedule이 와도, 파서가 한 사이클 돌 때까지 task가 안 뜸
scheduler heartbeat 지연: 결국 task slot 회수도 늦어짐
CPU/메모리 폭증
dag_processing.total_parse_time 메트릭이 interval보다 길면 "파서가 못 따라잡는" 상태
비싼 CPU/메모리 위에서 돌아가는 만큼, 체크해보고 정리할 가치가 있습니다.
먼저 무엇이 진짜 모범 사례인지 그래도 공식 문서에 나와있습니다.
https://airflow.apache.org/docs/apache-airflow/stable/best-practices.html
표로 보면,
레이어 | 핵심 권고 |
|---|---|
코드 | Top-level 에서 DB/API/파일 I/O 금지. |
파일/디렉터리 |
|
인프라 (3.x) | dag-processor standalone 서비스 사용. |
회귀 방지 | Airflow 3 마이그레이션 위생은 Ruff |
dag-processor: 2.3 에서 optional 도입 (standalone_dag_processor=True) -> 3.0 에서 mandatory. 더 이상 scheduler 내부에서 안 돕니다.
설정 섹션 이동: 파싱 관련 설정이 [scheduler] -> [dag_processor] 로 이동. 옛 섹션에 두면 silent 하게 무시.
.airflowignore 기본 syntax: 2.x regex -> 3.x glob. 가장 비싼 silent breaking change (뒤에서 자세히).
dag_dir_list_interval deprecated: 2.x 잔재.
막연히 "느린 것 같다" 가 아니라, 어디서부터 어떻게 봐야 하는지 순서대로 정리해봐야합니다.
직감 대신 메트릭부터. Airflow 3.x dag-processor 는 StatsD / OpenTelemetry 로 다음 메트릭을 emit 합니다:
dag_processing.total_parse_time: 가장 중요. 한 사이클 전체 시간
dag_processing.last_duration.<filename>: 파일별 마지막 파싱 시간 --- 무거운 파일 식별용
dag_processing.last_runtime.<filename>: 파일별 last run 경과
dag_processing.processes: 활성 child process 수
dag_processing.processor_timeouts: timeout 카운트 (cycle 평균 왜곡 원인)
메트릭 인프라가 없으면 임시로 dag-processor 로그의 file processing timestamp 를 추출해서 cycle 을 근사할 수도 있습니다 (정확하진 않습니다).
제 상황을 실측해보면...
"timestamp":"2026-05-18T03:47:31.967Z"
"timestamp":"2026-05-18T03:50:03.596Z" (+152s)
"timestamp":"2026-05-18T03:52:32.280Z" (+149s)
"timestamp":"2026-05-18T03:55:07.587Z" (+155s)
...
평균: ~155s설정값 min_file_process_interval=30s 와 ~5배 격차.
여기서 한 가지 중요한 사실은,
min_file_process_interval 은 "이 파일을 마지막 파싱 후 N초가 지나야 재대상에 들어간다" 는 lower bound 이지 cycle 보장이 아닙니다.
cycle 이 155s 면 모든 파일이 30s 를 훨씬 초과해 매번 재파싱 큐에 즉시 재진입 -> 30s 설정은 사실상 무의미한 상태가 됩니다.
코드를 의심하기 전에 인프라 한 줄을 먼저 의심하세요. 가장 큰 ROI 가 여기 있는 경우가 많습니다.
# 3.x 는 [dag_processor] 섹션
airflow config get-value dag_processor parsing_processes
airflow config get-value dag_processor min_file_process_interval
airflow config get-value dag_processor file_parsing_sort_modeparsing_processes: 병렬 파서 수. 기본 1. CPU 코어 / 평균 파일 파싱 시간 / 메모리 마진 보고 조정해야합니다.
min_file_process_interval: 기본 30s. cycle 이 이보다 길면 의미 없음.
file_parsing_sort_mode: 파싱 우선순위. modified_time를 권장합니다.(최근 변경 파일 먼저)
저도 parsing_processes 를 조금 늘려줬습니다.
10:07:57 → 10:09:05 68s
10:09:05 → 10:10:20 75s
10:10:20 → 10:11:29 69s
...
평균: ~72s (표준편차 ~4s)훨씬 나아졌네요.
parsing_pre_import_modules (3.x 신규)여기서 많이 놓칩니다.
3.x 에 추가된 parsing_pre_import_modules 는 dag-processor manager 가 시작할 때 명시한 모듈을 한 번 import 해두고,
child process fork 시 copy-on-write 로 재사용합니다. 모든 Dag가 공통으로 import 하는 패키지가 있다면 직격탄이 됩니다.
# airflow.cfg
[dag_processor]
parsing_pre_import_modules = ...이런 식으로 모듈을 넣어줄 수 있습니다.
그러면 child 마다 매번 import 하면 N × import 비용이지만, pre-import 하면 1회로 끝.
.airflowignore 와 silent breaking change코드와 인프라 다음은 파일 디스커버리. 각 번들에 .airflowignore 가 잘 잡혀 있는지:
for d in <여러분들의 번들들>; do
echo "--- $d ---"
cat $d/dags/.airflowignore 2>/dev/null || echo "(NONE)"
donedag_discovery_safe_mode 만 믿지 말기"흔히 "dag_discovery_safe_mode=True 면 'airflow'/'DAG' 키워드 없는 파일은 자동 스킵된다" 고 알려져 있죠.
하지만 utility 파일들이 대부분 from airflow.* import ... 를 포함하므로 키워드 매칭에 걸려 자동 스킵을 우회합니다. 명시 제외가 안전합니다.
.airflowignore 했는데 안 줄어든다?각 번들에 .airflowignore 를 일괄 작성해서 머지하고 cycle 재측정. 그런데...
12:00:39 → 12:01:46 67s
12:01:46 → 12:03:01 75s
12:03:01 → 12:04:04 63s헬퍼 파일이 빠졌으면 분명 더 줄어야 하는데.
제 유틸 파일의 dag-processor 로그 timestamp 가 여전히 60 초마다 갱신되고 있었습니다. 파싱이 계속 되고 있다는 뜻이죠.
.airflowignore glob default직접 Airflow 의 매칭 함수를 호출해서 검증해봤습니다.
from airflow._shared.module_loading import find_path_from_directory
help(find_path_from_directory)
# find_path_from_directory(base_dir_path, ignore_file_name,
# ignore_file_syntax: str = 'glob') -> Generator
# ^^^^^^^^^^^^^^^^^^Airflow 3.x 에서 .airflowignore 기본 syntax 가 glob 으로 변경됐습니다 (2.x 는 regex).
제가 작성한 패턴은 모두 regex 형태 (^_signal_utils\.py$) 라서 glob 으로 해석되면 매칭 0 건. 에러도 안 납니다. 그냥 silent 하게 무시되는 거죠.
실측 비교:
# regexp 모드
list(find_path_from_directory(d, '.airflowignore', ignore_file_syntax='regexp'))
# glob 모드
list(find_path_from_directory(d, '.airflowignore', ignore_file_syntax='glob'))여기는 신규 PR 마다 잡으면 누적되지 않는 영역이지만, 한 번은 전수 점검할 가치가 있습니다.
# top-level Variable.get / Connection.get
rg "^(Variable\.get|Connection\.get)" */dags/
# top-level DB 클라이언트 인스턴스화
rg "^(PostgresClient|KISClient)\(" */dags/
# 동적 Dag 생성
rg "for .+ in .+:" */dags/ | rg "DAG\("이건 CI에서 그래도 칼같이 잡아왔던 터라 모두 양호했습니다.
이런 코드레벨 안티패턴들은 pre-commit을 통해서 커밋하기 전에 미리 잡으면 편하게 관리할 수 있습니다.
"Dag 파싱 느려요" 면 보통 코드부터 의심하는데, 이미 코드 위생이 괜찮은 환경에서는 인프라 탓일 수 있습니다.
측정 없이 직감으로 갔으면 며칠 헛수고할 수도 있습니다 🥲
그리고 Airflow 3.x에서의 silent change 들을 알고 있어야할 것 같네요.
다른 분들도 Dag 파싱 최적화 하실 때 체크했던 것들이 있으시면 알려주시면 감사드릴 것 같습니다 😉
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.