ふくふくHukuhuku LLC
EP.13Foundation 12分公開: 更新:

べき等性 (Idempotency):「同じ処理を 2 回実行しても結果が変わらない」を保証する

ジョブが失敗してリトライ → データが二重計上された。バッチが重複実行 → 売上が 2 倍に。データ基盤の悲劇の多くはべき等性の欠如が原因。Idempotency Key・MERGE・upsert・冪等な API 設計まで Python / SQL サンプル付き。

#べき等性#idempotency#データパイプライン#API
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

べき等性 (idempotency) は、データ基盤・API・分散システムの信頼性の核となる概念。本記事では「同じ処理を何度実行しても結果が変わらない」をどう設計・実装するかを、データパイプライン・SQL・API の各層で扱います。

1. なぜべき等性が必要か

  • ジョブのリトライ: ネットワーク失敗・タイムアウトで自動再実行
  • デプロイの重複: CI/CD の二重実行、手動オペレーションミス
  • メッセージキューの重複配信: SQS / Kafka は at-least-once が基本
  • ネットワーク分断: クライアントが「失敗」と判定したが実は成功していたケース
  • 人為的な再実行: 「とりあえず再起動」「もう一度走らせて」
実例: 二重課金事故

Stripe API への課金リクエスト がネットワーク timeout → クライアントがリトライ → 実は最初のリクエストも成功していた → 同じ顧客に 2 回課金。Stripe は Idempotency-Key ヘッダーでこれを防げる仕組みを提供しているが、使い忘れると事故る。

2. べき等な操作 vs 非べき等な操作

操作べき等?理由
SET x = 5 (代入)✅何度やっても x = 5
x = x + 1 (インクリメント)❌実行回数で値が変わる
DELETE FROM users WHERE id=1✅あっても無くても結果は「無い」
INSERT INTO orders ...❌毎回新しい行が増える
UPSERT (MERGE)✅存在すれば更新、無ければ挿入
DROP TABLE IF EXISTS✅存在チェック付き
HTTP GET✅同じ URL は同じレスポンス
HTTP DELETE✅削除済みでも 404 を返すだけ
HTTP POST (素朴)❌毎回新しいリソース作成
HTTP PUT✅リソース全体を上書き

3. SQL でべき等にする (UPSERT / MERGE)

PostgreSQL: ON CONFLICT
SQL
-- 非べき等: 重複実行で行が増えるINSERT INTO users (id, name, email)VALUES (1, '佐藤', 'sato@example.com');
-- べき等: 重複時は何もしない (or 更新)INSERT INTO users (id, name, email)VALUES (1, '佐藤', 'sato@example.com')ON CONFLICT (id) DO NOTHING;
-- べき等: 存在すれば更新 (UPSERT)INSERT INTO users (id, name, email)VALUES (1, '佐藤', 'sato@example.com')ON CONFLICT (id) DO UPDATESET name = EXCLUDED.name,    email = EXCLUDED.email;
BigQuery / Snowflake: MERGE
SQL
-- 標準 SQL の MERGE 文MERGE INTO target_table TUSING source_table S  ON T.id = S.idWHEN MATCHED THEN  UPDATE SET T.value = S.value, T.updated_at = CURRENT_TIMESTAMP()WHEN NOT MATCHED THEN  INSERT (id, value, created_at)  VALUES (S.id, S.value, CURRENT_TIMESTAMP());
-- これを 100 回実行しても結果は同じ

4. dbt incremental モデルでのべき等性

dbt: unique_key を指定して merge 化
SQL
{{    config(        materialized='incremental',        unique_key='order_id',        on_schema_change='sync_all_columns'    )}}
SELECT    order_id,    user_id,    amount,    created_atFROM {{ source('raw', 'orders') }}{% if is_incremental() %}  WHERE created_at >= (SELECT MAX(created_at) FROM {{ this }})        - INTERVAL 1 DAY  -- オーバーラップで取りこぼし防止{% endif %}-- unique_key='order_id' を指定すると、dbt が MERGE 文を生成-- 重複実行しても行は重複しない

5. Python (Airflow タスク) でべき等にする

「処理済みリスト」テーブルでチェック
Python
def process_order(order_id, db):    # 既に処理済みかチェック    cursor = db.execute(        "SELECT 1 FROM processed_orders WHERE order_id = %s",        (order_id,)    )    if cursor.fetchone():        print(f"order {order_id} は処理済み、skip")        return
    # トランザクション内で「処理 + 処理済みマーク」を一括    with db.transaction():        # 実際の処理 (例: 在庫減らす、メール送る)        db.execute(            "UPDATE inventory SET stock = stock - 1 WHERE id = %s",            (order_id,)        )        # 処理済みマーク        db.execute(            "INSERT INTO processed_orders (order_id, processed_at) VALUES (%s, NOW())",            (order_id,)        )

6. API のべき等性 (Idempotency-Key)

Stripe ライクな Idempotency-Key 実装
Python
from flask import Flask, request, jsonifyimport hashlib, json
app = Flask(__name__)idempotency_cache = {}  # 本番は Redis 等
@app.post('/charge')def charge():    key = request.headers.get('Idempotency-Key')    if not key:        return jsonify({'error': 'Idempotency-Key required'}), 400
    # 同じキーで過去に処理した結果をキャッシュから返す    if key in idempotency_cache:        return jsonify(idempotency_cache[key]), 200
    # 実際の課金処理    body = request.json    result = process_charge(body['amount'], body['customer_id'])
    # 結果をキャッシュ (24h 等)    idempotency_cache[key] = result    return jsonify(result), 201
# クライアント側import requests, uuidkey = str(uuid.uuid4())  # クライアントが生成r = requests.post('/charge',    headers={'Idempotency-Key': key},    json={'amount': 1000, 'customer_id': 'c1'})# リトライ時も同じ key を送る → 2 回目以降はキャッシュ返答

7. メリットとデメリット

観点べき等化のメリットコスト/デメリット
信頼性✅ 二重実行事故が消える—
運用✅ 「とりあえず再実行」で安全—
実装コスト—❌ unique key 設計、idempotency table 管理が増える
書込み速度—❌ MERGE は INSERT より遅い (重複チェック分)
ストレージ—❌ Idempotency-Key の保管 (24h 程度) が必要
デバッグ—❌ 「実行されたか」の判定が unique key 経由に

8. べき等にできない処理の対策

  • メール送信: 送信前に sent_log テーブルでチェック (「user A に件名 X を当日送ったか」)
  • 外部 API 課金: Idempotency-Key 必須、API 側が対応していなければ自前で記録
  • プッシュ通知: 多少の重複は許容するか、message_id でクライアント側で deduplication
  • Webhook 受信: at-least-once 前提で受信側で重複検出 (event_id 記録)
  • ファイルアップロード: ファイルハッシュで重複チェック

夜間のAI処理でも、同じ入力を再実行したときの成果物の扱いを決めておく必要があります。DigitalOcean Managed Agentsの導入例では、入力の識別情報を記録し、検査と外部への操作を分け、結果の保存を確認する構成を紹介しています。

9. べき等性の「落とし穴」

  • ❌ MERGE で updated_at = NOW() にすると、毎回更新時刻が変わる (べき等の定義から外れる)
  • ❌ AUTO_INCREMENT のみで重複検出 → ON CONFLICT が効かない、別の unique 制約必要
  • ❌ Idempotency-Key の TTL が短すぎる → リトライ時に既にキャッシュが消えていて二重実行
  • ❌ トランザクションを跨いだ複数処理 → 部分実行のリスク、SAGA パターンや outbox で対応
  • ❌ テストでべき等性を確認しない → 本番で初めて気づく

10. ふくふくの推奨

「全パイプライン処理にべき等性チェック」をルール化

ジョブごとに「リトライしたらどうなる?」を必ず PR レビューで確認。Airflow タスク・dbt incremental・API ハンドラの 3 つは、特にべき等性必須。describe_idempotency.md のような設計書テンプレを社内標準にすると守りやすい。

11. 関連記事

本 EP は読者リアクションに応じて、「分散トランザクション (SAGA / 2PC)」、「Outbox パターンの実装」、「Kafka exactly-once の実態」 などの続編を追加していきます。

よくある質問

べき等性とは何ですか?
**「同じ操作を 1 回でも N 回でも実行して、結果が同じになる」** 性質。例: 「ファイルを削除する」は何度やっても結果同じ (べき等)、「ファイルに 1 行追加する」は実行回数で結果が変わる (非べき等)。データ基盤・API・分散システムの信頼性の核。
なぜべき等性が重要?
**現代システムでは「リトライ」が前提**。ネットワーク失敗、ジョブクラッシュ、デプロイ重複等で、同じ処理が複数回実行される可能性が常にある。べき等でなければ「リトライしたら二重課金された」「再実行したらデータが 2 倍」のような事故が起きる。
INSERT は非べき等?
**そのままでは非べき等** (毎回新しい行が増える)。べき等にするには (1) 主キー or unique 制約 + INSERT IGNORE、(2) MERGE / UPSERT 構文、(3) Idempotency Key を別テーブルで管理、のいずれか。
API でべき等にする方法
**HTTP メソッドの設計**: GET / PUT / DELETE はべき等、POST は非べき等。POST をべき等にするには `Idempotency-Key` ヘッダーを使い、サーバ側で同キーの結果をキャッシュ。Stripe API などが採用する標準パターン。
Airflow / dbt でべき等性は?
**Airflow タスクは「失敗したらリトライ」が前提** なので、各タスクをべき等に書く必要がある。dbt は incremental モデルでは特に注意 (重複データが入らないよう unique_key を必ず指定)。フル refresh はそもそもべき等。
べき等にできない処理は?
**「メール送信」「外部 API への決済」** など、副作用が外部に出る処理は完全なべき等化が困難。対策: (1) Idempotency Key で「過去に送信済み」をチェック、(2) at-least-once + 受信側で重複検出、(3) 重要な処理は手動承認を挟む。
シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp