シングルフライトキャッシング:1つのホットキーによるデータベースのダウンを防ぐ方法

シングルフライトキャッシング:1つのホットキーによるデータベースのダウンを防ぐ方法

キャッシュ失効時のデータベース負荷を管理するソリューション。

ウェブサイトに大量のトラフィックが発生すると、特にデータ取得をデータベースに依存している場合、パフォーマンスの問題が生じる可能性があります。ここでは、多くのアプリケーションが直面する共通の課題である「データベースに過負荷をかけることなくキャッシュ失効を処理する方法」についての解決策を説明します。

問題の理解

ウェブサイト podbor-minuta.ru では、一部のデータベースクエリが非常に高負荷です。処理を高速化するため、クエリ結果をキャッシュに保存しています。例えば、アパート一覧やリスティングページ用の集計数などをキャッシュしています。キャッシュが有効な間はすべて順調に動作します。しかし、キャッシュが失効すると問題が発生します。

ある日、高負荷時にサイトで深刻な問題が発生しました。500件のレスポンスが一括でタイムアウトしたのです。データベースは稼働し続けていたものの、クエリがキューに滞留し始めました。この事態は、複数のリクエストが同時に同じデータへアクセスしようとしたことでボトルネックが生じたために発生しました。

キャッシュスタンピードとは?

初期のキャッシュロジックは単純でした。値がキャッシュにない場合、アプリケーションはデータベースにクエリを発行して値を算出し、その値をキャッシュに保存します。これは単一のリクエストであれば問題なく機能します。しかし、キャッシュが失効し、突然200件のリクエストが同じデータに対して届いた場合を想像してみてください。各リクエストはキャッシュが空であることを確認し、それぞれが同時にデータベースへクエリを発行します。このシナリオは次のように呼ばれます: キャッシュスタンピード.

キャッシュスタンピードの発生中、データベースは同一の重いクエリで過負荷になり、接続上限に達してしまいます。その結果、一部のリクエストがタイムアウトによってドロップされます。

シングルフライトキャッシングの実装

この問題を解決するために、チームは次の手法を導入しました: シングルフライトキャッシング。この手法は、特定のキーに対する計算処理がすでに実行中である場合、同じキーに対する他のリクエストが独自のデータベースクエリを開始するのではなく、その結果を待機するようにします。その仕組みは以下のとおりです:

ステップ1:キャッシュの確認

まず、アプリケーションは値がすでにキャッシュ内にあるかどうかを確認します。存在する場合は、その値を即座に返します。

ステップ2:キャッシュミスの処理

値がキャッシュ内にない場合、そのキーに対する計算処理がすでに進行中かどうかを確認します。進行中であれば、アプリケーションはその計算が完了するのを待機し、結果を返します。

ステップ3:Promiseの作成

計算処理が進行中でない場合、アプリケーションは処理を開始します。計算用のPromiseを作成し、キーを使ってMapに保存します。これにより、他のリクエストは新しいデータベースクエリを開始する代わりに、同じPromiseにアクセスできるようになります。

以下は、この仕組みがどのように機能するかを示す簡略化されたコード例です:

const inFlight = new Map<string, Promise<unknown>>();

async function getOrSet<T>(key: string, compute: () => Promise<T>): Promise<T> {
    const cached = cache.get(key);
    if (cached !== undefined) return cached as T;

    const running = inFlight.get(key);
    if (running) return running as Promise<T>;

    const promise = compute().then((value) => {
        cache.set(key, value);
        return value;
    }).finally(() => {
        inFlight.delete(key);
    });

    inFlight.set(key, promise);
    return promise;
}

シングルフライトキャッシングのメリット

シングルフライトキャッシングの実装により、いくつかのメリットがもたらされました:

  • データベース負荷の軽減:200件の同一クエリではなく1件のクエリのみがデータベースに到達するため、負荷が大幅に軽減されます。
  • パフォーマンスの向上:コネクションプールが無駄に枯渇することがなくなり、高トラフィック時でもスムーズなパフォーマンスが可能になります。
  • 効率性:キャッシュヒットの処理パスは高速なままであり、キャッシュ内でデータを見つけたリクエストに追加のオーバーヘッドは発生しません。

結論

シングルフライトキャッシングは、ピーク時、特にキャッシュ失効が発生した際のデータベース負荷を管理するための効果的な戦略です。重いクエリを一度に1つのリクエストのみが処理するようにすることで、アプリケーションはパフォーマンスを維持し、サーバーのクラッシュを防ぐことができます。

メリット

  • キャッシュ失効時のデータベース過負荷を防止。
  • キャッシュされたデータの高速なレスポンスタイムを維持。
  • 重複するデータベースクエリの数を削減。

デメリット

  • インフライト(処理中)リクエストを管理するための追加ロジックが必要。
  • より厳密な環境では共有ロックが必要になる場合があり、複雑さが増加。

注意点

この記事は教育目的で作成されたものです。キャッシング戦略を実装する場合は、プレースホルダーの値を実際のデータに置き換え、それらに依拠する前に元の情報源と照らし合わせて確認してください。

よくある質問

  • シングルフライトキャッシングとは何ですか? — 特定のキーに対して一度に1つのリクエストのみがデータを取得できるようにすることで、重複するデータベースクエリを防ぐキャッシング戦略です。
  • キャッシュスタンピードとは何ですか? — キャッシュミス後に同じデータを求める複数のリクエストが同時にデータベースに到達し、データベースが過負荷になる状況のことです。
  • キャッシングはどのようにパフォーマンスを向上させますか? — キャッシングは頻繁にアクセスされるデータをメモリに保存し、データベースに問い合わせるよりも高速なデータ取得を可能にします。
  • キャッシュが失効すると何が起こりますか? — キャッシュが失効すると、同じデータを求めるリクエストがデータベースに殺到する可能性があり、適切に管理されていない場合はパフォーマンスの低下を招きます。
  • シングルフライトキャッシングは複数サーバー構成でも使用できますか? — はい、ただし異なるサーバー間でリクエストを効果的に管理するには、共有ロックなどの追加の仕組みが必要です。
  • キャッシングのメリットは何ですか? — キャッシングはデータベース負荷を軽減し、データ取得を高速化して、アプリケーション全体のパフォーマンスを向上させます。

タグ

#caching #database #performance #webdev #architecture #softwaredevelopment #programming #tech

Free field guide

API Security Testing Checklist

A practical workflow for testing authentication, authorization, input handling, business logic, and evidence without losing track of scope.