{ “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
【検証と運用】
正常系の確認: スクリプトを手動実行し、
STATE_FILEが更新されることを確認します。jq . /var/lib/api-sync/last_id.json
ログ確認:
systemd経由で実行している場合は、journalctlを使用して実行履歴とエラーを追跡します。journalctl -u api-sync.service -f
【トラブルシューティングと落とし穴】
権限問題: 状態ファイルを保存するディレクトリ(例:
/var/lib/api-sync)は、実行ユーザーが書き込み権限を持っている必要があります。chownで適切に設定してください。ディスクフル: 状態ファイルの書き込み中にディスクがいっぱいになると、
mvは成功しても内容は不完全になる可能性があります。重要な環境ではdfチェックを事前に入れることを検討してください。APIトークンの秘匿:
API_TOKENはスクリプト内にハードコードせず、EnvironmentFile(権限 600 推奨)から読み込むようにしてください。
【まとめ】
運用の冪等性を維持するための3つのポイント:
アトミック更新:
mvコマンドによるファイル置換を用い、中途半端な状態保存を許さない。エラーハンドリング:
set -euo pipefailとtrapを駆使し、異常終了時にリソースをクリーンアップする。状態の明示: どのデータまで処理したかを「外部の状態ファイル」に逃がし、スクリプト自体はステートレスに保つ。

