【指数バックオフ再試行】MSXML2.ServerXMLHTTP によるHTTP通信障害の自動リトライとバックオフ制御の実装
現場の自動化ツールや業務システムにおいて、Web APIとの連携はもはや不可欠の要素だ。しかし、VBScript(WSH)によるHTTP通信で、こんな悪夢を経験したことはないだろうか?
「ネットワークのわずかな瞬断や、APIサーバーの瞬間的な高負荷(HTTP 500 / 503 / 429 Too Many Requests)で、スクリプト全体が異常終了した」
「ただ単純に `For` ループで3回リトライさせたら、サーバーに負荷を集中させてしまい、かえってブロックされた」
レガシーな実行環境と揶揄されがちなVBScriptであっても、プロトコルの特性を理解し、「指数バックオフ(Exponential Backoff)とジッター(Jitter)」を実装すれば、モダンなアプリケーションに引けを取らない極めて堅牢な通信モジュールを作り上げることが可能だ。
今回は、実務の現場で即座に使える、妥協なきプロダクションコードを伝授しよう。
—
なぜ「単純なリトライ」は悪手なのか?
通信エラーが起きたとき、素朴なエンジニアはこう書く。
‘ 【アンチパターン】絶対に真似してはいけないリトライ処理
For i = 1 to 3
Set http = CreateObject(“MSXML2.ServerXMLHTTP.6.0”)
http.Open “POST”, “https://api.example.com/v1/data”, False
http.Send payload
If http.Status = 200 Then Exit For
WScript.Sleep 1000 ‘ 1秒固定で待機
Next
このコードの何が問題か?
1. サーバーへのDDoS攻撃化: 複数台のクライアントPCでこのスクリプトが動いていた場合、障害時に一斉に「1秒おき」にリトライが発生し、復旧しようとしているサーバーをさらに叩きのめすことになる。
2. 瞬断への弱さ: ネットワークの輻輳(混雑)が原因である場合、短すぎる固定ウェイトでは何度リトライしても成功率は上がらない。
指数バックオフ(Exponential Backoff)という解
これを解決するのが指数バックオフだ。試行回数が増えるごとに、待機時間を $2^n$ のように指数関数的に増やしていく。さらに、複数のクライアントが同時にリトライするのを防ぐために「ランダムな揺らぎ(ジッター)」を付加するのが、大規模システムにおける鉄則である。
- 1回目の失敗: 約 2秒 待つ
- 2回目の失敗: 約 4秒 待つ
- 3回目の失敗: 約 8秒 待つ
この設計思想を、純粋なVBScriptの構文だけで美しく実装する。
—
プロダクションコード:堅牢なHTTP通信クラスの全貌
以下のコードは、エラーハンドリング、タイムアウト設定、HTTPステータスの厳密な判定、そして指数バックオフによる自動リトライをカプセル化した実践的なモジュールだ。
‘ ==============================================================================
‘ ファイル名: RobustHttpCaller.vbs
‘ 概要: MSXML2.ServerXMLHTTP を使用した指数バックオフ付きHTTPクライアント
‘ ==============================================================================
Option Explicit
‘ 実行テスト
Call Main()
Sub Main()
Dim url, payload, response
url = “https://httpbin.org/post” ‘ テスト用エンドポイント
payload = “{“”sample_key””: “”sample_value””}”
‘ リトライ付きPOST通信を実行(最大試行回数: 4回、初期ウェイト: 1000ms)
response = SendHttpWithExponentialBackoff(“POST”, url, payload, 4, 1000)
If Not IsEmpty(response) Then
WScript.Echo “通信成功!レスポンス:” & vbCrLf & response
Else
WScript.Echo “致命的なエラー: すべてのリトライが失敗しました。”
End If
End Sub
‘ ——————————————————————————
‘ 指数バックオフ制御付き HTTP送信関数
‘ ——————————————————————————
Function SendHttpWithExponentialBackoff(method, url, payload, maxRetries, baseWaitMs)
Dim http
Dim attempt, waitMs, success
Dim status
attempt = 0
success = False
SendHttpWithExponentialBackoff = Empty
Do While attempt < maxRetries
attempt = attempt + 1
Set http = Nothing
On Error Resume Next
Set http = CreateObject("MSXML2.ServerXMLHTTP.6.0")
If Err.Number <> 0 Then
‘ オブジェクト生成失敗などの致命的環境エラー
WScript.Echo “[CRITICAL] MSXML2.ServerXMLHTTP.6.0 のインスタンス化に失敗: ” & Err.Description
Exit Function
End If
‘ タイムアウト設定 (ミリ秒):
‘ 第1引数: Resolve, 第2引数: Connect, 第3引数: Send, 第4引数: Receive
http.setTimeouts 5000, 5000, 10000, 30000
http.Open method, url, False
http.setRequestHeader “Content-Type”, “application/json”
http.setRequestHeader “User-Agent”, “VBScript-RobustClient/1.0”
‘ 送信実行
If method = “POST” Or method = “PUT” Then
http.Send payload
Else
http.Send
End If
If Err.Number <> 0 Then
‘ 通信レベルのエラー (DNS名前解決失敗、接続タイムアウトなど)
WScript.Echo “[WARN] 試行 ” & attempt & “/” & maxRetries & ” – ネットワークエラー発生: ” & Err.Description
status = 0
Else
status = http.Status
End If
On Error GoTo 0
‘ レスポンスコードの評価
If status >= 200 And status < 300 Then
' 成功 (2xx)
SendHttpWithExponentialBackoff = http.responseText
success = True
Exit Do
ElseIf status = 429 Or status = 408 Or (status >= 500 And status <= 599) Then
' リトライ対象のステータス (429: Too Many Requests, 408: Request Timeout, 5xx: Server Error)
WScript.Echo "[WARN] 試行 " & attempt & "/" & maxRetries & " - サーバーが一時エラーを返返却しました (Status: " & status & ")"
ElseIf status >= 400 And status < 500 Then
' リトライしても無駄なクライアントエラー (400 Bad Request, 401 Unauthorized, 404 Not Found 等)
WScript.Echo "[ERROR] クライアントエラーを検出しました (Status: " & status & ")。リトライを中止します。"
Exit Do
Else
' その他想定外のステータス
WScript.Echo "[WARN] 試行 " & attempt & "/" & maxRetries & " - 不明なステータスコード: " & status
End If
' 最大試行回数に達していなければバックオフ待機
If attempt < maxRetries Then
' 指数バックオフ計算: baseWaitMs (2 ^ (attempt - 1))
' ここに簡易的なランダムジッター(±20%の揺らぎ)を付加する
Randomize
Dim jitter
jitter = 0.8 + (Rnd 0.4) ' 0.8 ~ 1.2 の係数
waitMs = CLng(baseWaitMs (2 ^ (attempt - 1)) jitter)
WScript.Echo "[INFO] " & waitMs & "ミリ秒後に再試行します..."
WScript.Sleep waitMs
End If
Loop
If Not success Then
' ログ出力基盤やDB、イベントログへの記録処理をここに記述可能
End If
End Function
---
アーキテクトが解説するコードの急所と設計思想
1. `MSXML2.ServerXMLHTTP.6.0` の選択とインスタンスのライフサイクル
古いコードでは `MSXML2.XMLHTTP` が使われがちだが、これはクライアント側のセキュリティコンテキストやキャッシュ挙動に依存するため、サーバーサイドや自動化スクリプトには不向きだ。必ずスレッドセーフで高機能な `6.0` を指定する。
さらに、ループの各イテレーションごとに `Set http = Nothing` からの再生成を行っている点に注目してほしい。COMオブジェクトをループ内で再利用すると、メモリリークやステータスの持ち越しバグの原因になる。通信ごとに新品のオブジェクトを使い捨てるのが、VBScriptにおける最も安全なメモリ管理手法である。
2. 厳密なタイムアウト設定 (`setTimeouts`)
デフォルトのままで通信を行うと、サーバーが無応答になった際にスクリプトが無限にフリーズする(スレッドがハングする)。
`setTimeouts` メソッドを使い、DNS解決、接続、送信、受信の各フェーズに明確な制限時間を設けることで、異常なAPIサーバーに対してリソースをドレインされるリスクを防いでいる。
3. ステータスコードの完全なトリアージ
すべてのエラーを一緒くたにしてはならない。
- リトライすべきエラー: `408` (Timeout), `429` (Rate Limit), `5xx` (Server Internal Error)
- リトライしてはいけないエラー: `4xx` (クライアント側の記述ミス、認証エラー等。何度リトライしても結果は同じ)
この切り分けをコード内で行うことで、無駄なトラフィックの発生を防ぎつつ、障害時の無駄なウェイト時間をカットしている。
—
実務連携の注意点:ファイル出力・データベース連携との組み合わせ
バックオフ制御付きの通信モジュールを実際の業務自動化(日報集計、マスター同期など)に組み込む際は、以下の点に留意せよ。
1. ログの永続化:
コンソール出力(`WScript.Echo`)だけでなく、WSH環境であれば `Scripting.FileSystemObject` を使ってローカルのログファイル(`C:\Logs\api_error.log`)にタイムスタンプ付きでエラー内容を追記する機構を必ず併設すること。
2. データベース(SQLite / SQL Server)連携時のトランザクション:
API通信が最終的に失敗した場合、ローカルDB側で発行していたトランザクションが宙ぶらりんにならないよう、確実に `Rollback` を実行する、あるいは「処理待ちキューテーブル」のステータスを `Error` に更新してバッチを安全に離脱する設計が求められる。
まとめ
「VBScriptだから仕方ない」という言い訳は、プロフェッショナルの辞書には存在しない。
WSHという枯れた環境であっても、背後にあるネットワークの理屈(指数バックオフ、ジッター、タイムアウト管理)を正しくコードに翻訳さえすれば、ミッションクリティカルな環境に耐えうる堅牢な自動化ツールを構築できる。
今日の知見をあなたの現場のコードベースに組み込み、真に信頼できるバックエンド自動化を実現してほしい。
