外部API連携における「冪等性」を担保するステート管理とアトミック更新の自動化

Tech

{ “expert_role”: “SRE / DevOps Engineer”, “focus”: “Idempotent API Integration & Atomic State Management”, “technical_stack”: [“Bash”, “jq”, “curl”, “systemd”, “POSIX utils”], “security_level”: “Operational Hardening” } V1.2.0-2024-SRE-BEST-PRACTICE

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

外部API連携における「冪等性」を担保するステート管理とアトミック更新の自動化

【導入と前提】

API連携の二重実行を防止し、中断後の再開を安全に行うための状態管理(State Management)手法を実装します。

  • 前提条件:

    • OS: Linux (Ubuntu 22.04 LTS / RHEL 9 等)

    • ツール: bash (4.x+), jq (JSON処理), curl (通信), systemd

【処理フローと設計】

graph TD
    A["開始"] --> B{"ステートファイル確認"}
    B -->|存在| C["最終処理済みIDを取得"]
    B -->|不在| D["初期値設定"]
    C --> E["APIより差分データを取得"]
    D --> E
    E --> F{"新規データあり?"}
    F -->|No| G["終了"]
    F -->|Yes| H["ループ処理実行"]
    H --> I["APIリクエスト送信"]
    I --> J["一時ファイルへ状態書き出し"]
    J --> K["mvによるアトミック更新"]
    K --> H
    H --> L["全完了/終了"]

ステートファイルには最後に成功したリソースIDやタイムスタンプを保存します。更新時は一時ファイルを作成し、mv コマンドで上書きすることで、ディスクフルやクラッシュ時のファイル破損を防ぐ「アトミック更新」を実現します。

【実装:堅牢な自動化スクリプト】

#!/usr/bin/env bash

# --- 設定とエラーハンドリング ---

set -euo pipefail

# -e: エラーで即終了, -u: 未定義変数でエラー, -o pipefail: パイプ内エラーを捕捉

STATE_FILE="/var/lib/api-sync/last_id.json"
TEMP_STATE_FILE="${STATE_FILE}.tmp"
API_ENDPOINT="https://api.example.com/v1/resource"
API_TOKEN="${API_TOKEN:-default_token}"

# 終了時の一時ファイル削除

trap 'rm -f "$TEMP_STATE_FILE"' EXIT

# --- ステート読み込み ---

if [[ -f "$STATE_FILE" ]]; then
    LAST_ID=$(jq -r '.last_id' "$STATE_FILE")
else
    LAST_ID="0"
fi

# --- データ取得 ---


# --retry: 通信エラー時の再試行, -f: HTTPエラーをシェルエラーとして扱う, -s: 進捗非表示

response=$(curl -sfL --retry 3 \
    -H "Authorization: Bearer $API_TOKEN" \
    "${API_ENDPOINT}?since_id=${LAST_ID}")

# --- 処理ループ ---


# jqの-cで各要素を1行ずつ出力し、readで処理

echo "$response" | jq -c '.items[]' | while read -r item; do
    current_id=$(echo "$item" | jq -r '.id')

    # 業務ロジック(例:外部サービスへの通知)

    echo "Processing ID: $current_id"

    # --- ステートのアトミック更新 ---


    # 1. 一時ファイルに現在の進捗を書き出す

    echo "{\"last_id\": \"$current_id\", \"updated_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$TEMP_STATE_FILE"

    # 2. mvによるアトミック(不可分)な置換


    # 同一ファイルシステム内であれば、この操作中に電源が切れてもファイルは旧か新のどちらかで維持される

    mv "$TEMP_STATE_FILE" "$STATE_FILE"
done

echo "Synchronization completed successfully."

systemd タイマー設定例

定期実行を堅牢にするため、cronではなくsystemdを利用します。

# /etc/systemd/system/api-sync.service

[Unit]
Description=Idempotent API Sync Service
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/api-sync.sh
Environment="API_TOKEN=secret_value_here"
User=syncuser
Group=syncuser
StateDirectory=api-sync

# /etc/systemd/system/api-sync.timer

[Unit]
Description=Run API Sync every 5 minutes

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target

【検証と運用】

  1. 正常系の確認:

    • スクリプト実行後、cat /var/lib/api-sync/last_id.json でIDが更新されているか確認。

    • journalctl -u api-sync.service で実行ログ(標準出力)を確認。

  2. 冪等性の検証:

    • 実行中に Ctrl+C で強制終了し、再実行した際に「前回完了したID」から再開されることを確認。
  3. エラーハンドリングの確認:

    • APIトークンを無効化し、set -e により適切に異常終了するか確認。

【トラブルシューティングと落とし穴】

  • 権限問題: StateDirectory=api-sync を指定すると、systemdは自動的に /var/lib/api-sync を作成し、実行ユーザに権限を与えます。手動実行時はパスの書き込み権限に注意してください。

  • 環境変数の露出: API_TOKEN はスクリプトに直書きせず、systemdの EnvironmentEnvironmentFile、またはシークレット管理ツールから注入してください。

  • ファイルシステムの境界: mv によるアトミック更新は、同一パーティション内である必要があります。/tmp 等から /var/lib へ移動させる場合はアトミック性が損なわれる(コピーになる)ため、同じディレクトリ内に一時ファイルを作成するのが定石です。

【まとめ】

運用の冪等性を維持するための3つのポイント:

  1. ステートの外部化: どこまで処理したかをプロセス外部(ファイル等)に記録し、再開可能にする。

  2. アトミックな書き込み: tempファイル作成 -> 内容書き込み -> mv の手順を徹底し、中途半端な状態を排除する。

  3. 安全なシェルオプション: set -euo pipefail で予期せぬ失敗を即座に検知し、後続の誤処理を防ぐ。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました