Airflow 조그맣게 시작하기 - 각종 트러블슈팅

햄도·2021년 5월 2일
1

Airflow slowstart

목록 보기
3/6

(airflow version 2 기준으로 작성)
튜토리얼은 매우 쉬웠지만 에어플로우를 운영 환경에서 사용하기 위해 도입하는 과정에서 크고 작은 문제들이 있었다. 해결한 문제도 있고 땜빵만 해둔 문제도 있지만 일단 정리해보자.

timezone & scheduling

처음 써보는 입장에서 가장 골때렸던 문제.. DAG를 몇번 올려보고 내려보고 스케줄링 걸어서 돌아가는 모양새도 관찰하고 나서야 이해한 것 같다. 처음에는 왜 이게 안돌지? 왜 이게 갑자기 돌지? 이렇게 하면 돌겠지? 그런데 왜 안돌지? 의 연속이었다.

젠킨스에서는 스케줄링을 당장 5분 후로 걸어두기만 하면 5분 후 바로 배치가 돌았던 것 같다. 에어플로우도 그와 유사한 동작을 기대하고 스케줄링을 걸었는데, 배치는 절대 돌지 않았다. 여기에는 두 가지 문제가 있었다.

1. DAG가 pause 상태였다


DAG를 꺼두고(회색 토글) 이틀정도 배치가 돌아가길 기도했다. DAG를 만들고 등록된 후에는 작동할 수 있도록 꼭 켜두자.

2. 꼬인 start_date

처음 배치를 등록할 때 스케줄링 시간의 timezone은 start_date의 timezone을 따라간다는 글을 읽고, 꼼꼼하게 pendulum을 이용해 timezone을 설정한 후 start_date는 당일(당시 2021년 2월 1일)로 해놓았다.

    KST = pendulum.timezone("Asia/Seoul")
    args = {'owner': 'jaehee'}

    dag  = DAG(dag_id='my_first_dag',
               default_args=args,
               start_date=datetime(2021, 2, 1, tzinfo=KST),
               schedule_interval='0 17 * * *')

시작 일자가 오늘이고, 현재 시간보다 2시간 이후인 오후 5시로 스케줄링을 해놓았으니 당연히 돌아갈거라 생각했다. 하지만 그 배치는 2시간 후 돌지 않았다. 이 현상을 이해하기 위해서는 execution_time에 대해 알아야 한다.

매일 오후 3시 반에 배치를 등록하는 예시를 보자. start_date가 한국 시간으로 2월 1일 오전 10시이고, 스케줄은 30 15 * * * 로 등록한 경우 2월 1일 오후 3시 반에는 배치가 도는게 아니라 배치 주문이 들어간다. (execution_time) 실제로 이 배치가 도는 것은 다음 날인 2월 2일 3시 반이다.

  • tree view에서 각 점에 마우스를 올리면 위와 같은 정보를 확인할 수 있다. 위 dag_run이 스케줄링 된 시간은 run id에 있다. 즉 주문(스케줄링)이 들어간 시간은 utc 기준 1월 31일 오전 6시 30분이고, 실제 수행 시작한 시간은 utc-started에 써져있는 2월 1일 오전 6시 30분이다.

일주일 주기 배치도 똑같다. start_date가 한국 시간으로 2월 1일 오전 10시이고, 스케줄은 0 17 * * 1 로 등록한 경우 2월 1일 오후 5시에는 배치 주문이 들어가고, 다음주인 2월 8일 오후 5시에 execution_time이 2월 1일 오후 5시인 배치가 돌아간다.

여기에서 execution_time은 각 DAG 수행내역의 pk와 같은 역할을 한다. 로그도 따로 설정을 하지 않는 이상 execution_time 기준으로 쌓이고, UI에서 조회할 수 있는 각종 차트도 execution_date 기준으로 데이터를 보여준다.

dag는 start_date로부터 스케줄링 주기가 한 차례 지난 후 수행된다는 친절한 설명도 FAQ에 있다.

이런 동작방식때문에 배치를 등록하고, 스케줄링대로 돌아가는것을 오늘 바로 보고싶다면 start_date를 오늘로부터 수행주기만큼 전으로 등록해야 한다. 이 사실을 뒤늦게 알고 start_date를 바꿔보았지만, start_date는 한번 등록되면 dag 파일에서 수정한다고 해도 업데이트되지 않기 때문에 배치는 영영 동작하지 않았고 DAG를 삭제한 후 새로 등록하고 나서야 스케줄링이 완료되었다.

한가지 더 헷갈렸던건, manual 실행이 스케줄된 배치 수행에 영향을 미치는지였다. 이 부분은 수행 로그를 관찰하고 알 수 있었다.

위처럼 하루 주기인 배치가 당일 수행되어도 스케줄링 시간에 수행중이지 않으면 스케줄된 배치도 수행된다.

하지만 스케줄된 시간에도 배치가 수행중이면 매뉴얼로 돌린 배치만 수행된다.

라고 생각했는데 여기에도 함정이 있었다. 정확히는 스케줄된 시간에 배치가 돌고있으면 수행이 안되는게 아니라, 스케줄된 시간에 배치가 돌고있으면 배치 주문이 들어가지 않기 때문에 다음날의 배치가 수행되지 않고, 당일의 배치는 전날 잘 주문이 들어갔다면 수행된다.

참고

version

에어플로우의 가장 최신 버전은 2020년 12월에 나온 2.0인데 대부분의 튜토리얼 글이 1.x 기준이라서 cli 명령어가 소소하게 다른 문제점이 있었다. 하지만 오류 메세지로 친절하게 안내해주어 큰 문제는 없었다. 1.x를 써본적이 없어 새로 적응해야 한다거나 하는 문제도 없었다.

2.0의 주요 특징

  • TaskFlow API를 이용해 PythonOperator를 더 깔끔하게 만들 수 있다.
  • 스케줄러 성능 향상
  • 하나 이상의 스케줄러 사용 가능
  • task의 묶음인 Task Group의 등장
  • UI 개선

참고

DB 연동

에어플로우에서 사용하는 패키지인 sqlalchemy가 짧은 간격으로 metadb와 커넥션을 맺고 끊으며 계속해서 롤백 세션이 생기는 문제가 있었다. sqlite3로 테스트를 해볼땐 롤백을 하든 말든 상관이 없었는데, DBA가 모니터링하는 postgres에 연결하고 나서부터 문제가 되기 시작했다.

찾아보니 에어플로우의 문제는 아니고 sqlalchemy의 pooling mechanism때문에 생긴 문제였다.

When the Connection is closed at the end of the with: block, the referenced DBAPI connection is released to the connection pool. From the perspective of the database itself, the connection pool will not actually “close” the connection assuming the pool has room to store this connection for the next use. When the connection is returned to the pool for re-use, the pooling mechanism issues a rollback() call on the DBAPI connection so that any transactional state or locks are removed, and the connection is ready for its next use.

커넥션이 다시 사용될 수 있도록 커넥션 풀로 돌아갈 때 불필요한 트랜잭션이나 락이 풀리도록 rollback을 해주는데, 이게 짧은 주기로 계속되다보니 DBA 입장에서는 롤백 세션이 계속해서 떠있는 것처럼 보였다.

대응 1

먼저 Nullpool을 사용해 sqlalchemy에서 connection pooling을 하지 않도록 설정해봤지만 해결되지 않았다. 에어플로우에서는 sqlalchemy session을 사용하고 있었는데, 이것을 사용하면 기본적으로 transaction이 시작되고, 해당 transaction이 끝나며 connection이 close되는데 이 connection close도 암묵적으로 rollback을 포함한다.

대응 2

connection close도 암묵적으로 rollback을 포함한다는 데에서 힌트를 얻어, engine create 시 pool_reset_on_return 인자를 'commit'으로 전달하도록 에어플로우 소스를 수정해봤다. 이렇게 세팅하면 커넥션을 깨끗하게 만들기 위해 rollback 대신 commit을 해준다.

이 방법을 사용해 rollback 세션이 줄어들긴 했지만, 굳이 소스를 바꿔가면서 롤백 세션을 없앨 필요 없다는 의견이 있어 DBA와 잘 합의하여 평화롭게 마무리되었다.

참고

갑자기 죽는 스케줄러와 웹서버

스케줄러와 웹서버가 소리소문없이 죽어버리는 문제가 있었다. 많은 사람이 비슷한 문제를 겪었는데 아직 그럴듯한 해결책은 없었다.

일단은 docker-compose.yml에서 restart option을 주어 좀비처럼 살아나게 했다.
metadatabase와 scheduler의 health check도 추가하고, 죽으면 대응하기로 했는데 restart option만으로도 아직은 문제가 생기지 않았다.

참고

UI tree view에서 tooltip 안나옴

개발자 도구 콘솔을 보니 moment.defaultZone.name을 못가져오고 있었다. 이게 뭔지는 모르지만 default timezone 설정이 제대로 안됐나 싶어 UI에서 default timezone을 설정해주니 tooptip이 잘 나왔다.

profile
developer hamdoe

0개의 댓글