外部API連携における実行状態の管理とアトミックな更新による冪等性の確保

Tech

{ “engine”: “gemini-1.5-pro”, “intent”: “technical_documentation_sre”, “style”: “professional_sre_guide”, “reliability”: “unverified_draft”, “focus”: “idempotency_and_atomic_updates” }

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

外部API連携における実行状態の管理とアトミックな更新による冪等性の確保

【導入と前提】

外部APIへの二重リクエストを防ぎ、障害発生時の安全な再試行を可能にするため、状態ファイルを用いた冪等な自動化スクリプトを構築します。

  • 実行環境: GNU/Linux (Bash 4.4+)

  • 必須ツール: jq (JSON処理), curl (HTTPクライアント), coreutils (mv, rm等)

【処理フローと設計】

graph TD
A["スクリプト開始"] --> B{"状態ファイルの確認"}
B -->|存在しない| C["空の状態ファイルを初期化"]
B -->|存在| D["APIから最新データを取得"]
C --> D
D --> E["jqで未処理の差分を抽出"]
E --> F{"未処理分はあるか?"}
F -->|Yes| G["APIアクション実行"]
G --> H["テンポラリファイルへ状態書込"]
H --> I["mvコマンドによるアトミック更新"]
F -->|No| J["終了処理"]
I --> J
  1. 差分抽出: 前回の実行で保存した「処理済みIDリスト」と、APIから取得した最新リストを jq で比較し、新規項目のみを特定します。

  2. アトミック更新: 状態ファイルへの書き込み中にクラッシュしてもデータが破損しないよう、別名で保存してから mv で置換します。

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

#!/bin/bash


# ==============================================================================


# API Integration Script with Idempotency and Atomic State Updates


# ==============================================================================

set -euo pipefail
IFS=$'\n\t'

# 設定

readonly STATE_FILE="/var/lib/my-app/processed_ids.json"
readonly TEMP_STATE_FILE="${STATE_FILE}.tmp"
readonly API_ENDPOINT="https://api.example.com/v1/resources"
readonly API_TOKEN_FILE="/etc/my-app/api_token"

# クリーンアップ処理

trap 'rm -f "${TEMP_STATE_FILE}"' EXIT

# 状態ファイルの初期化(存在しない場合)

if [[ ! -f "${STATE_FILE}" ]]; then
    echo '[]' > "${STATE_FILE}"
fi

# 1. 最新データの取得


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

response=$(curl -sSLf -H "Authorization: Bearer $(cat "${API_TOKEN_FILE}")" "${API_ENDPOINT}")

# 2. 未処理IDの抽出 (jqを使用した集合演算)


# 状態ファイルにあるIDを除外して、新規IDのリストを作成

new_ids=$(echo "${response}" | jq -r --argfile old "${STATE_FILE}" \
    '.[] | select(.id as $id | $old | index($id) | not) | .id')

if [[ -z "${new_ids}" ]]; then
    echo "処理すべき新規項目はありません。"
    exit 0
fi

# 3. 各項目に対するアクションの実行

for id in ${new_ids}; do
    echo "Processing ID: ${id}..."

    # APIアクション実行例 (POST)


    # --retry: 接続失敗時のリトライ回数

    curl -sSLf -X POST "${API_ENDPOINT}/${id}/activate" \
        -H "Authorization: Bearer $(cat "${API_TOKEN_FILE}")" \
        --retry 3

    # 4. 状態ファイルのアトミックな更新


    # 処理が成功するたびに、既存の状態にIDを追加して一時ファイルへ出力

    jq --arg id "${id}" '. += [$id]' "${STATE_FILE}" > "${TEMP_STATE_FILE}"

    # POSIXシステムにおけるmvはアトミックな操作であるため、


    # 書き込み途中の不完全なファイルで上書きされるリスクを排除できる

    mv "${TEMP_STATE_FILE}" "${STATE_FILE}"
done

echo "全ての処理が正常に完了しました。"

systemdによる自動実行(タイマー設定)

定期実行を行う場合は、Cronよりも詳細なログ管理が可能な systemd.timer を推奨します。

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

[Unit]
Description=API Sync Task with Idempotency

[Service]
Type=oneshot
ExecStart=/usr/local/bin/api-sync.sh
User=appuser
Group=appuser

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

[Unit]
Description=Run API Sync every 15 minutes

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

[Install]
WantedBy=timers.target

【検証と運用】

正常系の確認

実行後、状態ファイルにIDが記録されているか、APIログ(またはスクリプトの標準出力)を確認します。

# 状態ファイルの中身を確認

jq '.' /var/lib/my-app/processed_ids.json

# 次回の実行で「処理すべき項目はありません」と表示されるか確認

sudo /usr/local/bin/api-sync.sh

エラー確認

systemd を利用している場合、標準出力および標準エラー出力は journalctl で一括管理されます。

# 実行ログの確認

journalctl -u api-sync.service -f

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

  1. 権限問題: 状態ファイルを保存するディレクトリに、実行ユーザーの書き込み権限が必要です。sudo で実行する場合は set -e によりエラー発生時に即停止することに注意してください。

  2. APIトークンの秘匿: スクリプト内に直接トークンを書かず、環境変数または読み取り制限のかかったファイル(600)から読み込むようにします。

  3. 部分的な失敗: ループ内で特定のIDの処理が失敗した場合、set -e によりスクリプト全体が停止します。その際、失敗したIDは状態ファイルに記録されないため、次回実行時に再度処理対象となり、冪等性が維持されます。

  4. ディスクフル: mv 前の jq 出力時にディスク容量が不足していると、不完全な一時ファイルが作成されます。この際、set -o pipefail が設定されていれば、エラーとして検知可能です。

【まとめ】

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

  1. 外部の状態管理: スクリプトのメモリ上ではなく、外部ファイルに処理済みフラグを保持する。

  2. アトミックな置換: ファイル更新時は「一時ファイル作成 + mv」のパターンを徹底する。

  3. jqによる厳密なフィルタリング: APIレスポンスを直接ループに回さず、既存の状態との差分のみを抽出する。

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

コメント

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