はじめに
タイムアウトした場合に起こる冪等性の性質についてまとめます。
背景
前回は冪等性のことについて触れました。
今回はタイムアウトしたために、ユーザー側(クライアント)は何回もリトライするけど、サーバー側は1回しっかりと登録されていることを確認します。
クライアントとサーバーで待ち時間を作り、処理をずらす
図にすると以下のような感じにします。
クライアント側 サーバー側
│ │
│ 処理開始 │
│ │
│ API呼び出し ──────────────────────────────────→ │
│ │
│ │ 処理開始
│ │
│ 3秒待つ │
│ │
│ タイムアウト │
│ │
│ 処理終了 │
クライアント側で処理開始しますが、3秒でタイムアウトします。
そのため、サーバー側の処理完了を待たず、処理終了してしまいます。
コード
route
import { supabase } from "../../../utils/supabase";
export async function POST() {
console.log("開始");
await new Promise((resolve) => {
setTimeout(resolve, 5000);
});
console.log("5秒経過");
const idempotencyKey = "test-timeout-001";
const { data, error } = await supabase.from("favorites_dev").insert([{ workspace_set_dev_id: 2, user_id: "<USER_ID>", idempotency_key: idempotencyKey,},])
.select();
if (error) {
console.error(error);
return Response.json(
{ error: error.message },
{ status: 500 }
);
}
console.log("完了");
return Response.json(data);
}
page
"use client";
export default function TimeoutTest() {
const testTimeout = async () => {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
console.log("3秒経過 → タイムアウト");
controller.abort();
}, 3000);
try {
console.log("API呼び出し開始");
const response = await fetch("/api/test-timeout", {
method: "POST",
signal: controller.signal,
});
const data = await response.json();
console.log("APIレスポンス:", data);
} catch (error) {
if (
error instanceof DOMException &&
error.name === "AbortError"
) {
console.log("タイムアウトしました");
} else {
console.error("その他のエラー:", error);
}
} finally {
clearTimeout(timeoutId);
}
};
return (
<button onClick={testTimeout}>
タイムアウトテスト
</button>
);
}
何度もPOSTしましたが、DBに登録されているデータは1つだけでした。
{"idx":1,"id":14,"created_at":"2026-09-03 12:25:54.909377+00","workspace_set_dev_id":2,"user_id":"<USER_ID>","idempotency_key":"test-timeout-001"}
しっかりとエラーも出ています。
{
code: '23505',
details: 'Key (idempotency_key)=(test-timeout-001) already exists.',
hint: null,
message: 'duplicate key value violates unique constraint "favorites_dev_idempotency_key_key"'
}
おわりに
今回はタイムアウトがあった場合に冪等性担保されるかを確認しました。
しかし、クライアント側ではどうなっているかわからないため通知が必要となります。次回以降その対応をしていきます。
参考文献

