Airflow - 구축

NuJey·5일 전

Airflow 구축 후기

여러 데이터를 매일 긁어와 가공하고 리포트로 내보내는 게 핵심인데, 이걸 굴리는 파이프라인을 Airflow로 구현했고, 몇 달 굴려보니 할 말이 좀 생겨서 정리해둔다.

Airflow가 뭐 하는 물건이냐

한 줄로 하면 "정해진 순서대로, 정해진 시간에, 여러 작업을 돌려주고 그 결과를 추적해주는 도구" 다.

이렇게만 쓰면 크론(cron)이랑 뭐가 다른지 모르겠는데, 실제로 처음엔 크론으로 시작했다가 옮겨온 거라 차이가 뭔지는 확실히 알고 있다.

크론은 "매일 2시에 이 스크립트 실행"까지만 해준다. 그런데 실무에서 필요한 것은 아래와 같다.

  • A가 끝나야 B를 돌릴 수 있다 (수집이 끝나야 집계를 한다)
  • A가 실패하면 B는 돌면 안 된다
  • 실패하면 2번까지 재시도하고, 그래도 안 되면 알려줘라
  • 어제 것만 다시 돌리고 싶다
  • 지난주에 3일치가 실패했는데 그것만 골라 다시 돌리고 싶다
  • 지금 뭐가 돌고 있고, 뭐가 실패했는지 화면으로 보고 싶다

크론으로 이걸 다 하려면 결국 상태를 어딘가 기록하고, 순서를 관리하고, 재시도를 직접 짜야 한다. 그러다 보면 작은 워크플로 엔진을 스스로 만들고 있게 된다. Airflow는 그걸 이미 만들어둔 물건이다.

작업 흐름

수집 >> 적재 >> 집계 >> 알림

이게 전부다. 화살표 방향이 의존 관계고, Airflow가 순서·재시도·기록·화면을 알아서 해준다. 이 흐름 하나를 DAG이라고 부른다. (Directed Acyclic Graph)

매일 도는 수집, 시간마다 도는 중간 수집, 집계, 정합성 검사, 백필용 수동 DAG 같은 것들.

어떻게 구성했나

중요한 판단이 하나 있었다. Airflow가 직접 일을 하게 두지 않았다.

Airflow  →  "이 계정들 수집해" (메시지 큐에 넣기)
              ↓
          수집 컨테이너 여러 개가 붙어서 처리
              ↓
          S3 → 데이터 웨어하우스 → 집계 → 서비스 DB

Airflow는 일을 시키고, 결과가 잘 들어왔는지 확인하는 것까지만 한다. 실제 API 호출과 데이터 수집은 별도 컨테이너가 한다.

처음엔 Airflow 안에서 다 하려고 했는데, 그러면 수집이 무거워질 때마다 Airflow 서버가 같이 죽는다. 스케줄러가 죽으면 다른 작업까지 전부 멈추니까 최악이다. 실제로 서버 메모리를 아끼겠다고 사양을 한 단계 낮췄다가, 웹서버가 재시작 루프에 빠져서 SSH도 안 먹는 상태까지 가본 적이 있다. 그 뒤로 "오케스트레이터는 가볍게, 일은 밖에서" 를 원칙으로 잡았다.

느낀점

1. 멱등하게 짜야한다.

멱등(idempotent)하다는 건 같은 걸 몇 번을 돌려도 결과가 같다는 뜻이다. 데이터 파이프라인에서는 이게 거의 전부다.

우리는 적재를 전부 INSERT가 아니라 MERGE(있으면 갱신, 없으면 삽입) 로 하고, 수집 범위도 매번 며칠씩 겹쳐서 다시 가져온다. 낭비 같지만 아니다.

한번은 배포 순서를 잘못해서 적재 작업이 3시간 반 동안 죽어 있던 적이 있다. 보통 이런 사고는 "그 시간 데이터가 비었다"로 끝나는데, 데이터 유실이 0이었다. 문제를 고치자 다음 사이클이 겹쳐진 범위를 다시 가져오면서 스스로 메웠다.

멱등하게 짜두면 장애 대응이 "복구 작업"이 아니라 "고치고 기다리기" 가 된다. 이 차이가 새벽에 엄청나게 크다.

2. 성공했다는 표시가 성공을 뜻하진 않는다

API에는 호출량 제한이 있다. 어느 날 코드 배포로 API 호출이 늘었고, 제한에 걸리면서 특정 작업이 실패하기 시작했다. 10분마다 도는 작업이라 9시간 동안 54번 실패했고, 실패 알림도 54번 왔다.

그래서 이렇게 고쳤다. "호출 제한은 우리 잘못이 아니고 다음 사이클에 다시 하면 되니까, 이 경우엔 에러로 처리하지 말고 그냥 넘어가자."

알림은 깨끗하게 멎었다. 화면도 전부 초록불이 됐다. 그런데 로그를 열어보니 이랬다.

[모니터] 호출 제한 — 이번 사이클 건너뜀
[모니터] 호출 제한 — 이번 사이클 건너뜀
[모니터] 호출 제한 — 이번 사이클 건너뜀

기능은 여전히 하나도 안 돌고 있었다. 바뀐 건 알림의 양뿐이었고, "이 작업이 실제로 하는 일"은 그대로 죽어 있었다. 시끄럽게 죽던 게 조용히 죽는 걸로 바뀐 것뿐이다.

에러를 삼키는 코드를 쓸 때는 "몇 번 연속으로 삼켰으면 그건 알려라" 를 같이 넣어야 한다. 안 그러면 초록불이 거짓말을 하기 시작한다.

3. 로그에만 있는 신호는 없는 것과 같다

애초에 이 파이프라인에 "수집 상태 감시" 기능을 붙이게 된 계기가 있다.

어떤 광고 계정 하나가 인증이 끊겨서 5일 동안 데이터가 안 들어왔는데, 아무도 몰랐다. 사람이 리포트를 눈으로 보다가 발견했다.

그런데 수집기는 그동안 매일 열 번씩 꼬박꼬박 실패 로그를 남기고 있었다. 신호는 계속 있었다. 아무도 안 봤을 뿐이다.

이런 게 생각보다 곳곳에 있다. "이미지 몇 개 못 가져왔음"도 로그 한 줄, "알림 보낼 사람을 못 찾았음"도 로그 한 줄. 전부 아무도 안 보는 곳에 쌓인다.

교훈은 단순하다. 누군가 보러 가야만 보이는 정보는, 없는 정보다. 중요한 신호는 사람이 있는 곳(메일, 슬랙 같은)으로 밀어내야 한다.

4. 배포와 반영은 다르다

우리는 CI/CD를 일부러 안 뒀다. DAG 파일을 서버 디렉터리에 물려놔서 git pull만 하면 반영되고, 롤백도 되돌리기 한 줄이면 된다. 단순해서 장애 중에 특히 좋았다.

대신 함정이 하나 생겼다. "머지했으니 반영됐겠지" 라고 가정하게 된다.

실제로 그러다 사고가 났다. 코드보다 DB 스키마 변경이 먼저 나가버려서, 아직 배포되지 않은 구버전 코드가 없는 컬럼을 찾다가 계속 실패했다. 위에서 말한 그 3시간 반짜리 장애다.

자동배포가 없는 구조라면 "진짜 반영됐나"를 확인하는 절차가 문서에 있어야 한다. 지금은 파일 수정 시각과 새 코드가 실제로 있는지를 보고 넘어간다.

그래서 Airflow - ?

  • 작업 사이에 순서와 의존 관계가 있다
  • 매일/매시간 반복되고, 실패하면 재시도가 필요하다
  • 과거 특정 구간만 다시 돌려야 할 일이 종종 있다
  • 지금 뭐가 도는지 눈으로 봐야 한다

반대로 작업 하나가 독립적으로 하루 한 번만 돌면 되는 거면 크론이 낫다. Airflow는 서버도 띄워야 하고 DB도 붙어야 해서, 굴리는 비용 자체가 있다.

몇 달 해보고 남은 한 문장은 이거다.

파이프라인은 데이터를 잃는 실패엔 강하게 만들기 쉽다. 멱등하게 짜면 된다.
어려운 건 조용히 멈추는 실패 쪽이다. 초록불, 아무도 안 보는 로그, 안 오는 알림.

다음에 시간을 쓸 곳도 수집 로직이 아니라, 안 돌고 있다는 사실이 사람에게 도달하는 경로라고 생각하고 있다.

0개의 댓글