Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Airflow1=>Airflow2へのupgrade 事例紹介

Airflow1=>Airflow2へのupgrade 事例紹介

AIrflow 1.10.15/CloudComposer1 で動く本番環境を
Airflow 2.2.1/CloudComposer2 にupgradeしたので事例の紹介です。

https://github.com/reizist/slide/tree/master/datatech_casual%232

Reiji Kainuma

May 17, 2022
Tweet

More Decks by Reiji Kainuma

Other Decks in Programming

Transcript

  1. 自己紹介 @reizist Web Backend / Infra / Data (Infra) R

    なんとかという会社で データエンジニア 魚を捌いて食べるのが好きで、海沿い への移住を検討しています 2 / 24
  2. Airflow2(Composer2) の恩恵 / モチベーション Airflow1 系のサポート体制への懸念 GCP においても2023/3 でAirflow1 を非サポート化

    daily job を落とさず毎日朝11 時までに必ずデータを揃えたい都 合上安心できるサポート体制が必要 6 / 24
  3. 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
  4. Airflow2(Composer2) の恩恵 / モチベーション TaskGroup が使える グループ化したいTask をSubDagOperator で実現していた =>

    稀に想定以上のリソースを要求しworker が落ちる障害 非推奨なSubDagOperator からの脱却 8 / 24
  5. Airflow2(Composer2) の恩恵 / モチベーション Composer2 によるインフラリソース管理の柔軟化 Airflow1 時はdaily job 実行時にnode

    数を自前スケールしていた => GKE のAutoPilot によりnode 管理が不要に スケジューラのマシンタイプや実行数は指定不可だった => スケジューラの冗長化が可能になりより堅牢に daily job は半日しっかり動き半日ほとんど動かない => インフラリソースの最適化が見込めた 9 / 24
  6. Airflow2 への upgrade 方法 基本は公式Doc を読んで順に対応すればOK airflow API が変わっていたりmodule path

    が変わっていたりする その他細かい変更はあるのでAirflow1/Airflow2 の両ソースをパッと 見られる環境にしておくと安心 11 / 24
  7. Airflow2 への upgrade 方法 事前制約: daily job は停止できない Airflow2 切り替え後不調時のrollback

    環境が必須 Airflow 1 系と2 系の両環境を用意しDAG のPause/Unpause を利用 検証時に発覚: Airflow2 系でSubDagOperator が動かない現象が発生 => TaskGroup への完全移行が必須 12 / 24
  8. Airflow2 への upgrade 方法 Airflow1 系はSubDagOperator Airflow2 系はTaskGroup で動かす必要があった とはいえ可能な限りソースコードの差分を小さくしたい

    => タスクの依存関係を作るメソッドを切り出し、SubDag でwrap す るメソッドとTaskGroup でwrap するメソッドに分離 13 / 24
  9. 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
  10. * 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
  11. 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
  12. 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
  13. 補足 : SubDagOperator を辞める SubDagOperator に on_failure_callback を指定し失敗時にslack で メンションを受け取る運用

    => SubDag 内のどのtask が落ちてもSubDag 自体も失敗する挙動 => TaskGroup にはcallback は指定できない => 全task にcallback を指定することで失敗を検知 19 / 24
  14. 補足 : Airflow1=>Airflow2 へのデータ移行 airflow_db backup のための公式script がある ただしAirflow2.2 ではexecution_date

    カラムが廃止されるなどの schema 変更があるので2.0 系を挟む必要がある SubDag を辞める都合上1 系データのimport を頑張る必要もないと 判断 ただし1 系のデータはBQ にEmbulk でexport しておいた 20 / 24
  15. 補足 : 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
  16. 補足 : CloudLogging の sink error が発生 BQ のquery 実行ログをAudit

    Log として残していた ( おそらくBigQueryOperator からBigQueryExecuteQueryOperator に変わったことで)stderr のschema が変わった gcp_audit_log.stderr テーブルの再作成で対処した 22 / 24