Redis 用 Amazon ElastiCache
Redis 用 ElastiCache ユーザーガイド (API バージョン 2015-02-02)

キャッシュ戦略

以下のトピックでは、キャッシュにデータを入力して維持するための戦略について説明します。

キャッシュにデータを入力して維持するために実装する戦略は、キャッシュするデータとそのデータへのアクセスパターンによって異なります。たとえば、ゲームサイトでトップ 10 リーダーボードを提供する場合と、ニュースサイトでトレンド記事を提供する場合では、多くの場合、異なる戦略を使用する必要があります。このセクションの後半では、一般的なキャッシュのメンテナンス戦略、それらの利点と欠点について説明します。

遅延読み込み

遅延読み込みは、その名前が示すようため、必要なときにのみキャッシュにデータを読み込むキャッシュ戦略です。この戦略は以下のように機能します。

Amazon ElastiCache はインメモリキー/値ストアであり、アプリケーションとそのアクセス先のデータストア (データベース) 間にあります。アプリケーションがデータをリクエストする場合は、常に ElastiCache キャッシュに最初にリクエストを行います。データがキャッシュにあり最新である場合、ElastiCache はアプリケーションにデータを返します。データがキャッシュに存在しないか、有効期限が切れている場合、アプリケーションはデータストアにデータをリクエストします。その後、データストアはアプリケーションにデータを返します。次に、アプリケーションはストアから受信したデータをキャッシュに書き込みます。これにより、次回のリクエスト時に、データをよりすばやく取得できます。

キャッシュヒットは、データがキャッシュ内にあり、有効期限が切れていない場合に発生します。

  1. アプリケーションはキャッシュにデータをリクエストします。

  2. キャッシュはアプリケーションにデータを返します。

キャッシュミスは、データがキャッシュ内にないか、有効期限が切れている場合に発生します。

  1. アプリケーションはキャッシュにデータをリクエストします。

  2. キャッシュはリクエストされたデータがないため、null を返します。

  3. アプリケーションはデータベースにデータをリクエストし、取得します。

  4. アプリケーションは新しいデータでキャッシュを更新します。

次の図は、これらの両方のプロセスを示しています。

遅延読み込みの利点と欠点

遅延読み込みの利点は以下のとおりです。

  • リクエストされたデータのみがキャッシュされる。

    ほとんどのデータがリクエストされないため、遅延読み込みではデータでキャッシュがいっぱいになることを回避できます。

  • ノード障害がアプリケーションにとって致命的にならない。

    ノードで障害が発生して新しい空のノードに交換された場合、アプリケーションはレイテンシーが長くなっても機能し続けます。新しいノードに対してリクエストが行われると、キャッシュミスのたびにデータベースに対するクエリが実行されます。同時に、データのコピーがキャッシュに追加され、後続のリクエストがキャッシュから取得されます。

遅延読み込みの欠点は以下のとおりです。

  • キャッシュミスのペナルティがある。キャッシュミスが発生するたびに、3 つのトリップが発生します。

    1. キャッシュに対する最初のデータリクエスト

    2. データベースへのデータクエリ

    3. キャッシュへのデータ書き込み

    これらのキャッシュミスのため、アプリケーションによるデータの取得に相当な遅延が発生する可能性があります。

  • データが古い。

    キャッシュミスがあるときにのみデータがキャッシュに書き込まれる場合、キャッシュ内のデータが古くなる可能性があります。このような結果になるのは、データベースでデータが変更されたときにキャッシュが更新されないためです。この問題に対処するには、書き込みスルー および TTL の追加 戦略を使用できます。

遅延読み込みの擬似コードの例

以下に示しているのは、遅延読み込みロジックの擬似コードの例です。

// ***************************************** // function that returns a customer's record. // Attempts to retrieve the record from the cache. // If it is retrieved, the record is returned to the application. // If the record is not retrieved from the cache, it is // retrieved from the database, // added to the cache, and // returned to the application // ***************************************** get_customer(customer_id) customer_record = cache.get(customer_id) if (customer_record == null) customer_record = db.query("SELECT * FROM Customers WHERE id == {0}", customer_id) cache.set(customer_id, customer_record) return customer_record

この例では、データを取得するアプリケーションコードは以下のとおりです。

customer_record = get_customer(12345)

書き込みスルー

書き込みスルー戦略では、データがデータベースに書き込まれると常にデータを追加するか、キャッシュのデータを更新します。

書き込みスルーの利点と欠点

書き込みスルーの利点は以下のとおりです。

  • キャッシュのデータが古くなりません。

    キャッシュにデータベースにデータが書き込まれるたびにキャッシュのデータが更新されるため、キャッシュのデータが常に最新の状態になります。

  • 書き込みペナルティと読み込みペナルティ

    1 回の書き込みで 2 回のトリップ:

    1. キャッシュへの書き込み

    2. データベースへの書き込み

    レイテンシーをプロセスに追加します。つまり、エンドユーザーは一般的に、データの取得時よりもデータの更新時のレイテンシーに対して寛容です。更新は作業量が大きく時間がかかるのが常です。

書き込みスルーの欠点は以下のとおりです。

  • 見つからないデータ。

    新しいノードをスピンアップした場合、その原因がノード障害であってもスケールアウトであっても、見つからないデータがあります。このようなデータは、データベースで追加または更新されるまで見つからないままになります。書き込みスルーで遅延読み込みを実装することで、見つからないデータを最小限に抑えることができます。

  • キャッシュからのチャーン。

    ほとんどのデータは読み取られることがなくなり、リソースの無駄になります。有効期限 (TTL) 値を追加することで、無駄な領域を最小限に抑えることができます。

書き込みスルー擬似コードの例

以下に示しているのは、書き込みスルーロジックの擬似コードの例です。

// ***************************************** // function that saves a customer's record. // ***************************************** save_customer(customer_id, values) customer_record = db.query("UPDATE Customers WHERE id = {0}", customer_id, values) cache.set(customer_id, customer_record) return success

この例では、データを取得するアプリケーションコードは以下のとおりです。

save_customer(12345,{"address":"123 Main"})

TTL の追加

遅延読み取りはデータが古くなる可能性がありますが、空ノードによる障害は発生しません。書き込みスルーでは常に新しいデータとなりますが、空ノードの障害が発生して、過剰なデータがキャッシュに入力されることがあります。各書き込みに有効期限 (TTL) 値を追加することで、各戦略の利点を得ることができます。同時に、余分なデータでキャッシュが乱雑になることを回避できます。

有効期限 (TTL) は、キーの有効期限が切れるまでの秒数を指定する整数値です。Redis では、この値に秒またはミリ秒を指定できます。アプリケーションが有効期限切れのキーを読み取ろうとすると、キーが見つからないとして扱われます。データベースに対してキーのクエリが実行され、キャッシュが更新されます。このアプローチは、値が古くないことを保証するものではありません。ただし、データが古くなりすぎることを防ぎ、キャッシュの値がデータベースから時々更新されることを必要とします。

詳細については、Redis の set コマンドを参照してください。

TTL 擬似コードの例

以下に示しているのは、TTL のある書き込みスルーロジックの擬似コードの例です。

// ***************************************** // function that saves a customer's record. // The TTL value of 300 means that the record expires // 300 seconds (5 minutes) after the set command // and future reads will have to query the database. // ***************************************** save_customer(customer_id, values) customer_record = db.query("UPDATE Customers WHERE id = {0}", customer_id, values) cache.set(customer_id, customer_record, 300) return success

以下に示しているのは、TTL のある遅延読み込みロジックの擬似コードの例です。

// ***************************************** // function that returns a customer's record. // Attempts to retrieve the record from the cache. // If it is retrieved, the record is returned to the application. // If the record is not retrieved from the cache, it is // retrieved from the database, // added to the cache, and // returned to the application. // The TTL value of 300 means that the record expires // 300 seconds (5 minutes) after the set command // and subsequent reads will have to query the database. // ***************************************** get_customer(customer_id) customer_record = cache.get(customer_id) if (customer_record != null) if (customer_record.TTL < 300) return customer_record // return the record and exit function // do this only if the record did not exist in the cache OR // the TTL was >= 300, i.e., the record in the cache had expired. customer_record = db.query("SELECT * FROM Customers WHERE id = {0}", customer_id) cache.set(customer_id, customer_record, 300) // update the cache return customer_record // return the newly retrieved record and exit function

この例では、データを取得するアプリケーションコードは以下のとおりです。

save_customer(12345,{"address":"123 Main"})
customer_record = get_customer(12345)

関連トピック