TL

ワークフローオーケストレーション(DAG)

数百のバッチが絡み合う分析基盤で、依存の取りこぼし・二重実行・過去分の作り直しに悩む人へ。パイプラインをDAGで表し、論理日付で駆動し、センサーで外部依存を待ち、冪等な再試行で安全に回す原理を解きます。

応用オーケストレーションDAGAirflowスケジューリングバックフィル冪等性最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.オーケストレーターはタスク間の先行・後続をDAG(有向非巡回グラフ)で宣言し、依存の無いタスクを並列に、あるタスクは上流の成功を待って実行する。実行の単位はDAGランと、その中の各タスクインスタンス(DAG×論理日付×タスク)である。
  • 2.駆動は壁時計時刻でなく論理日付(データ区間)で行う。スケジューラは区間の終端が過ぎたDAGランを起票し、失敗時はバックオフ付きで同じタスクインスタンスを再試行する。外部依存はセンサーで待ち、poke/reschedule/deferrableでワーカーの占有を避ける。
  • 3.本番の安定は冪等性で決まる。タスクは論理日付を鍵に区間を丸ごと置換(partition overwrite/MERGE)し、何度再実行しても結果を一意に収束させる。これによりリトライ・catchup・バックフィルという「再実行」がすべて安全になる。

パイプラインの本体は手順ではなくDAG

分析基盤のバッチ処理は、素朴に見ると「抽出→集計→マート作成→配信」という一本道の手順です。しかし現実の基盤には何十もの源泉と何百ものテーブルがあり、依存は木ではなく網の目になります。この網を正しく表す唯一の構造が DAG(有向非巡回グラフ) です。ノードがタスク、有向辺が「先行→後続」の依存を表し、循環しない(自分の出力が巡り巡って自分の入力にならない)ことがバッチの前提を保証します。

オーケストレーター(Airflow、Dagster、Prefect 等)は、このDAGを宣言として受け取り、実行を司ります。役割は四つです。依存解決(辺で結ばれた上流が成功して初めて下流を起動する)、並列制御(依存の無いタスクは同時に走らせる)、再試行(失敗タスクをバックオフ付きで再実行する)、待機(外部条件が満たされるまでセンサーで待つ)。手続き型スクリプトが「実行順を人が並べる」のに対し、オーケストレーターは依存だけを宣言させ、実行順は依存から機械的に導出する。ここが本質的な違いです。

オーケストレーションのDAGとSparkのDAGは別物

同じ「DAG」でも層が違います。Sparkの DAG1つのジョブの中で、RDD変換の系譜をシャッフル境界でステージに切る実行計画の話です。オーケストレーションのDAGはジョブとジョブの間で、「取込ジョブが終わったら集計ジョブ、その後マートジョブ」というタスク間の依存を、しかも時間をまたいで繰り返し表します。前者はミリ秒〜分の実行エンジン内部、後者は分〜時間のワークフローの外郭。混同すると設計の議論が噛み合いません。

論理日付とDAGラン:時刻でなくデータ区間で駆動する

オーケストレーションを理解する最大の鍵は、壁時計時刻(実行された瞬間)と論理日付(処理対象のデータ区間)を分離することです。日次パイプラインは日付ごとに1回走りますが、そのとき本質的に重要なのは「いま何時か」ではなく「どの日のデータを作っているか」です。この処理対象区間を 論理日付(logical date)/データ区間(data interval) と呼びます。

具体例で押さえます。2026-06-20 分の売上を作るDAGは、その日のデータが出そろう 区間の終端(2026-06-21 00:00)を過ぎてから実行されます。つまり「実行される壁時計時刻」は区間終端より後ろにずれる。スケジューラはこのずれを前提に、区間が閉じたDAGランを起票します。

概念意味例(日次)
論理日付/データ区間処理対象のデータが属する論理的な時間帯2026-06-20 00:00 〜 06-21 00:00
DAGラン(DAG Run)ある論理日付に対するDAG全体の1回の実行run for 2026-06-20
タスクインスタンスDAGラン内の1タスクの1回分(DAG×論理日付×タスク)build_sales@2026-06-20
壁時計の実行時刻実際にワーカーが走った瞬間(区間終端より後)2026-06-21 00:05 頃

この分離が効く理由は決定的です。出力を壁時計時刻でなく論理日付の関数にすれば、いつ実行しても・何回実行しても、同じ論理日付なら同じ結果になる。逆に「実行時の now()」を処理に使うと、再実行のたびに結果が変わり、後述の冪等性が崩れます。「毎日9時に集計」ではなく「対象日の区間が閉じたら集計」——このデータ準備完了で駆動する規律が、上流遅延に強い基盤の分かれ目です。

スケジューラの仕事:起票・依存解決・状態遷移

スケジューラは概念的に一定周期でループし、各DAGについて次を判定します。(1) 新しいDAGランを起票すべきか(前回スケジュール以降に、区間終端を過ぎた論理日付があるか)。(2) 実行中のDAGランで、上流が成功して実行可能になったタスクはどれか(3) それらのタスクインスタンスをどのワーカーへ割り当てるか

各タスクインスタンスは状態機械として遷移します。おおむね scheduled(実行可能と判定)→ queued(エグゼキュータの待ち行列へ)→ running(ワーカーで実行中)→ success / failed。失敗すると、リトライ回数が残っていれば up_for_retry を経て再び scheduled に戻ります。「どのタスクを次に動かせるか」は、上流タスクインスタンスの状態を辺に沿って調べるだけで機械的に決まる——これが依存をDAGで宣言することの実利です。

DAGラン(logical date = 2026-06-20)内の依存解決:

  extract_orders(success) ─┐
                           ├─► build_daily_sales ─► build_sales_mart
  extract_users(running)  ─┘        (両上流のsuccess待ち → まだscheduledにできない)

  → build_daily_sales は extract_users が success になるまで起動しない
  → 依存の無い extract_orders / extract_users は並列に走る

実行を担うのが エグゼキュータ(executor) で、これはワークロードの分散の仕方を決めます。単一プロセス内で回すもの、ワーカープールへ配るもの(Celery系)、コンテナ/Podとして1タスクを隔離実行するもの(Kubernetes系)があり、並列度・隔離・スケール弾力が変わります。オーケストレーター自身は「重い計算をする場所」ではなく、重い計算(Spark/SQLウェアハウス)を外部へ指示し、成否を追跡する司令塔である点を外さないでください。分散データ処理の本体はエンジン側にあります。

catchupとバックフィルは『再実行』の別名

DAGを新設したり一時停止から再開したりすると、未実行の過去区間が溜まります。これを論理日付ごとに順に埋めるのが catchup(キャッチアップ) です。既存DAGに対し過去の特定区間を意図的に作り直すのが バックフィル(backfill)。どちらも「過去の論理日付に対してDAGランを(再)実行する」ことに帰着し、成立条件はただ一つ、各タスクインスタンスが冪等であることです。新設DAGでcatchupが不要なら明示的に無効化しないと、意図せず過去全区間を走らせて基盤を溢れさせます。

センサーと外部依存:待ち方がワーカーを食い潰す

パイプラインは孤立して動きません。「上流チームのファイルがS3に置かれたら」「先行パーティションが揃ったら」「外部APIのジョブが完了したら」——こうした外部条件の充足を待つのが センサー(sensor) です。センサーは条件が真になるまで下流を堰き止める門で、これによって時刻でなく事実の成立でパイプラインを駆動できます。

問題は「どう待つか」です。素朴なセンサーは条件を周期的に確認し続けますが、その待ち方がワーカースロットを長時間占有すると、待っているだけのタスクで並列度を食い潰すという運用事故を起こします。待機モードは進化してきました。

モード待機中のワーカー占有特徴・注意
poke(ポーク)占有し続ける短い待ちには単純で低遅延。長時間待つとスロットを枯渇させる
reschedule占有を解放し次回チェックまで眠る長い待ち向き。チェック間隔ぶんの遅延と再スケジュール負荷が出る
deferrable(延期可能)専用の軽量プロセスへ委譲多数の長時間待機を少ないリソースで捌く。イベント駆動で効率的

センサーの設計では タイムアウト も必須です。上流が永遠に来ない場合に無限待機すると、そのDAGランが詰まり、後続の論理日付まで連鎖的に遅延します。「一定時間で諦めて失敗させる」ことで、壊れた依存を早期に可視化し、リトライやアラートに載せられます。センサーは便利ですが、待つほど脆くなる——外部依存は「押し付けられる(イベント通知される)」形に寄せられるなら、その方が堅牢です。

冪等性とリトライ:再実行が結果を壊さないこと

オーケストレーションの信頼性は、突き詰めれば冪等性(idempotency) の一点に収束します。定義は「同じ入力(同じ論理日付)に対し、タスクを何回実行しても最終結果が一意に収束する」。なぜ死活的か。リトライも、catchupも、バックフィルも、障害復旧も、すべてタスクインスタンスの再実行だからです。再実行が行を二重化したり状態を破壊したりするなら、オーケストレーターの中核機能がそのまま凶器になります。

非冪等の典型は追記(append/INSERT) です。同じ論理日付を二度走らせれば行が二重に積まれる。冪等にする定石は「追記せず、その区間の出力を丸ごと置換する」ことです。

非冪等:INSERT INTO sales SELECT ... WHERE d = '2026-06-20'
        → リトライやバックフィルのたびに同じ行が二重に積み上がる

冪等:  パーティション d='2026-06-20' を削除してから書き直す(partition overwrite)
        あるいは主キーで MERGE(UPSERT)し一意性を保つ
        → 論理日付 2026-06-20 の出力は、何回走らせても 1 通りに収束

ここで パーティション設計 が効いてきます。論理日付をそのままパーティション列にすれば、それが冪等キーになる。「この論理日付の出力=この論理日付の入力の関数」と決めておけば、どの区間をいつ何回作り直しても結果は変わりません。冪等性のない再試行は、失敗を回復するどころかデータを壊すギャンブルです。

リトライ自体にも原理があります。一時障害(ネットワーク瞬断、外部APIのスロットリング、スポットインスタンス退去)はランダムに起きるので、指数バックオフ(1分→2分→4分…)で再試行間隔を広げ、外部への殺到を避けます。ただし再試行が意味を持つのは失敗が一時的なときだけで、入力データが壊れている・ロジックにバグがある恒久障害はリトライで直りません。だからリトライ上限を設け、超えたら失敗を確定させて人に上げる(アラート)。リトライは一時障害の吸収装置であって、恒久障害の隠蔽装置ではありません

『処理時刻』を出力に混ぜると冪等性が即壊れる

processed_at = now()、実行ごとに変わる乱数・自動採番、あるいは「最後にDAGが走った時点までの全件」を掬うような壁時計依存のクエリを出力に含めた瞬間、同じ論理日付でも実行ごとに結果が変わり、冪等性は崩壊します。過去区間を作り直すと差分が生まれ、バックフィルが再現不能になる。時刻を持たせるなら「いつ処理したか」ではなく「どの論理日付のデータか」で出力を決める。冪等性は設計で作り込むもので、リトライ機能を付ければ自動で手に入るものではありません。

Airflow的な考え方:宣言されたDAGを時間軸へ展開する

Airflowに代表される考え方の核は、「コードで宣言した1つのDAG定義を、論理日付の系列に沿って何度も実体化する」ことです。あなたが書くのは「タスクと依存の形」というテンプレートであり、スケジューラがそれを論理日付ごとの DAGラン(さらにその中のタスクインスタンス)へ展開します。1本のDAG定義から、2026-06-1806-1906-20…と時間軸方向にインスタンスが並ぶ——この二次元(タスクの依存 × 論理日付の系列)で捉えるのが要諦です。

もう一つの柱は、タスク間で大きなデータを手渡ししないという原則です。オーケストレーターが受け渡すのは「どの論理日付を、どのテーブルに書いたか」といった小さなメタデータ(ポインタ) であって、数億行の中身そのものではありません。実データは外部の レイク/ウェアハウス に置き、タスクは「そこを読み、そこへ書く」よう指示するだけ。これにより、タスクは疎結合になり、個別に再実行でき、リネージ(依存の追跡) もDAGの辺として自然に得られます。DAGの構造そのものが「この数字はどのタスク群から来たか」という影響分析の地図になるわけです。

資格・面接で問われる整理

定番の勘所。(1) パイプラインの本体は手順でなくDAGによる依存宣言で、実行単位はDAGランとタスクインスタンス(2) 駆動は壁時計時刻でなく論理日付(データ区間) で、区間終端を過ぎてから実行される。(3) 冪等性が全ての土台——リトライ・catchup・バックフィルは全て再実行だから、追記でなくパーティション置換/MERGEで区間を一意化する。(4) 外部依存はセンサーで待ち、poke/reschedule/deferrable とタイムアウトでワーカー占有と無限待機を避ける。(5) リトライは指数バックオフで一時障害を吸収する装置で、恒久障害には上限を切ってアラートへ。(6) オーケストレーターは司令塔で、重い分散処理はエンジン側が担う。

まとめ

  • パイプラインの本体は一本道の手順ではなく DAGによる依存宣言。オーケストレーターは依存解決・並列制御・再試行・センサー待機を司り、実行順は依存から機械的に導出する。
  • 実行の単位は DAGラン(ある論理日付に対するDAG全体の1回)と、その中の タスクインスタンス(DAG×論理日付×タスク)。状態機械として scheduled→queued→running→success/failed を遷移する。
  • 駆動は壁時計時刻でなく 論理日付(データ区間)。区間の終端が過ぎてから実行され、出力を論理日付の関数にすることで再現性を確保する。
  • 冪等性が信頼性の土台。リトライ・catchup・バックフィルは全て再実行なので、追記でなくパーティション置換/MERGEで区間を一意化し、now() など壁時計依存の値を出力に混ぜない。
  • センサーで外部依存を待つが、待ち方(poke/reschedule/deferrable)とタイムアウトを誤るとワーカー枯渇や無限待機を招く。リトライは指数バックオフで一時障害を吸収する装置で、恒久障害には上限とアラートで応じる。
  • Airflow的発想は「1つのDAG定義を論理日付の系列へ展開」し、タスク間はメタデータだけを受け渡す。実データは外部エンジンに置き、DAG構造そのものがリネージと影響分析の地図になる。

データ工学 Article

ワークフローオーケストレーション(DAG)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

オーケストレーション

比較で見る軸

難易度: advanced / カテゴリ: データ工学 / タグ数: 6

導入後に効く点

駆動は壁時計時刻でなく論理日付(データ区間)で行う。スケジューラは区間の終端が過ぎたDAGランを起票し、失敗時はバックオフ付きで同じタスクインスタンスを再試行する。外部依存はセンサーで待ち、poke/reschedule/deferrableでワーカーの占有を避ける。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
データ工学
タグ数
6

判断チェックリスト

  • 自社の用途が「オーケストレーション / DAG」に近いか確認する。
  • 強みである「オーケストレーターはタスク間の先行・後続をDAG(有向非巡回グラフ)で宣言し、依存の無いタスクを並列に、あるタスクは上流の成功を待って実行する。実行の単位はDAGランと、その中の各タスクインスタンス(DAG×論理日付×タスク)である。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

オーケストレーションDAGAirflowスケジューリングバックフィルオーケストレーションDAGAirflow