みなさん、こんにちは!日々のインフラ構築やAPI開発、本当にお疲れ様です。ネットワークの裏側やプロトコルの美しさにロマンを感じる、プロトコルスペシャリストの私です。
Webアプリケーションを作ったり、APIを叩いたりする際、避けて通れないのがHTTPメソッドの役割ですよね。データの取得には GET、新しく作成するには POST、更新には PUT や PATCH を使います。ここで、ネットワークエンジニアやAPI設計者が夜な夜な頭を悩ませる「ある恐ろしい現象」についてお話しさせてください。
それは、「二重送信(ネットワークの再送)」です。
今回は、APIの「冪等性(でとうせい)」という少し難しそうな概念を、私たちが普段使っている「郵便配達」の世界に例えて、優しく紐解いていきたいと思います。一歩ずつ、確実に理解していきましょう!
—
1. 郵便配達で考える「二重送信」の恐怖
想像してみてください。あなたはネットショッピングで、どうしても欲しかった限定スニーカーをポチりました。注文ボタンを押して決済完了画面へ進もうとしたその瞬間……!
「ブチッ……!」
オフィスのWi-Fiが突然切断されてしまいました。「あれっ? 今の注文、ちゃんと言ったかな?」と焦ったあなたは、慌ててブラウザの「更新(リロード)」ボタンを押し、もう一度注文ボタンをカチカチッと連打してしまいました。
ネットワークの世界では、これが恐ろしい悲劇を引き起こします。
もしサーバー側がこのリクエストを「別々の注文」として受け取ってしまったらどうなるでしょう? あなたの意図とは裏腹に、同じスニーカーが2足届き、クレジットカードからは2回分の代金が引き落とされてしまうのです。
安全な操作と、危険な操作
HTTPの仕様では、メソッドによって安全性が異なります。
GETメソッド(安全・冪等):
何度同じページを開いても、サーバーにあるデータが勝手に増えたり減ったりすることはありません。何回やっても同じ結果(冪等=何度やっても同じ)になります。
POSTメソッド(危険・非冪等):
「新しいデータを作って!」という命令です。これを2回送れば、2つデータが作られてしまいます。郵便配達でいえば、同じ「お金を振り込んでください」という手紙を2通ポストに投函してしまうようなものです。
この「2回送っても1回分しか処理されない(安全性を保つ)」という魔法の性質こそが、今回テーマにする「冪等性(Idempotency)」なのです。
—
2. 魔法の合言葉 Idempotency-Key とは?
「じゃあ、POST リクエストでも何回も押せるようにするにはどうすればいいの?」と思いますよね。
ここで登場するのが、今回の主役である Idempotency-Key(アイデムポテンシー・キー/冪等性キー)というHTTPヘッダーです。Stripeなどの決済系APIをはじめ、モダンなWeb APIでは必須のテクニックとなっています。
郵便配達に例えてみましょう。
先ほどのスニーカーの例で、あなたが注文ボタンを押したとき、ブラウザ(クライアント)は心の中でこう考えます。
> 「よし、この注文には order-2023-1025-001 という世界に一つだけの整理番号(UUIDなど)を振っておこう。もし途中で通信が切れて同じ注文をもう一度送ることになっても、この整理番号を一緒に送れば、郵便局(サーバー)は『あ、さっきと同じやつね』と気づいて、2通目をゴミ箱に捨ててくれるはずだ!」
この「世界に一つだけの整理番号」こそが Idempotency-Key ヘッダーの正体です。
—
3. サーバーサイドでどうやって管理する?(仕組みの裏側)
では、このリクエストを受け取るサーバー側では、裏でどのようなドラマが繰り広げられているのでしょうか? データベースとキャッシュ(Redisなど)を使った、現場のリアルな処理フローを覗いてみましょう。
1. キーのチェック:
クライアントから POST リクエストが届きます。サーバーはまず、ヘッダーに含まれる Idempotency-Key の値を確認します。
2. 過去の記録を検索:
「このキー、過去に処理したことあるっけ?」と、Redisなどのメモリ上で直近の処理履歴を調べます。
3. 分岐処理:
- 初めてのキーの場合:
「お、新顔だな!」ということで通常の処理を実行し、データベースにデータを書き込みます。そして、「このキーに対する処理結果(ステータスコードやレスポンスボディ)」を、処理結果のキャッシュとして一定時間保存します。
- すでに処理済みのキーの場合:
「おや、この番号はさっき処理したぞ!」と気づきます。データベースへの二重書き込みは行わず、過去に保存しておいた「当時の処理結果」をそのままクライアントに返します。
これによって、クライアントが何度同じリクエストを再送しても、サーバー側は安全に「1回分の処理結果」を返すことができるというわけです。
—
4. 実装のイメージを見てみましょう
言葉だけだとイメージしにくいと思いますので、Python(Flaskフレームワークを想定)を使ったシンプルなサーバー側の実装イメージを見てみましょう。実務のコードにそのまま応用できるよう、日本語で丁寧にコメントを入れています。
from flask import Flask, request, jsonify
import uuid
app = Flask(__name__)
# 本来はRedisなどのキャッシュストレージを使いますが、今回は簡易的にメモリ上で管理します
processed_requests = {}
@app.route('/api/v1/orders', methods=['POST'])
def create_order():
# 1. クライアントから送信された Idempotency-Key ヘッダーを取得する
idempotency_key = request.headers.get('Idempotency-Key')
if not idempotency_key:
return jsonify({"error": "Idempotency-Key ヘッダーが指定されていません"}), 400
# 2. すでにこのキーで処理を行ったことがあるかチェックする
if idempotency_key in processed_requests:
print(f"[INFO] 重複リクエストを検知しました。キー: {idempotency_key}")
# 過去の処理結果をそのまま返す(二重処理の防止)
cached_response = processed_requests[idempotency_key]
return jsonify(cached_response["body"]), cached_response["status"]
# --- ここから通常の新規処理 ---
data = request.json
item_name = data.get("item_name")
# 例:データベースへの登録処理が走る(ここでは擬似的に成功とする)
print(f"[DB] 新しい注文を登録しました: {item_name}")
response_body = {
"order_id": str(uuid.uuid4()),
"status": "success",
"message": "注文が正常に完了しました"
}
status_code = 201
# 3. 今後のために、処理結果をキーとともにキャッシュに保存しておく
processed_requests[idempotency_key] = {
"body": response_body,
"status": status_code
}
return jsonify(response_body), status_code
if __name__ == '__main__':
app.run(debug=True)
クライアント側(JavaScriptやスマホアプリなど)からは、リクエストを送る際に以下のようにヘッダーを付与します。
// クライアント側でユニークなキー(UUIDなど)を生成する
const idempotencyKey = 'uuid-1234-5678-90ab-cdef';
fetch('https://api.example.com/v1/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey // ここに整理番号を載せる
},
body: JSON.stringify({ item_name: 'Super Sneaker' })
})
.then(response => response.json())
.then(data => console.log(data));
もしこの通信が途中でタイムアウトし、同じ idempotencyKey を持って再リクエストが行われたとしても、サーバーは安全に先ほどのレスポンスを返却してくれます。これで、二重決済の悪夢から解放されますね!
—
まとめ
今回は、Web APIにおける冪等性の確保と Idempotency-Key ヘッダーの設計について、郵便配達の例えを交えながら解説しました。
- ネットワークは時に不安定で、リクエストが「二重送信」されるリスクは常にある。
POSTなどの非冪等な操作であっても、Idempotency-Keyを活用すれば安全性を担保できる。- サーバー側でキーと処理結果を紐づけてキャッシュすることで、二重実行を防ぎつつ、クライアントへ正しいレスポンスを返し続けることができる。
インフラやネットワークの世界は、目に見えないパケットのやり取りの連続ですが、こうした一つひとつの丁寧な設計の積み重ねによって、私たちの便利なデジタル社会が支えられています。
「難しそうだな」と感じた技術も、身近な仕組みに置き換えてみると、途端に愛着が湧いてくるものですよね。この記事が、みなさんの日々の開発やインフラ設計の小さなヒントになれば幸いです。
それでは、また次のネットワークの深淵でお会いしましょう!
コメント