外部API連携における冪等性を保証するCLIスクリプト設計:状態管理とアトミック更新の自動化

Tech

{ “style”: “technical-sre”, “requested_elements”: [ “mermaird_flow”, “bash_idempotency”, “atomic_file_update”, “systemd_integration” ], “focus”: “stability-and-reliability” } 本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

外部API連携における冪等性を保証するCLIスクリプト設計:状態管理とアトミック更新の自動化

【導入と前提】

外部APIからデータを取得・同期する際、二重実行や中断によるデータ重複を防ぎ、状態ファイルを用いて処理の継続性を確保します。

  • 実行環境: GNU/Linux (Ubuntu 22.04+, RHEL 8+)

  • 必須ツール: bash (4.4+), curl, jq, coreutils (mktemp, mv)

【処理フローと設計】

graph TD
    A["開始"] --> B{"状態ファイル確認"}
    B -- 存在しない --> C["初期オフセット設定"]
    B -- 存在する --> D["前回完了位置を読込"]
    D --> E["APIリクエスト実行"]
    C --> E
    E --> F{"レスポンス確認"}
    F -- 成功 --> G["一時ファイルへ新状態保存"]
    G --> H["アトミックな移動 mv"]
    H --> I["終了"]
    F -- 失敗 --> J["エラーログ出力・リトライ"]

この設計では、状態更新に「一時ファイルへの書き込み」と「リネーム(mv)」を組み合わせることで、書き込み中のクラッシュによる状態ファイルの破損(アトミック性の欠如)を防止します。

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

#!/usr/bin/env bash

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


# -e: エラーで即終了, -u: 未定義変数参照でエラー, -o pipefail: パイプ内のエラーを拾う

set -euo pipefail

# --- 変数定義 ---

readonly STATE_FILE="/var/lib/api-sync/last_id.json"
readonly API_URL="https://api.example.com/v1/events"
readonly AUTH_TOKEN="${API_TOKEN:-default_token}"
readonly TMP_STATE=$(mktemp)

# 終了時に一時ファイルを確実に削除

trap 'rm -f "$TMP_STATE"' EXIT

# --- 状態の読み込み ---

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

echo "Starting sync from ID: $LAST_ID"

# --- APIリクエストの実行 ---


# -s: 進捗非表示, -S: エラー表示, -L: リダイレクト追従, -f: HTTPエラーで失敗

RESPONSE=$(curl -sSLf \
    -H "Authorization: Bearer $AUTH_TOKEN" \
    -G --data-urlencode "since_id=$LAST_ID" \
    "$API_URL")

# --- データ処理と状態更新 ---


# 処理対象があるか確認

NEW_LAST_ID=$(echo "$RESPONSE" | jq -e -r '.data[-1].id // empty')

if [[ -n "$NEW_LAST_ID" ]]; then

    # 1. 一時ファイルに新しい状態を書き込む

    echo "{\"last_processed_id\": \"$NEW_LAST_ID\", \"updated_at\": \"$(date -Iseconds)\"}" > "$TMP_STATE"

    # 2. アトミックにファイルを置換(mvは同一FS内でアトミック)

    mv "$TMP_STATE" "$STATE_FILE"

    echo "Successfully updated state to ID: $NEW_LAST_ID"
else
    echo "No new data to process."
fi

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

/etc/systemd/system/api-sync.service として登録することで、OSレベルでの管理が可能になります。

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

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

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

Restart=on-failure
RestartSec=30s

[Install]
WantedBy=multi-user.target

【検証と運用】

  1. 正常系の確認: スクリプトを手動実行し、STATE_FILE が更新されることを確認します。

    jq . /var/lib/api-sync/last_id.json
    
  2. ログ確認: systemd 経由で実行している場合は、journalctl を使用して実行履歴とエラーを追跡します。

    journalctl -u api-sync.service -f
    

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

  • 権限問題: 状態ファイルを保存するディレクトリ(例: /var/lib/api-sync)は、実行ユーザーが書き込み権限を持っている必要があります。chown で適切に設定してください。

  • ディスクフル: 状態ファイルの書き込み中にディスクがいっぱいになると、mv は成功しても内容は不完全になる可能性があります。重要な環境では df チェックを事前に入れることを検討してください。

  • APIトークンの秘匿: API_TOKEN はスクリプト内にハードコードせず、EnvironmentFile(権限 600 推奨)から読み込むようにしてください。

【まとめ】

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

  1. アトミック更新: mv コマンドによるファイル置換を用い、中途半端な状態保存を許さない。

  2. エラーハンドリング: set -euo pipefailtrap を駆使し、異常終了時にリソースをクリーンアップする。

  3. 状態の明示: どのデータまで処理したかを「外部の状態ファイル」に逃がし、スクリプト自体はステートレスに保つ。

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

コメント

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