「キャッシュをちゃんと実装したのに、なぜかAPIが何度も叩かれている…」そんな経験、ありませんか?🤔
実は、キャッシュが正しく動いていても重複リクエストが発生するケースがあります。これは「サンダーリングハード(Thundering Herd)」と呼ばれるバグで、並行処理を扱うアプリケーションではよく起きる落とし穴です。
今回はこの問題を具体的なコード例とあわせてわかりやすく解説していきます!
🐘 サンダーリングハードとは?

イメージとしてはこんな感じです。
あなたがカフェに入って「本日のランチはありますか?」と聞きます。店員がキッチンに確認しに行っているその間に、3人のお客さんが同じ質問をしました。全員が「まだ答えが返ってきていない」ため、店員はキッチンに4回往復することになります。
これと同じことがAPIサーバーで起きます。
- リクエストAが来る → キャッシュにない → GitHub APIへ問い合わせ開始
- その間にリクエストBが来る → まだキャッシュにない → GitHub APIへ問い合わせ開始
- その間にリクエストCが来る → まだキャッシュにない → GitHub APIへ問い合わせ開始
結果、キャッシュは正常なのに同じエンドポイントへ3回リクエストが飛んでしまいます。
🔍 問題のあるコード例
よくあるキャッシュ実装のパターンです。一見問題なさそうに見えますよね。
# ❌ サンダーリングハードが発生するパターン
import asyncio
cache = {}
async def get_user_data(username: str):
# キャッシュを確認
if username in cache:
return cache[username] # キャッシュヒット!
# キャッシュにない場合、外部APIを叩く
# ← ここで複数リクエストが「同時に」通過してしまう
data = await fetch_from_github(username)
cache[username] = data
return data
ポイントをまとめるとこんな感じです👇
awaitの間は処理が一時停止して他のリクエストが動き出す- キャッシュチェックと書き込みの間にタイムラグが生まれる
- その隙間を複数リクエストが「すり抜けて」しまう
✅ 解決策:インフライトリクエストの共有
解決策は「すでに問い合わせ中のリクエストがあれば、その結果を使い回す」仕組みを作ることです。
# ✅ サンダーリングハードを防ぐパターン
import asyncio
cache = {}
in_flight = {} # 「問い合わせ中」のFutureを管理する辞書
async def get_user_data(username: str):
# キャッシュを確認
if username in cache:
return cache[username]
# すでに同じユーザーへの問い合わせが進行中か確認
if username in in_flight:
# 進行中のリクエストが完了するのを待つだけ(API呼び出しなし)
return await in_flight[username]
# 初めての問い合わせ → Futureを登録してから実行
loop = asyncio.get_event_loop()
future = loop.create_future()
in_flight[username] = future
try:
data = await fetch_from_github(username)
cache[username] = data
future.set_result(data) # 待機中のリクエストにも結果を渡す
return data
except Exception as e:
future.set_exception(e)
raise
finally:
del in_flight[username] # 問い合わせ完了後にクリーンアップ
これで3つのリクエストが同時に来ても、GitHub APIへの呼び出しは1回だけになります。残りの2つは最初のリクエストの結果をそのまま受け取るイメージです。
🧠 まとめ
今回学んだことをまとめます。
- キャッシュが正常でも、並行リクエストのタイミング次第でAPI重複呼び出しは起きる
- これを「サンダーリングハード問題」と呼ぶ
- インフライトリクエストの管理(進行中の問い合わせを使い回す)で解決できる
非同期処理やAPIキャッシュを実装しているプロジェクトでは、ぜひ一度「同時リクエストが来たらどうなるか?」を意識してみてください。意外なところに落とし穴が潜んでいるかもしれませんよ😊
Next.jsやFastAPIでAPIを構築している方には特に関係の深い話なので、ぜひ実際のコードに当てはめて試してみてください!





