ボタンは回り続け、タブにはエラーが出て、十分かけて入力したフォームは消えてしまった。わからないのは、接続が切れる前に書き込みが届いたかどうかだ。この不明を失敗と決めつけることが、重複コメント、二重注文、繰り返しのサポート依頼を生む。

決まった順で復旧する。テキストを残し、結果を見つけ、そのうえで二度目の送信が許されるか決める。

テキストと時刻から始める

ほかの何かに触れる前に、ページから内容をコピーする。ブラウザがまだ入力欄に保持している下書きも含む。時刻、いたページ、行った操作を書き留める。この三つが残りの調査を可能にする。タブがまだ開いているなら、ネットワークの記録を読むまで開いたままにする。テキストを保存する費用はゼロで、失う費用は作業全体だ。

次に、実際に観察できる二種類の障害を切り分ける。リクエストの途中で切れた接続と、エラーで応答したサーバーはネットワークパネルで見え方が違い、アプリの判断について何かを語るのは後者だけだ。

アプリ自身の表示で結果を探す

結果を表示する読み取り画面を再読み込みする。コメント一覧、注文履歴、チケットの待ち行列、編集中のレコードなどだ。最新の項目ではなく、アカウント、宛先、時刻、内容で照合する。自分より後に別の誰かが書いた可能性があるからだ。バックグラウンド処理なら、接続確認のために新しい処理を作るのではなく、既存の参照とその状態を調べる。

結果があれば終わりで、エラーは応答経路のもので書き込みのものではない。なければ、アプリがまだ確定していない可能性を残す。待ち行列の処理、遅延した読み取り画面、遅れている複製は、成功した書き込みをしばらく隠すことがある。

結論を出す前に、決めた二度目の確認を行い、両方の時刻を記録する。二十秒違いの二回の確認は、十分違いの二回の確認よりずっと弱い証拠だ。

二度目の送信が安全か判断する

答えは操作の種類で決まり、こちらの辛抱強さでは決まらない。HTTPは、同じ意図した効果で繰り返せるメソッドとそうでないメソッドを分け、後者の自動再試行は意味論が許すとわかっている場合を除いて勧めていない。コメントの作成、注文、支払いの送信は通常後者に入る。だから成功したように見える二度目のクリックは、一度目について何も語らない。

クライアントを保守しているなら、APIが文書化している再試行や冪等性の仕組みを使い、試行ごとに新しいキーを作るのではなく同じ論理操作に結びつける。追跡用のリクエスト識別子は、サーバーがそう扱わない限り重複抑制キーではない。

curl -i -X POST https://api.example.net/v1/orders \
  -H "Idempotency-Key: 6f1c2a94-3b7e-4a10-9c22-5d0e8f7b1a63" \
  -H "Content-Type: application/json" \
  --data @order.json

キーが役に立つのは、サービスがそれを操作に対応づけて保存し、繰り返しのときに元の結果を返す場合だけだ。本番で頼る前に、ステージングのエンドポイントでその挙動を試す。

もう一度押す前に証拠を残す

エラーのスクリーンショットではなく、ネットワークの記録を残す。リクエストのメソッド、URL、応答またはタイムアウト、起きた時刻が、管理者がアプリの記録と突き合わせられる情報だ。記録をコマンドとして書き出すのは、そのあとコピーからCookie、認証ヘッダー、私的な内容をすべて除いた場合に限る。自分のセッションはそのままにし、取得した認証情報つきのリクエストを近道として再送してはならない。

サービスの所有者に具体的な質問を一つする。この内容でこの時刻の操作は成功したのか、参照はあるのか。この問いには答えがあるが、"サイトは壊れたのか"には答えがない。

この作業の目的は、操作がちょうど一回だけ起きたと確認することだ。閉じられるエラーメッセージは、信頼できる記録と同じではない。何かを送る前に必要なのは後者だ。

関連記事