Slide 1

Slide 1 text

Airflow2 に upgrade した事例紹介 @reizist 1 / 24

Slide 2

Slide 2 text

自己紹介 @reizist Web Backend / Infra / Data (Infra) R なんとかという会社で データエンジニア 魚を捌いて食べるのが好きで、海沿い への移住を検討しています 2 / 24

Slide 3

Slide 3 text

Airflow1.10.15/CloudComposer1 を Airflow2.2.1/CloudComposer2 に upgrade した事例を紹介します 3 / 24

Slide 4

Slide 4 text

Airflow を使っている方 Airflow を使っているがまだ1 系の方 は参考になるかもしれません 4 / 24

Slide 5

Slide 5 text

アジェンダ Airflow2 upgrade の恩恵/ モチベーション Airflow2 へのupgrade 方法 Airflow2 切り替え後の話 補足 5 / 24

Slide 6

Slide 6 text

Airflow2(Composer2) の恩恵 / モチベーション Airflow1 系のサポート体制への懸念 GCP においても2023/3 でAirflow1 を非サポート化 daily job を落とさず毎日朝11 時までに必ずデータを揃えたい都 合上安心できるサポート体制が必要 6 / 24

Slide 7

Slide 7 text

Airflow1 # Operator の定義が冗長的 extract = PythonOperator(task_id="extract", python_callable=extract) transform = PythonOperator(task_id="transform", python_callable=transform) load = PythonOperator(task_id="load", python_callable=load) extract >> transform >> load # 依存関係を明示的に定義 Airflow2 order_data = extract() order_summary = transform(order_data) load(order_summary["total_order_value"]) Airflow2(Composer2) の恩恵 / モチベーション DAG の記述量が減り簡素化できる"TaskFlow API" 後述の理由によりAirflow1 と書き方を継続 7 / 24

Slide 8

Slide 8 text

Airflow2(Composer2) の恩恵 / モチベーション TaskGroup が使える グループ化したいTask をSubDagOperator で実現していた => 稀に想定以上のリソースを要求しworker が落ちる障害 非推奨なSubDagOperator からの脱却 8 / 24

Slide 9

Slide 9 text

Airflow2(Composer2) の恩恵 / モチベーション Composer2 によるインフラリソース管理の柔軟化 Airflow1 時はdaily job 実行時にnode 数を自前スケールしていた => GKE のAutoPilot によりnode 管理が不要に スケジューラのマシンタイプや実行数は指定不可だった => スケジューラの冗長化が可能になりより堅牢に daily job は半日しっかり動き半日ほとんど動かない => インフラリソースの最適化が見込めた 9 / 24

Slide 10

Slide 10 text

Airflow2(Composer2) の恩恵 / モチベーション 総じてdaily job を動かすtask runner としてより安全に/ 運用コスト の削減が見込めた 10 / 24

Slide 11

Slide 11 text

Airflow2 への upgrade 方法 基本は公式Doc を読んで順に対応すればOK airflow API が変わっていたりmodule path が変わっていたりする その他細かい変更はあるのでAirflow1/Airflow2 の両ソースをパッと 見られる環境にしておくと安心 11 / 24

Slide 12

Slide 12 text

Airflow2 への upgrade 方法 事前制約: daily job は停止できない Airflow2 切り替え後不調時のrollback 環境が必須 Airflow 1 系と2 系の両環境を用意しDAG のPause/Unpause を利用 検証時に発覚: Airflow2 系でSubDagOperator が動かない現象が発生 => TaskGroup への完全移行が必須 12 / 24

Slide 13

Slide 13 text

Airflow2 への upgrade 方法 Airflow1 系はSubDagOperator Airflow2 系はTaskGroup で動かす必要があった とはいえ可能な限りソースコードの差分を小さくしたい => タスクの依存関係を作るメソッドを切り出し、SubDag でwrap す るメソッドとTaskGroup でwrap するメソッドに分離 13 / 24

Slide 14

Slide 14 text

Airflow2 への upgrade 方法 def _tasks(dag, child_dag_name, args): start_task = dummy_operator(task_id="start_task") task = PythonOperator(task_id="main_task", python_callable=_main) end_task = dummy_operator(task_id="end_task") start_task >> task >> end_task def build_xxx_dag(parent_dag_name, child_dag_name, args): dag_name = "%s.%s" % (parent_dag_name, child_dag_name) with DAG(dag_id=dag_name, default_args=args) as dag: _tasks(dag, args) return dag def build_xxx_task_group(dag, args): with TaskGroup("xxx_tasks") as xxx_tasks: _tasks(dag, args) return xxx_tasks 14 / 24

Slide 15

Slide 15 text

* Airflow1 系: daily_task_for_v1.py dag = DAG(dag_id=DAG_NAME, default_args=default_args, schedule_interval="00 15 * * *") daily_start_task = DummyOperator(task_id="daily_start_task", dag=dag) daily_end_task = DummyOperator(task_id="daily_end_task", dag=dag) xxx_tasks = SubDagOperator( task_id="xxx_tasks", subdag=build_xxx_dag(DAG_NAME, "xxx_tasks", default_args), default_args=default_args, dag=dag, ) daly_start_task >> xxx_tasks >> daily_end_task * Airflow2 系: daily_task_for_v2.py dag = DAG(dag_id=DAG_NAME, default_args=default_args, schedule_interval="00 15 * * *") daily_start_task = DummyOperator(task_id="daily_start_task", dag=dag) daily_end_task = DummyOperator(task_id="daily_end_task", dag=dag) with dag: xxx_tasks = build_xxx_task_group(dag, default_args) daly_start_task >> xxx_tasks >> daily_end_task Airflow2 への upgrade 方法 15 / 24

Slide 16

Slide 16 text

Airflow2 への upgrade 方法 master branch では daily_task_for_v1.py を、 version2 branch では daily_task_for_v2.py を使う _tasks() 内をいじらない限りconflict は発生しない master branch へのmerge をtrigger に version2 branch へauto merge master branch ではAirflow1 が、 version2 branch ではAirflow2 がそれぞれ最新のソースコードで 動く状態を担保 16 / 24

Slide 17

Slide 17 text

master version2 topic workflow-airflow 1 Cloud Composer workflow-airflow 2 Cloud Composer deploy deploy 安定稼働後 merge workflow-airflow 2 Cloud Composer deploy 削除 auto merge TaskGroup 化 module path の修正 17 / 24

Slide 18

Slide 18 text

Airflow2 切り替え後の話 障害対応が減り安定稼働した 昨期2021/10 - 2022/3 で失敗通知を受け取り手動で再実行する など何らかの人間による対応をした件数: 40 件ほど 今期2022/4 - : 3 件 外部サービスの不調などAirflow とは無関係の症状が主で Airflow 起因の対応は0 件 18 / 24

Slide 19

Slide 19 text

補足 : SubDagOperator を辞める SubDagOperator に on_failure_callback を指定し失敗時にslack で メンションを受け取る運用 => SubDag 内のどのtask が落ちてもSubDag 自体も失敗する挙動 => TaskGroup にはcallback は指定できない => 全task にcallback を指定することで失敗を検知 19 / 24

Slide 20

Slide 20 text

補足 : Airflow1=>Airflow2 へのデータ移行 airflow_db backup のための公式script がある ただしAirflow2.2 ではexecution_date カラムが廃止されるなどの schema 変更があるので2.0 系を挟む必要がある SubDag を辞める都合上1 系データのimport を頑張る必要もないと 判断 ただし1 系のデータはBQ にEmbulk でexport しておいた 20 / 24

Slide 21

Slide 21 text

補足 : CloudComposer2 の地味な変更点 backend db がMySQL からPostgreSQL に変わっている Airflow の仕組み上のretry とは別にSQL 経由でnon successed な task をrerun する自前の仕組みを使っているため影響した SELECT unfinished_task.dag_id, task_id, state, operator, IF(state = 'queued', (TO_SECONDS(NOW()) - TO_SECONDS(COALESCE(end_date, start_date))), --- `TO_SECONDS` はMySQL にのみ組み込み (TO_SECONDS(NOW()) - TO_SECONDS(start_date)) ) AS duration_time, unfinished_task.execution_date, start_date FROM ( SELECT * FROM task_instance WHERE state IN ('running', 'queued', 'shutdown') AND execution_date LIKE '{}%' AND ..... ) unfinished_task 21 / 24

Slide 22

Slide 22 text

補足 : CloudLogging の sink error が発生 BQ のquery 実行ログをAudit Log として残していた ( おそらくBigQueryOperator からBigQueryExecuteQueryOperator に変わったことで)stderr のschema が変わった gcp_audit_log.stderr テーブルの再作成で対処した 22 / 24

Slide 23

Slide 23 text

まとめ 堅牢化/ コスト削減の目的で Airflow2/CloudComposer2 にupgrade した 障害対応数は減って安定化した celery/worker_concurrency 調整によるインフラリソースの最適化 を今後予定 23 / 24

Slide 24

Slide 24 text

ご清聴ありがとうございました 24 / 24