Mímisbrunnr知恵の泉

← データエンジニアリング 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:データ取り込み(バッチ・CDC) | 関連:データ品質とテストワークフローオーケストレーション

要点(BLUF)

概念 ── なぜ再実行が前提なのか

分散・スケジュール実行では、ジョブが途中で死ぬのは日常です。リトライ(自動再実行)で回復しますが、そのとき**「もう半分書き込んでいたら?」**が問題になります。べき等なら「もう一度全部流す」だけで正しい状態に収束するので、復旧が圧倒的に楽になります。

flowchart LR
    R["ジョブ失敗・途中停止"] --> RT["リトライ(再実行)"]
    RT --> Q{"べき等?"}
    Q -->|はい| OK["何回流しても同じ結果 -> 安全"]
    Q -->|いいえ| NG["重複・二重計上 -> データ破損"]

仕組み ── 素朴INSERT vs UPSERT

同じバッチを2回流して比べます。

-- 非べき等:追記なので再実行で二重に
INSERT INTO target_naive(id,val) VALUES (1,10),(2,20),(3,30);

-- べき等:主キー衝突時は更新。何回流しても3行のまま
INSERT INTO target_upsert(id,val) VALUES (1,10),(2,20),(3,30)
ON CONFLICT(id) DO UPDATE SET val=excluded.val;

実行結果(実機・SQLite/各2回実行):

素朴INSERTを2回 -> 6 行(重複・3行のはずが倍)
UPSERTを2回     -> 3 行(べき等・3行のまま)

UPSERTは「無ければ挿入、有れば更新」。同じ鍵のデータが何度来ても、最終状態は1回流したのと同じ。これが冪等設計の最小形です。

設計の勘所 ── 冪等にする4つの型

手法やり方向く場面
UPSERT/MERGE主キー衝突で更新行単位の取り込み
delete-insert対象パーティションを消してから入れ直す日次パーティション再生成
overwriteパーティション/テーブルを丸ごと置換バッチ全再計算
自然キー+去重一意キーで重複排除してから書くイベント取り込み

ポイントは「書き込みを“追記”でなく“その鍵の最終状態への収束”にする」こと。日次バッチなら「6月3日分を再計算するなら、6月3日のパーティションを消して入れ直す」。途中で死んでも、もう一度流せば同じ6月3日分になります。Airflowの再実行(→ ワークフローオーケストレーション)が安心して使えるのは、各タスクが冪等だからです。

なぜそうするか ── exactly-once の現実解

なぜ「ちょうど1回(exactly-once)」を保証しようとしないのか。分散環境で送達を厳密に1回にするのは非常に難しい(→ 分散システム)。現実解は「at-least-once(最低1回)配送+べき等な受け手」。重複が来ることを前提に、受け手が重複を吸収すれば、実質的にexactly-onceと同じ結果になります。「壊れない配送」を作るより「壊れた配送が来ても結果が壊れない受け手」を作る方が、はるかに堅牢です。

⚠️ よくある落とし穴

対応ラボ

data-engineering-study/labs/04_etl_pipeline.pyPYTHONIOENCODING=utf-8 で実行・素朴INSERT二重化とUPSERT冪等を確認済み)。

関連

第4章 ETLとデータパイプライン 目次