プログラミング入門

キャッシュが正常動作しているのにAPIが重複呼び出しされる「サンダーリングハード」問題を解説

「キャッシュをちゃんと実装したのに、なぜかAPIが何度も叩かれている…」そんな経験、ありませんか?🤔

実は、キャッシュが正しく動いていても重複リクエストが発生するケースがあります。これは「サンダーリングハード(Thundering Herd)」と呼ばれるバグで、並行処理を扱うアプリケーションではよく起きる落とし穴です。

今回はこの問題を具体的なコード例とあわせてわかりやすく解説していきます!

🐘 サンダーリングハードとは?

server cache
server cache / Photo by panumas nikhomkhai via Pexels

イメージとしてはこんな感じです。

あなたがカフェに入って「本日のランチはありますか?」と聞きます。店員がキッチンに確認しに行っているその間に、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を構築している方には特に関係の深い話なので、ぜひ実際のコードに当てはめて試してみてください!

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

もしも

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

電子工作とプログラミングを同時に学べる

Amazonで見る

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

もしも

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

AIコーディングの現場活用法を学ぶ一冊

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です