<에어플로우> 아파치 에어플로우(Airflow)에 대해 알아보기

|
회사에서 태스크를 관리하기 위한 용도로 아파치 에어플로우를 사용했었다. 이 글을 통해 처음부터 시작하며 겪었던 어려움이나 혼동했던 부분들을 공유하고자 한다.

1. 아파치 에어플로우란?

아파치 에어플로우는 Python 기반으로 태스크를 등록하고, 워크플로우를 관리하고, 모니터링하는 도구이다. 간단하게 기능을 요약하자면 다음과 같다.

  • 특정 워크플로우(DAG이라고 한다)를 간편한 파이썬 코드를 통해 등록할 수 있다.
  • DAG 사이의 순서나, 조건부 실행 등 복잡한 플로우를 설계할 수 있다.
  • 또한 Operator를 통해, 다른 프레임워크들과의 통합을 간단하게 해두었다.
  • 등록한 DAG의 스케줄링이 세부적으로 가능하다.
  • 스케일링이 가능하다.
  • 간편한 WebUI를 통해 모니터링과 DAG 관리가 가능하다.

기타 태스크 관리 도구나 단순한 Crontab을 통해 태스크를 관리했던 사람이라면 에어플로우가 가져다주는 이점이 매우 크다는 것을 알 것이다. 일단 최초 세팅이 끝나고 나면, DAG만 기계적으로 추가해주면 되기 때문이다.

하지만, 이 편리함과 더불어 에어플로우를 경험하며 겪게 되는 불편함들도 있는데, 이러한 부분들은 아래에서 다루도록 하겠다.

2. 개념 및 동작 원리 소개

에어플로우에서 주로 쓰이는 개념은 다음과 같다.

DAG

Directed Cyclic Graph의 약자로, 순환하지 않고 방향이 있는 그래프라고 생각하면 된다. 아래 그림은 여기서 따왔다.

01

각 DAG은 에어플로우의 핵심 개념으로써 관련 태스크들을 선언하고, 묶고, 그들간의 흐름을 정리하고, 동작에 관련된 메타 정보를 선언해주는 공간이다.

단일 파이썬 파일 하나가 DAG에 대응되는 개념이며, 해당 파일 내에서 선언해주는 방식으로 진행된다.

자세한 DAG의 선언에 관한 내용은 여기를 읽어보자. 단, 왼쪽 메뉴의 버전을 꼭 확인하자.

Task & Operator

태스크는 DAG 내부에서 실행되는 가장 기본적인 단위이다. 단일 태스크, 또는 태스크들이 모여 DAG을 구성하고, 그들 사이에 의존성이나 실행 규칙 같은 것들을 선언할 수 있다.

태스크의 종류는 크게 세 가지가 있다.

  • Operator: 에어플로우 자체적으로, 또는 프로바이더가 제공해주는 템플릿화된 태스크를 의미한다. 예를 들어 EmailOperator를 사용하면 이메일에 관한 기능을 직접 구현할 필요 없이 쉽게 가져다 쓸 수 있고, MySqlOperator를 사용하면 MySQL에 손쉽게 연결하여 필요한 기능을 쓸 수 있다.
  • Sensor: 오퍼레이터와 비슷하지만, 외부의 이벤트가 발생할 때까지 기다리는 태스크를 의미한다.
  • TaskFlow: 데코레이터 @task 형태로 쓰이며, 사용자 지정 파이썬 함수를 의미한다.

여기서 에어플로우의 편리함이 보이는데, 정말 무수한 종류의 Operator를 통해 편하게 기능 구현을 할 수 있다는 점이다. 해당 목록은 여기서 살펴보자. 카산드라, 드루이드, 하이브, AWS, Azure, GCP 등 수많은 플랫폼에 연결이 가능하다.

동작 원리

hello_world라는 DAG이 있다고 가정하자. DAG은 아래처럼 3개의 태스크로 구성되어 있다. 문법은 고려하지 않고, 그냥 어떤 형태로 되어있는지만 참고하자.

hi = make_hi()
ready_to_say_hi = ready_hi(hi)
say_hi(ready_to_say_hi) 

이때, 에어플로우는 대략적으로 아래와 같은 과정을 거친다.

  1. 스케줄러가 DAG의 선언을 감지하고, 메타DB에 반영한다.
  2. 스케줄러가 해당 시간에 동작해야 하는 hello_world DAG을 발견하고, executor에 전달한다.
  3. executor가 단일 executor로 동작하거나, 분산 처리가 가능할 경우 worker들에게 배분한다.
  4. 최초로 make_hi 태스크를 실행한다. 이때 결과값을 hi라는 변수에 저장하는데, 이를 로컬 머신이 아닌 메타DB에 저장한다. (XCom)
  5. ready_hi가 메타DB에 있는 hi를 다시 가져와서 다음 태스크를 실행한다. 마찬가지로 결과를 또 저장한다.
  6. 똑같은 원리로 say_hi를 실행한다.
  7. 실행 결과를 메타DB에 저장하고, DagRUN을 업데이트한다.

동작에 관한 내용은 이 정도로 하고, 사용하며 겪은 단점과 주의점을 살펴보도록 하겠다. (태스크 관리에 대해 에어플로우만큼의 세분화된 기능을 제공해주는 툴은 사실상 없다고 보기 때문에 장점은 따로 다루지 않겠다. 그냥 전반적인 기능이 주는 편리함!)

3. What airflow IS NOT! 에어플로우에 대한 오해

일단 내가 에어플로우에 가지고 있던 잘못된 오해들을 먼저 다루겠다.

1. 에어플로우는 워크 플로우를 분배해주지, 비동기 분산처리를 해주는 시스템이 아니다.

에어플로우를 도입하기 전에는 Celery를 기반으로 자체적인 태스크 시스템을 구축하여 사용하고 있었다. 이때는 자연스럽게 비동기 호출을 통해 태스크를 비동기로 무수히 많이 찍어냈고, 그런 동작을 사용함에 있어 어려움이 없었다.

그런데 에어플로우로 마이그레이션을 하고 나서, 도통 구현하기 어려운 부분들이 있었다. 예를 들어, 특정 스트림 파이프에서 데이터를 퍼내는 작업을 진행할 때 나는 이 파이프를 빠르게 비워내기 위해 비동기로 여러 워커를 돌리고 싶었다. 예를 들어 데이터가 100덩어리가 있으면 1덩어리씩 넘어가면서 비동기 태스크를 100번 호출하는 식으로 구현을 하려 했는데, 이러한 동작이 불가능했다.

그 이유는 에어플로우의 태스크는 미리 정의된 방식대로 동작하기 때문이다. 다이나믹하게 태스크를 선언하는 것은 기술적으로 가능은 하지만, 개별 태스크 관리가 어려워진다. 동적으로 선언을 했을 때, 실패 시 동작은 어떻게 할 것이며 나중에 WebUI에서 로그를 살필 때도 태스크가 사라질 수 있기 때문에 문제가 된다.

추가적으로, 에어플로우는 자체적으로 비동기 실행을 지원하지 않는다. 다른 파이썬 라이브러리를 통해서는 직접 구현해야만 한다. 비동기를 지원하지 않는 이유는 비동기 태스크들이 허용된다면 정기적으로 돌아야 하는 스케줄된 태스크들의 실행에 영향을 줄 수 있기 때문일 것이다. (동작 slot을 차지하여 제 시간에 실행이 안 되는 등..)

2. 에어플로우는 이벤트 기반 동작을 하지 않는다.

에어플로우는 이벤트를 기반으로 동작하지 않는다. 즉, 서버의 역할처럼 대기를 하고 있다가 특정 요청이 들어왔을 때 태스크를 수행하는 용도가 아니다. (어찌어찌 그렇게 구현할 수는 있겠지만)

에어플로우는 철저히 스케줄링을 위한 시스템이다.

단, 내부 태스크에 대한 이벤트 동작은 존재한다. 위에서 간단히 설명한 Sensor는 다른 Dag이나 Task가 종료됨을 감지하여, 종료 이후 해당 Task가 돌도록 설정할 수 있다.

3. 에어플로우는 데이터를 주고받기 적합한 시스템이 아니다.

위에서 설명한 XCom 시스템을 통해 에어플로우는 데이터를 주고받는다. 알아두어야 할 점은, 파이썬에서 하듯이 태스크의 결과값을 변수에 저장하는 행위 자체가 메타DB에 접근하는 일이라는 사실이다.

즉, A 태스크에서 B 태스크로 변수를 넘기면, 이 정보는 로컬 머신에서 전달되는 것이 아닌, 메타DB를 거쳐서 나온다. 따라서 변수 자체가 이러한 트랜잭션을 늘리는 행위이고, 더 나아가 큰 데이터를 주고받는 순간 에어플로우 자체에서 통신 제한에 걸리거나 그 외 성능 저하 이슈를 겪을 수 있다.

이는 애초에 워커가 분산되어있는 시스템을 가정하고 있기 때문에 데이터의 중재소로써 메타DB를 사용하기 때문이다.

4. 에어플로우의 단점

이번에는 에어플로우의 단점을 살펴보자.

1. 잔버그가 많다. 불친절하다.

에어플로우 사이트에 들어가면 상당히 문서화가 잘 되어 있음을 알 수 있다. 하지만 동시에, 에어플로우를 깊게 파다보면 문서화가 드문드문 되어있고 버전에 잘 맞지 않는 부분들이 있다는 것도 알 수 있다.

더 나아가, 에어플로우를 사용하다보면 알 수 없는 DAGRUN 실패나 메타DB 관련 버그가 종종 일어난다. 특히 메타DB 관련 버그는 고치기도 어렵기 때문에 초기화가 가능한 상태면 그냥 초기화하곤 했다.

물론 에어플로우에 대한 이해가 부족하여 버그를 더 많이 겪은 걸 수 있지만, 에어플로우 깃헙을 들어가보면 2.x 이상의 버전에서 에러 케이스가 많고, 현재 알려진 버그도 상당히 많다.

2. 워커간에 코드 동기화가 번거롭다.

에어플로우 자체적으로 워커 A와 B 사이의 코드를 동기화해주는 기능이 없다. 따라서 깃이나 기타 배포 서비스를 통해 두 워커의 DAG 코드를 일치시켜주어야 한다.

3. (놀랍게도) 로그 관리를 직접 해주어야 한다.

에어플로우는 크게 두 종류의 로그를 만들어낸다.

  1. 메타DB에 쌓이는 로그
  2. 로컬 머신에 쌓이는 로그

정말 놀랍게도, 이 두 로그는 자동으로 로테이트되지 않는다. 기간이 지나면 자동으로 삭제되지 않고, 사용자가 직접 제거해주어야 한다.

1번의 메타DB는 airflow-maintenance-dags라는 커스텀 DAG을 사용하면 되고, 2번은 logrotate 와 같은 툴을 사용하면 된다.

로그를 제거하지 않으면 용량이 터지는 경우가 무조건 발생하기 때문에 꼭 사전에 세팅해주고, 잘 제거가 되는지 확인해주자.

4. 태스크가 많아지면 메타DB와 WebUI가 버벅거린다.

예전에 고객사별로 태스크를 관리할 필요가 있었는데, 그렇게되면 개별 DagRUN에 200~300개의 태스크가 생기곤 했었다. 이 경우, 돌고있는 수만큼 태스크가 메타DB에 연결하기 때문에 메타DB의 부하가 커지고, 이를 UI로 확인하려고 해도 WebUI도 버벅거리게 된다.

이는 단점이라기보다도 에어플로우의 사용에 적합하지 않은 경우가 아니었나 생각하지만, 그래도 생각난 김에 적어보았다.

5. 마무리하며

에어플로우는 분명히 좋은 툴이다. 간단한 코드 몇 줄로 태스크 사이의 의존성을 나타낼 수 있고, 스케줄링, 로그까지 남겨주기 때문에 간편하다. 다만 사용함에 있어 꼭 알아야 할 부분들이 잘 전달되어 있지 않은 것도 사실이다.

내가 에어플로우를 처음 접할 때 너무나도 궁금했던 내용들이었는데, 쉽게 정보를 구할 수 없었다. 그동안의 의미있는 삽질을 통해, 구글링을 통해, 에어플로우 슬랙을 통해 알게된 내용들을 이렇게 정리하여 공유할 수 있어 기쁘다. 😙