메트릭 설정: StatsD와 OpenTelemetry
Apache Airflow Official Documentation (in-repo snapshot) — Apache Software Foundation administration-and-deployment/logging-monitoring/metrics.rst (전체)
이 모듈을 다 읽으면
- StatsD와 OpenTelemetry 메트릭 백엔드 설정 방법과 차이를 설명할 수 있다
- 다중 스케줄러 환경에서 서비스 인스턴스를 식별해야 하는 이유를 설명할 수 있다
- allow/block list, 커스텀 메트릭 emit 시 주의할 제약을 판단할 수 있다
Airflow는 StatsD 또는 OpenTelemetry로 메트릭을 내보낼 수 있다. 두 백엔드는 설정 방법뿐 아니라 다중 프로세스(HA 스케줄러 등)를 구분해내는 능력, 태그 지원 여부, 히스토그램 처리 방식에서 근본적인 차이가 있다.
StatsD 설정
StatsD를 쓰려면 먼저 ``pip install 'apache-airflow[statsd]'``로 필요한 패키지를 설치한 뒤, ``airflow.cfg``에 ``[metrics] statsd_on = True``와 함께 ``statsd_host``, ``statsd_port``, ``statsd_prefix``를 설정한다.
UDP 대신 유닉스 도메인 소켓으로 메트릭을 보낼 수도 있다. ``statsd_socket_path``를 설정하면 ``statsd_host``, ``statsd_port``, ``statsd_ipv6``는 무시된다. 표준 StatsD 백엔드는 스트림 유닉스 소켓을 쓰지만, Datadog 백엔드(``statsd_datadog_enabled = True``)를 쓰면 스트림·데이터그램 소켓을 모두 지원하며 ``unix://``, ``unixgram://``, ``unixstream://`` URL도 받아들인다.
커스텀 StatsD 클라이언트를 쓰려면 ``statsd_custom_client_path``에 모듈 경로를 지정한다(PYTHONPATH에 있어야 함). ``statsd_socket_path``가 설정된 경우 커스텀 클라이언트는 ``statsd.UnixSocketStatsClient``를 상속해야 하고, 그렇지 않으면 ``statsd.StatsClient``를 상속해야 한다.
StatsD에는 리소스라는 개념이 없어서, 메트릭을 그것을 생성한 프로세스에 귀속시킬 수 없다. HA로 여러 스케줄러 같은 동일 컴포넌트를 여러 프로세스가 실행하면, 각자 같은 시리즈를 내보내고 서버는 마지막에 도착한 값만 남긴다. 프로세스를 구분하려면 OpenTelemetry를 써야 한다.
핵심 포인트
- StatsD는 statsd_on/host/port/prefix로 설정하며 apache-airflow[statsd] 설치가 필요하다
- statsd_socket_path 설정 시 host/port/ipv6는 무시되고 유닉스 소켓을 사용한다
- StatsD는 리소스 개념이 없어 HA로 여러 프로세스가 같은 메트릭을 내보내면 서버는 마지막 값만 남긴다
OpenTelemetry 설정과 컴포넌트/인스턴스 식별
OpenTelemetry를 쓰려면 ``pip install 'apache-airflow[otel]'``로 설치하고, 메트릭 백엔드와의 연결을 위한 OpenTelemetry Collector(또는 호환 서비스)가 필요하다. ``[metrics] otel_on = True``와 함께 ``otel_host``, ``otel_port``, ``otel_prefix``, ``otel_interval_milliseconds``(기본 60000), ``otel_service``, ``otel_ssl_active``를 설정한다. 이 개별 설정 키들은 표준 OpenTelemetry 환경변수(``OTEL_EXPORTER_OTLP_ENDPOINT`` 등)로 대체될 예정으로 지원 중단(deprecated) 상태다.
OpenTelemetry는 메트릭마다 그것을 생성한 리소스를 라벨링한다. 이때 두 리소스 속성이 배포를 얼마나 세밀하게 구분할 수 있는지를 결정한다. ``service.name``은 어떤 컴포넌트가 메트릭을 보고했는지를 나타내며 기본값이 모든 Airflow 프로세스에 대해 ``airflow``이므로, 컴포넌트별로 구분하려면 직접 설정해야 한다. ``service.instance.id``는 그 컴포넌트의 어떤 프로세스가 보고했는지를 나타내며 기본적으로 비어있어, 스케줄러가 2개 이상이면 서로 구분되지 않는다. Airflow는 ``service.name``을 ``OTEL_SERVICE_NAME``에서, 그 외 리소스 속성을 ``OTEL_RESOURCE_ATTRIBUTES``에서 읽는다(예: 스케줄러마다 ``OTEL_RESOURCE_ATTRIBUTES="service.instance.id=$(hostname)"``).
같은 리소스를 공유하는 프로세스들은 시리즈도 공유하고, 백엔드는 마지막에 도착한 익스포트만 남긴다. 여러 프로세스가 같은 컴포넌트를 실행할 때는 데이터가 집계되는 게 아니라 유실된다. 예를 들어 스케줄러마다 자기 루프에서 메타데이터 DB를 샘플링하므로, ``pool.open_slots`` 같은 게이지는 여러 스케줄러의 값을 합친 게 아니라 임의의 한 스케줄러의 샘플만 보여준다. 각 프로세스를 식별하고 나면 그 샘플들을 의도적으로 조합할 수 있다(예: ``min by (pool_name) (airflow_pool_open_slots)``로 모든 스케줄러가 관측한 값 중 최솟값 구하기).
핵심 포인트
- service.name은 컴포넌트 종류(스케줄러/트리거러/워커 등)를 구분하고, service.instance.id는 같은 컴포넌트의 서로 다른 프로세스를 구분한다
- 두 속성 모두 기본값으로는 구분되지 않아 HA 스케줄러의 메트릭이 서로 덮어써질 수 있다
- OTEL_SERVICE_NAME/OTEL_RESOURCE_ATTRIBUTES 환경변수로 프로세스별 식별자를 심는다
- 식별자를 심지 않으면 pool.open_slots 같은 게이지가 집계가 아니라 임의의 한 프로세스 값만 보여준다
히스토그램 메트릭과 백엔드 요구사항
Airflow의 타이밍 메트릭(``timing()``/``timer()``)은 OpenTelemetry의 지수 버킷 히스토그램(exponential bucket histogram)으로 집계되어, 버킷 경계가 관측된 값의 범위(밀리초에서 몇 시간까지)에 자동으로 맞춰지므로 수동으로 명시적 버킷을 튜닝할 필요가 없다.
이를 엔드투엔드로 제대로 수집하려면 연결하는 메트릭 백엔드가 OpenTelemetry 지수 히스토그램(그리고 Prometheus의 경우 네이티브 히스토그램으로의 변환)을 지원해야 한다. OpenTelemetry Collector는 ``opentelemetry-collector-contrib`` 0.115.0 이상이 필요하며, 그보다 낮은 버전은 OTLP 지수 히스토그램을 Prometheus 네이티브 히스토그램으로 변환하지 못한다. Prometheus는 네이티브 히스토그램을 명시적으로 활성화해야 하는데, 2.40~3.8 버전에서는 ``--enable-feature=native-histograms`` 플래그로 시작해야 하고, 3.8 이상부터는 스크레이프 설정에 ``scrape_native_histograms: true``를 지정해야 한다(3.8에서 추가되었고, 3.9부터는 기능 플래그가 아무 효과가 없는 no-op이 되어 이 설정이 필수가 된다). 백엔드가 네이티브 히스토그램을 지원하지 않으면 지수 히스토그램 데이터 포인트가 누락되거나 잘못 렌더링될 수 있다.
핵심 포인트
- timing()/timer()는 버킷 경계가 자동 조정되는 OTel 지수 히스토그램으로 집계된다
- OTel Collector-contrib 0.115.0 이상, Prometheus 2.40~3.8은 플래그, 3.8 이상은 scrape_native_histograms 설정이 필요하다(3.9부터 플래그는 no-op)
- 백엔드가 네이티브/지수 히스토그램을 지원하지 않으면 데이터가 누락되거나 잘못 표시될 수 있다
Allow/Block 리스트와 메트릭 이름 변경
모든 메트릭을 다 보내고 싶지 않다면 allow list나 block list로 특정 메트릭만 보내거나 막을 수 있다. 각 리스트는 메트릭 이름 어디에나 매칭되는 정규식들을 콤마로 구분한 집합이며(``^``로 앵커링하면 접두어 매칭), 두 리스트가 모두 설정되어 있으면 block list는 무시된다(``[metrics] metrics_allow_list``, ``metrics_block_list``).
메트릭 이름을 바꾸고 싶다면 ``[metrics] stat_name_handler`` 옵션에 이름을 검증하고 필요하면 변환해 반환하는 함수 경로를 지정한다.
핵심 포인트
- metrics_allow_list와 metrics_block_list는 정규식 목록이며, 둘 다 설정되면 block list는 무시된다
- stat_name_handler로 메트릭 이름을 검증/변환하는 커스텀 함수를 지정할 수 있다
커스텀 메트릭 만들기
태스크, 플러그인, 커스텀 오퍼레이터 안에서 Airflow가 내부적으로 쓰는 것과 같은 stats 클라이언트를 통해 자체 메트릭을 낼 수 있다. Airflow 3에서 권장 임포트 경로는 ``airflow.sdk.observability``이며, ``stats.incr``, ``stats.decr``, ``stats.gauge``, ``stats.timing``과 컨텍스트 매니저인 ``stats.timer``를 제공한다(모듈 레벨 함수는 3.3.0에 추가됨; 그 이전 버전은 ``from airflow.sdk.observability.stats import Stats``의 ``Stats`` 클래스를 사용). ``incr``/``decr``/``gauge``/``timing``/``timer``는 백엔드가 지원하는 경우 차원별(dimensional) 메트릭을 위한 ``tags`` 매핑도 받을 수 있다.
태그 지원은 백엔드에 따라 다르다. 클래식 StatsD 프로토콜에는 태그 개념이 없다. OpenTelemetry는 태그를 네이티브 속성으로 보내지만, StatsD는 기본적으로 ``tags`` 매핑을 버린다 — 태그를 라벨로 바꾸려면 ``statsd_influxdb_enabled = True``(InfluxDB ``name,key=value`` 포맷) 또는 ``statsd_datadog_enabled = True``(DogStatsD ``|#key:value`` 포맷) 같은 태그 지원 와이어 포맷을 켜야 하고, Prometheus의 ``statsd_exporter``가 이 포맷들을 읽어 라벨로 바꾼다.
메트릭 이름은 250자 이하여야 하고 ``a-z``, ``A-Z``, ``0-9``, ``_``, ``.``, ``-``, ``/``만 쓸 수 있으며, 잘못된 이름은 로그로 남고 메트릭이 전송되지 않는다. 백엔드가 활성화되어 있지 않으면 이 메트릭들은 조용히 버려진다. 커스텀 메트릭이 보이지 않는다면 ``metrics_allow_list``/``metrics_block_list``도 확인해야 한다 — allow list가 설정되어 있으면 거기 없는 메트릭은 조용히 드롭된다.
핵심 포인트
- Airflow 3.3.0+에서는 airflow.sdk.observability의 stats.incr/gauge/timing/timer 모듈 함수를 쓴다
- StatsD는 기본적으로 tags를 버리며, InfluxDB/DogStatsD 와이어 포맷을 켜야 라벨로 변환된다
- 메트릭 이름은 250자 이하, 제한된 문자셋만 허용되며 위반 시 조용히 드롭된다
- 커스텀 메트릭이 안 보이면 백엔드 활성화 여부와 allow/block list를 먼저 의심해야 한다