Data Engineer Associate — 第 10

処理を1つのジョブに束ねる — タスク種別と依存関係の設計

読了 17確認 6更新 2026-08-09

この章で学ぶこと

  • 1つのジョブに束ねる範囲を失敗単位から判断できる
  • 主要なタスク種別と必須のコンピュートを言い分ける
  • depends_onで並列と直列を混ぜたDAGを組み立てる
  • Run ifの6条件と未達時の状態を使い分ける
  • If/elseとFor eachで分岐とループを表現する

この章に出てくる用語

タスク
ジョブを構成する最小の実行単位です。ノートブックやSQLなど種別を選び、計算資源とリトライ回数を個別に割り当てます。1ジョブに最大1,000タスクを置け、100タスクを超えるジョブはJobs API 2.2が必要です。
depends_on
タスク間の依存を宣言するフィールドで、必ず後続タスク側に書きます。先行タスクのキーを配列で並べると有向非巡回グラフができ、依存のないタスクは自動で並列実行されます。tasks配列の並び順は実行順序に影響しません。
Run if
依存タスクの結果をどう扱うかを決める条件で、既定はAll succeeded、API値はALL_SUCCESSです。ALL_DONEなど6種類あり、満たさない側はUpstream failedかExcludedになります。
Excluded
Run ifの失敗系条件が満たされなかったタスクに付く状態です。下流の条件評価では成功として扱われますが、依存がすべてExcludedなら自分もExcludedになり、除外は下流へ連鎖します。
If/else 条件タスク
left、op、rightの3つで真偽を判定する分岐タスクです。EQUAL_TOは文字列比較なので12.0==12はfalse、GREATER_THAN_OR_EQUALは数値比較なので12.0>=12はtrueです。
For each タスク
JSON配列の要素ごとにネストタスクを繰り返す制御タスクです。Concurrencyの既定は1で、UI直書きの入力は5,000文字まで、ネストにFor eachは置けず下流はFor each本体に依存させます。
タスク値
タスク間で小さな値を受け渡す仕組みです。dbutils.jobs.taskValues.setで書き込み、JSON表現で48KiBまで。設定はPythonノートブックに限られ、下流は動的値参照で読むのが推奨です。
Run job タスク
別のジョブをまるごと1つのタスクとして起動する種別です。3段を超える入れ子と、互いを呼び合う循環依存はサポートされません。同時実行の親タスクはワークスペースあたり750という別枠の上限を消費します。

Lakeflow Jobs の全体像とタスク種別 — 何を1つのジョブに束ねられるのか

ノートブックを1本ずつ手で実行しているうちは、どこまで進んだのかが担当者の記憶の中にしかありません。取り込みが途中で落ちたことに気づかないまま変換を走らせ、ほとんど空のテーブルで集計表を上書きしてしまう。夜間バッチでこれが起きると、翌朝の数字が静かに狂います。処理の順番と、前の処理が成功したかどうかの判定を、人間の記憶の外側に置くのがジョブという仕組みです。

Lakeflow Jobs (旧 Databricks Jobs) では、ジョブが入れ物で、タスクが実際の実行単位です。タスクは1つずつ種別を選び、どの計算資源で動かすか、失敗したら何回やり直すかを個別に決めます。ノートブックの末尾で `dbutils.notebook.run()` を呼んで次の処理につなぐ書き方とは、ここが決定的に違います。呼び出し方式では失敗がノートブック1本分としてしか見えず、やり直しは常に先頭からです。処理をタスクに割ってジョブに束ねておけば、失敗した箇所とその下流だけを Repair run で再実行できます。

タスクに割る見返りは、設定を個別に持てることです。1つのタスクは、実行に使うコンピュート、最大再試行回数の `max_retries`、再試行の最小間隔をミリ秒で指定する `min_retry_interval_millis`、タイムアウト時にも再試行するかを決める `retry_on_timeout`、打ち切り時間の `timeout_seconds` を、それぞれ自分の値として持ちます。上流に3回、下流に0回といった設定が普通にでき、`depends_on` によってリトライ設定が継承されることもありません。1本の巨大なノートブックにまとめると、この調整の単位がすべて失われます。

実行時間の制御は2段構えです。`timeout_seconds` (UI の Timeout) を超えたタスクは Timed Out として打ち切られます。一方、所要時間しきい値の Warning は超過した時点でイベントを出すだけで、処理そのものは止めません。長引いたら知らせたいが止めたくはない、という要件はこちらです。なお Timeout と Retries を併用した場合、タイムアウトは各リトライに個別に適用されます。Timeout 30分・Retries 2回なら、各試行がそれぞれ30分の枠を持ちます。

タスク種別は「何を動かすか」と「どのコンピュートが要るか」で選ぶ

種別は18種類あり、1つのジョブの中に違う種別を自由に混在させられます。ノートブックを起点に SQL で集計し、最後にダッシュボードを更新する構成が1ジョブで完結します。dbt プロジェクトのために外部スケジューラを用意する必要はなく、宣言型パイプラインもノートブックでラップせず起動できます。API と YAML で使うキー名まで含めて、よく使うものを並べます。

タスク種別API のキー動かすもの必要なコンピュート
Notebook`notebook_task`ノートブッククラスタまたはサーバーレス
Python script`spark_python_task``.py` ファイルクラスタまたはサーバーレス
Python wheel`python_wheel_task`wheel のエントリポイントクラスタまたはサーバーレス
JAR`spark_jar_task`JAR の main クラスクラスタ
SQL`sql_task`クエリ / アラート / `.sql` ファイルサーバーレスまたは Pro の SQL ウェアハウス
Dashboard`dashboard_task`公開済みダッシュボードの更新サーバーレスまたは Pro の SQL ウェアハウス
dbt`dbt_task`dbt コマンドSQL ウェアハウスと dbt を動かすコンピュート
Pipeline`pipeline_task`既存のパイプラインパイプライン側の設定どおり
Run job`run_job_task`別のジョブ不要 (親タスク)
If/else`condition_task`条件式の評価不要
For each`for_each_task`ネストタスクの反復ネストタスク側の設定どおり

真っ先に覚えるのは右端の列です。SQL タスクと Dashboard タスクは、サーバーレスまたは Pro の SQL ウェアハウスでしか動きません。Classic ウェアハウスは選べず、ジョブクラスタや汎用クラスタも指定できません。Dashboard タスクを構成するには、対象ダッシュボードへの CAN VIEW 以上の権限も必要です。

Notebook タスクのソースは Workspace か Git provider の2択です。Git を選ぶと相対パスは `/` や `./` で始めず `etl/bronze/ingest.py` のように書き、1つのジョブの全タスクで使えるリモートリポジトリは1つだけです。出力にも上限があり、全セルの合計が30MB、単一セルが8MB を超えると、その実行はキャンセルされ失敗になります。開発中に入れた `display()` を残したまま本番の全量データを流すと、処理そのものは正しくてもここで落ちます。

SQL タスクの実行対象はクエリ、アラート、`.sql` ファイルの3つで、`.sql` にはセミコロン区切りで複数文を書けます。結果は下流から `{{tasks.<task_name>.output.first_row.<column>}}` で先頭行の列値として読めますが、出力は1,000行かつ48KBまで、保持期間は7日です。Dashboard タスクのデータセットパラメータはタスクの入力欄では指定できず、あらかじめダッシュボードのフィルターウィジェットに紐付けておく必要があります。更新完了を知らせる Subscribers は、Dashboards の UI で設定した通知先とは別に管理されます。

Pipeline タスクが起動できるのは triggered モードのパイプラインだけで、continuous のパイプラインは対象外です。Full refresh を有効にすると完全更新として実行されます。1つのパイプラインは同時に1更新しか実行できないため、Pipeline タスクを含むジョブは Maximum concurrent runs を3にしても実質1実行に制限され、For each の中に置いた場合も Concurrency の値にかかわらず1反復ずつ進みます。Run job タスクは Job ドロップダウンから選んだ既存のジョブを起動しますが、3段を超える入れ子と、直接または間接に互いを起動する循環依存はサポートされません。

どこまでを1つのジョブに入れるか

先に物理的な上限を押さえます。数値そのものが問われます。

対象上限・既定補足
1ジョブのタスク数1,000100を超えるジョブの操作には Databricks CLI 0.244.0 以上、Python SDK 0.45.0 以上が必要
1コンピュート上の実行コンテキスト150超えるとエラー。複数のコンピュートへの分散が必要
ワークスペースの同時タスク実行2,000Run job と For each の親タスクは対象外
親タスクの同時実行750上の2,000とは別枠
保存済みジョブ数12,000ジョブ作成は1時間あたり10,000件まで
Maximum concurrent runs既定 1キューイングは既定で有効。あふれた実行は最大48時間まで待機

上限に余裕があっても、何でも束ねてよいわけではありません。判断の軸は失敗したときに一緒にやり直したい範囲かどうかです。取り込みと変換と集計は同じ日次データの運命共同体なので、1つのジョブにまとめて Repair run の対象範囲に収めます。逆に細かく切りすぎるとコンピュートの確保が毎回挟まるので、数十秒で終わる処理を20個に割っても遅くなるだけです。Repair run は同じ実行 ID の中で、失敗したタスクと未実行の下流タスクだけを再実行する機能です。10タスク中2つが失敗しても、成功済みの8つは結果が再利用されます。束ねる範囲がそのまま部分再実行の単位になる、と考えると設計しやすくなります。

なお、1,000という上限は「そこまで入る」という意味であって「そこまで入れてよい」という意味ではありません。数百のタスクを1つのジョブに詰めると実行履歴の一覧は重くなり、個々のタスクまで追えなくなります。層ごと、ドメインごとにジョブを割るほうが運用は軽くなります。

逆に、別チームが所有していて更新頻度も違うテーブルの処理を同じジョブに入れると、相手側の都合で自分のジョブが赤くなり、通知も自分に飛んできます。この場合はジョブを分け、Run job タスクで呼ぶか、上流テーブルを見張る Table update トリガーで起動します。ジョブをまたぐ依存は、依存関係ではなく起動条件で表現するということです。トリガーはジョブ単位の設定でタスクごとには持てないので、毎朝7時に必ず動かしたい処理とファイル到着時だけ動かしたい処理を無理に束ねると、片方に合わせた空振りの実行がコストとして積み上がります。

確認 — 穴あけ 3

0 / 3

空欄を押すと選択肢が出ます。間違えても減点はありません。

SQL タスクを実行するには、サーバーレスまたは の SQL ウェアハウスが必要です。

1つのジョブに定義できるタスクは最大 で、100を超えると新しい CLI や SDK が必要になります。

Notebook タスクは全セルの出力合計が 、単一セルの出力が8MBを超えると失敗します。

DAG を組む — depends_on、Run if 6条件と Excluded、If-else 条件タスク、For each

依存を宣言していないタスクは、ジョブが始まった瞬間に全部いっせいに走り出します。「変換タスクが参照するテーブルが見つからない」という失敗の大半は、コードの問題ではなく `depends_on` の書き忘れです。この依存の網は DAG (有向非巡回グラフ。矢印をたどっても元の位置に戻れない形の図) と呼ばれます。もう1つ決めるべきは、上流が失敗したら止めたいのか、失敗しても通知だけは走らせたいのかです。DAG の設計とは、この2つを書き下ろす作業です。

depends_on は後続タスク側に書く

依存は必ず待つ側のタスクに書きます。先行タスクに「次はこれを動かす」と書くフィールドはなく、`precedes` や `wait_for` は存在しません。次の例は、ジョブ定義を YAML ファイルで管理する仕組みである Declarative Automation Bundles での書き方です。

tasks:
  - task_key: ingest
  - task_key: clean_a
    depends_on:
      - task_key: ingest
  - task_key: clean_b
    depends_on:
      - task_key: ingest
  - task_key: publish
    depends_on:
      - task_key: clean_a
      - task_key: clean_b

この定義では `ingest` の成功後に `clean_a` と `clean_b` が並列で起動し、両方が成功してから `publish` が走ります。合流の条件を別に書かなくてよいのは、`depends_on` に2つ並べれば既定の Run if「All succeeded」が両方の成功を要求してくれるからです。ここで押さえるべきは、`tasks` 配列の並び順は画面の表示順を決めるだけで、実行順序には一切関係しない点です。並び順で順序を表現したつもりの定義は、実際には全タスクが同時に起動します。

日本語の要件からの読み替えは機械的です。「A の完了後に B と C を並列で起動し、B の完了後に D を起動」なら、B と C の `depends_on` がそれぞれ `[A]`、D の `depends_on` が `[B]` です。矢印の向きを逆に書くと上流が下流を待つ形になり、永久に起動しない定義ができあがります。粒度は再実行したい単位で割るのが目安です。

Run if の6条件と、満たされなかったときの状態

依存を張っただけのタスクは、Run if の既定値 All succeeded で動きます。上流が1つでも失敗すると下流は Upstream failed になり、実行されません。困るのは、失敗したときにこそ動いてほしい通知タスクです。UI のラベルだけでなく、API と YAML の `run_if` に書く大文字の値も一緒に覚えます。

UI の表記`run_if` の値実行される条件不成立時の状態
All succeeded (既定)`ALL_SUCCESS`すべての依存が実行され成功Upstream failed
At least one succeeded`AT_LEAST_ONE_SUCCESS`依存の1つ以上が成功Upstream failed
None failed`NONE_FAILED`失敗した依存がなく、1つ以上が実行済みUpstream failed
All done`ALL_DONE`結果を問わず依存がすべて完了不成立になる場面なし
At least one failed`AT_LEAST_ONE_FAILED`依存の1つ以上が失敗Excluded
All failed`ALL_FAILED`すべての依存が失敗Excluded

境目は1本です。成功を要求する条件が満たされないと Upstream failed、失敗を要求する条件が満たされないと Excluded になります。前者はジョブ全体も失敗として扱われ、後者はジョブの成否に影響しません。上流が失敗したときだけ通知したいタスクに At least one failed を設定すれば、平常時は Excluded として静かに除外され、ジョブは成功のまま終わります。All done は依存の結果をいっさい問わないので、後片付けや一時ファイルの削除に向きます。割合で判定する条件は存在しません。

None failed と All succeeded は紛らわしいので分けておきます。None failed は「失敗した依存がゼロで、かつ少なくとも1つは実行された」で、スキップされた依存が混ざっていても成立します。All succeeded は全依存の成功を求めるため、こちらのほうが厳しい条件です。1つのタスクで後片付けと通知を兼ねることはできないので、役割ごとにタスクを分けます。

Excluded は成功として下流に伝わる

Excluded は失敗ではなく、条件が合わずに除外された状態です。この区別が下流の判定を左右します。

  • Excluded の上流は、下流の Run if を評価するとき成功として扱われます。
  • Upstream failed と Upstream canceled の上流は、スキップではなく失敗として扱われます。
  • 依存がすべて Excluded になったタスクは、Run if の設定にかかわらず自分も Excluded になります。除外は依存の鎖に沿って下流へ連鎖します。

失敗時の通知ブランチを長く伸ばしたときに末尾のタスクが動かない理由は、たいてい3つめです。タスクを一時的に走らせたくないときは、削除ではなく無効化を使います。無効化すると設定と実行履歴を残したまま実行だけがスキップされ、下流タスクは自分の Run if 条件に照らして実行可否が判定されます。

If/else 条件タスクで値を見て分岐する

Run if が見ているのは上流の成否だけです。「行数が1,000以上なら全件ロード、そうでなければ差分ロード」のように値で分岐したいときは If/else 条件タスクを使います。ノートブックの中に `if` 文を埋め込むと、どちらに分岐したのかが実行画面に出ません。

条件は左オペランド・演算子・右オペランドの3要素です。`condition_task` には `left`、`op`、`right` を指定します。`op` に入る値は `EQUAL_TO`、`NOT_EQUAL`、`GREATER_THAN`、`GREATER_THAN_OR_EQUAL`、`LESS_THAN`、`LESS_THAN_OR_EQUAL` の6つです。オペランドにはジョブパラメータ、タスクパラメータ、タスク値の動的値参照を置けます。SQL 文や Python の式は書けません。

tasks:
  - task_key: check_rows
    condition_task:
      left: "{{tasks.count_rows.values.n}}"
      op: GREATER_THAN_OR_EQUAL
      right: "1000"
  - task_key: load_full
    depends_on:
      - task_key: check_rows
        outcome: "true"

分岐先を決めるのは `depends_on` の `outcome` です。指定できる値は `"true"` か `"false"` で、上流が If/else 条件タスクのときだけ意味を持ちます。UI では下流タスクの Depends on に `check_rows (true)` と表示されます。上流の成否を見る `run_if` とは役割が違います。

`==` `!=` は文字列比較、`>``>=``<``<=` は数値比較です。したがって `12.0 == 12` は false になり`12.0 >= 12` は true になります。しきい値の判定に等価比較を使う表記の揺れで静かに false になり後続が丸ご走らなくなります。ブール値は `"true"` `"false"` いう文字列に直されてから比較されます。

For each で同じ処理を回す

リージョンごとに同じ処理を回したいとき、タスクを対象の数だけコピーすると定義がすぐ壊れます。For each タスクは `inputs` に配列を渡し、`task` に定義したネストタスクを要素数だけ繰り返します。`inputs` の与え方は3通りで、それぞれ上限が違います。

与え方記法上限
JSON 配列を直接書く`["JP", "US", "DE"]`UI で5,000文字
上流のタスク値を参照する`{{tasks.<task_name>.values.<key>}}`48 KB
ジョブパラメータを参照する`{{job.parameters.<name>}}`10,000文字

ネストタスクの中では `{{input}}` が現在の反復の値全体を、`{{input.region}}` がオブジェクトの特定フィールドを指します。制約は3つです。第一に、Concurrency の既定は1で、指定しない限り反復は1つずつ順番に走り、所要時間は反復数に比例して伸びます。第二に、ネストタスクに別の For each タスクを置くことはできません。第三に、ネストタスクは下流から依存先にできないため、全反復の完了を待ちたい集計タスクは For each タスク本体に依存させます。ネストタスクが Pipeline タスクの場合は、Concurrency をいくつに設定しても1反復ずつになります。

分岐と反復の材料になるタスク値

分岐やループの条件は上流の計算結果から決まります。上流のノートブックが `dbutils.jobs.taskValues.set(key="n", value=1500)` で値を置き、下流はパラメータ欄に `{{tasks.count_rows.values.n}}` を書いて受け取ります。`set` と `get` は Python 関数で、言語に Python を選んだノートブックの中でしか呼べません。置かれた値のほうは、パラメータをサポートする他のタスク種別からも動的値参照で読めます。渡せるのは JSON として表現できる値で、その JSON 表現のサイズは48KiBを超えられません。DataFrame をそのまま渡すことはできません。

下流のノートブックで直接読む書き方は `dbutils.jobs.taskValues.get(taskKey="count_rows", key="n", debugValue=0)` です。ただし Databricks が推奨するのは、下流タスクのパラメータ欄に動的値参照を書く方法です。タスク名が変わったときに直す場所が1か所で済みます。

ここまでが構成の話です。組んだジョブをどのトリガーで動かし、失敗した実行を Repair run でどう救うかは次章で扱います。UI と Jobs API の機能網羅は記事「Lakeflow Jobs とは?ワークフロー管理完全ガイド」、宣言型パイプラインとの使い分けは記事「DLT と Workflows の違い」、コンピュートの選び方は記事「クラスタの種類」に譲ります。

確認 — 穴あけ 3

0 / 3

空欄を押すと選択肢が出ます。間違えても減点はありません。

Run if に At least one failed を選んだタスクは、上流がすべて成功した平常時には になります。

UI の「All succeeded」に対応する API / YAML の run_if の値は です。

If/else 条件タスクで、タスク値が 12.0 のとき条件 12.0 == 12 の評価結果は です。

この章のまとめ

  1. depends_onは後続タスク側に書く。tasks配列の並び順は実行順序に無関係
  2. Run ifの成功系未達はUpstream failed、失敗系未達はExcludedで下流に連鎖
  3. API値はALL_SUCCESS / ALL_DONE / AT_LEAST_ONE_FAILED などの大文字表記
  4. If/elseの==は文字列比較、>=は数値比較。分岐先はoutcomeのtrue/falseで指定
  5. For eachのConcurrency既定は1。下流はFor each本体に依存させる

この端末にだけ保存されます(登録不要)

この章の根拠

最終確認 2026-08-09 / 対応バージョン DEA 2026-05-04 改訂版

コース全体

  1. Databricks とレイクハウスの全体像 — なぜ必要か、何がどこに属し、どの計算資源で動くか
  2. データはどこに、どんな形で保存されるか — テーブルの実体と Delta Lake の内部
  3. 計算資源を作り込み、コードを書く場所を決める — クラスタ設定とノートブック/ローカル IDE
  4. Spark はどう動き、SQL で何をどこまで書けるか
  5. PySpark でデータを加工し、遅いコードを見抜く
  6. 流れ込むデータを受け止める — Structured Streaming と Auto Loader のファイル検出
  7. 取り込み時にスキーマをどう扱うか — Auto Loader のスキーマ進化と COPY INTO
  8. ファイル以外からも取り込む — read_files・Lakeflow Connect・JDBC と API
  9. 生データを使える形に育てる — メダリオン設計と宣言的パイプライン
  10. 処理を1つのジョブに束ねる — タスク種別と依存関係の設計
  11. ジョブを動かし、失敗から立て直す — トリガー・リトライ・パラメータ
  12. 書いたものを安全に本番へ届ける — Git 連携とデプロイの自動化
  13. 誰に何を見せるか — Unity Catalog のアクセス制御とアイデンティティ統制
  14. 誰が何をしたか、いくらかかったか、どこで詰まったかを見る
  15. テーブルを保守して速くする — 運用コマンド・保持期間・レイアウト設計・障害復旧
  16. DEA 直前仕上げ — 方式選定・出題範囲対応表・数値総まとめ・引っかけの型