데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Apache Airflow 3.x Dag Parsing 최적화 체크리스트

      추영욱 26.05.26
      2,121 4 2
      DEVOTEE 요약
      Airflow DAG 파싱은 DAG 파일을 주기적으로 import하여 객체를 생성하고 직렬화하는 과정으로, 이 과정이 느려지면 태스크 실행 지연, 스케줄러 문제, CPU/메모리 폭증 등 심각한 성능 저하를 초래합니다. 최적화를 위해서는 먼저 `dag_processing.total_parse_time` 같은 메트릭으로 현황을 파악하고, `parsing_processes` 증대, Airflow 3.x의 `parsing_pre_import_modules` 활용 등 인프라 설정을 튜닝해야 합니다. 또한 `top-level` 코드 안티패턴을 제거하고, 불필요한 파일은 `.airflowignore`로 명시적 제외하되, Airflow 3.x에서는 `glob` 문법으로 변경된 점을 유의해야 합니다.

      Dag 파싱을 최적화 한다는 것?

      저만 그런진 모르겠지만 저는 처음에 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 금지. Variable.get() top-level 금지 -> Jinja {{ var.value.X }}. 무거운 import (pandas, torch) 는 task 함수 내부로

      파일/디렉터리

      .airflowignore 로 비-Dag 파일 명시 제외. 1 파일 1 Dag 원칙 (FileProcessor 병렬화)

      인프라 (3.x)

      dag-processor standalone 서비스 사용. [dag_processor] 섹션의 parsing_processes, min_file_process_interval, file_parsing_sort_mode 튜닝. 3.x 신규 parsing_pre_import_modules 로 공통 모듈 pre-load

      회귀 방지

      Airflow 3 마이그레이션 위생은 Ruff AIR3 그룹 + 파싱 안티패턴 (top-level Variable.get 등) 은 별도 커스텀 lint


      자주 헷갈리는 3.x 변경 사항 4가지

      1. dag-processor: 2.3 에서 optional 도입 (standalone_dag_processor=True) -> 3.0 에서 mandatory. 더 이상 scheduler 내부에서 안 돕니다.

      2. 설정 섹션 이동: 파싱 관련 설정이 [scheduler] -> [dag_processor] 로 이동. 옛 섹션에 두면 silent 하게 무시.

      3. .airflowignore 기본 syntax: 2.x regex -> 3.x glob. 가장 비싼 silent breaking change (뒤에서 자세히).

      4. 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_mode
      • parsing_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)

      훨씬 나아졌네요.


      공통 import 비용 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)"
      done

      "dag_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 파싱 최적화 하실 때 체크했던 것들이 있으시면 알려주시면 감사드릴 것 같습니다 😉

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      추영욱 님의 최신 블로그

      더보기
      동영상 기고하기