外部API連携の冪等性を保証する:状態ファイルのアトミック更新スクリプト

Tech

{ “logic_density”: “high”, “technical_accuracy”: “SRE-grade”, “style”: “professional_automation_draft”, “security_focus”: [“atomic_writes”, “credential_handling”, “error_trapping”] } 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

外部API連携の冪等性を保証する:状態ファイルのアトミック更新スクリプト

【導入と前提】

API連携の二重実行や不完全な更新を防ぐため、状態管理ファイルとアトミックなファイル置換を組み合わせた堅牢な同期CLIを構築します。

  • OS: POSIX準拠 (Linux/macOS)

  • 依存ツール: curl (7.71.0+ 推奨), jq (1.6+), coreutils

【処理フローと設計】

graph TD
    A["開始"] --> B{"状態ファイルの確認"}
    B -->|存在| C["前回の状態をロード"]
    B -->|不在| D["新規初期化"]
    C --> E["外部APIへのリクエスト"]
    D --> E
    E --> F{"レスポンス評価"}
    F -->|成功| G["一時ファイルへ書込"]
    G --> H["アトミックなrenameで本番反映"]
    H --> I["終了"]
    F -->|失敗| J["エラー処理/ログ記録"]
    J --> K["異常終了"]

APIレスポンスを直接状態ファイルに書き込むのではなく、一旦一時ファイル(.tmp)に保存し、mv コマンドでアトミックに(不可分に)更新することで、書き込み中のクラッシュによるファイル破損を防ぎます。

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

#!/usr/bin/env bash

# --- 安全のための初期設定 ---


# -e: エラー発生時に即座に終了


# -u: 未定義変数の参照をエラーとする


# -o pipefail: パイプ途中のエラーを無視しない

set -euo pipefail

# --- 設定項目 ---

STATE_FILE="/opt/app/state.json"
API_ENDPOINT="https://api.example.com/v1/sync"
API_TOKEN="${API_TOKEN:-}" # 環境変数から取得
BACKUP_DIR="/opt/app/backups"

# --- 必須コマンドの存在確認 ---

for cmd in curl jq; do
    if ! command -v "$cmd" > /dev/null 2>&1; then
        echo "Error: $cmd is not installed." >&2
        exit 1
    fi
done

# --- クリーンアップ処理 ---


# スクリプト終了時に一時ファイルを確実に削除

TMP_FILE=$(mktemp)
trap 'rm -f "$TMP_FILE"' EXIT

# --- メイン処理 ---

echo "Starting synchronization process..."

# 1. 前回の状態を取得 (ファイルがない場合は空のJSON)

LAST_SYNC=$( [ -f "$STATE_FILE" ] && jq -r '.last_sync' "$STATE_FILE" || echo "1970-01-01T00:00:00Z" )

# 2. APIリクエスト実行


# -s: 進捗表示を非表示


# -S: エラー時はエラーメッセージを表示


# -L: リダイレクトを追跡


# -f: HTTPエラー(4xx/5xx)時に異常終了(exit 22)させる


# --retry: 通信エラー等の一時的失敗時に3回再試行

RESPONSE=$(curl -sSLf --retry 3 \
    -H "Authorization: Bearer $API_TOKEN" \
    -H "Content-Type: application/json" \
    "$API_ENDPOINT?since=$LAST_SYNC")

# 3. データの加工と一時保存


# jq --arg: シェル変数を安全にJSONクエリへ渡す

echo "$RESPONSE" | jq --arg now "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
    '. + {last_sync: $now}' > "$TMP_FILE"

# 4. アトミックな更新


# 同一ファイルシステム内であれば、mv操作はシステムコールレベルでアトミック

mkdir -p "$BACKUP_DIR"
[ -f "$STATE_FILE" ] && cp "$STATE_FILE" "$BACKUP_DIR/state_$(date +%Y%m%d%H%M%S).json.bak"
mv "$TMP_FILE" "$STATE_FILE"

echo "Synchronization completed successfully."

systemdによる定期実行設定 (Timer)

Cronよりも詳細なログ記録と依存関係管理が可能な systemd を推奨します。

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

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

[Service]
Type=oneshot
ExecStart=/usr/local/bin/sync_script.sh
User=appuser
EnvironmentFile=/etc/default/api-sync

# 実行失敗時のリトライ設定

Restart=on-failure
RestartSec=30s

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

[Unit]
Description=Run API Sync every 15 minutes

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

[Install]
WantedBy=timers.target

【検証と運用】

  • 正常系の確認 STATE_FILE のタイムスタンプと中身の last_sync フィールドが更新されているか確認します。

    cat /opt/app/state.json | jq .
    
  • ログの確認 systemdを使用している場合、journalctl で実行履歴とエラー出力を追跡できます。

    journalctl -u api-sync.service -f
    
  • ドライランの実施 破壊的な変更を行う前に、mv 部分を cat に置き換えてレスポンス構造を確認してください。

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

  1. 書き込み権限: 状態ファイルを置くディレクトリに対して、実行ユーザー(例: appuser)に rwx 権限が必要です。

  2. ディスクフル: mv 前の mktempcp でディスク容量不足になると、更新が失敗します。監視対象に含めるべきです。

  3. 環境変数の露出: API_TOKEN はスクリプト内にハードコードせず、systemdEnvironmentFile(パーミッション 600)経由で読み込むか、シークレット管理ツールを使用してください。

  4. ファイルシステムの制約: 状態ファイルと一時ファイルが異なるパーティションにある場合、mv がコピー&削除の動作になり、アトミック性が失われます。必ず同一ディレクトリ内、または同一マウントポイント上で処理してください。

【まとめ】

  1. Atomic Move: 一時ファイル作成後に mv することで、中途半端な書き込み状態(破損)を排除する。

  2. Strict Error Handling: set -euo pipefailtrap により、予期せぬエラー時に「静かに失敗する」ことを防ぎ、リソースを解放する。

  3. Externalized State: 処理の「前回到達点」をコード外に保存し、APIリクエストのクエリパラメータに反映させることで冪等性を担保する。

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

コメント

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