NDB キャッシュ

NDB はキャッシュを管理します。キャッシュ レベルは 2 つあり、一つはインコンテキスト キャッシュ、もう一つは App Engine の標準キャッシュ サービス(memcache)へのゲートウェイです。どちらのキャッシュも、すべてのエンティティ タイプにおいてデフォルトで有効になっていますが、ニーズに合わせて変更できます。また、NDB は自動バッチ処理という機能を実装しています。これはサーバーのラウンド トリップを最小限にするために、複数のオペレーションをグループ化するものです。

はじめに

キャッシュは、ほとんどの種類のアプリケーションの役に立ちます。NDB は書き込みまたは読み込みを行うデータを自動的にキャッシュ保存します(アプリケーションでキャッシュに保存しないように構成している場合を除きます)。キャッシュからのほうが、Datastore からよりも高速に読み込めます。

NDB 関数のキャッシュ動作の多くは、コンテキスト オプション引数を渡すことで変更できます。たとえば、key.get(use_cache=False, use_memcache=False) を呼び出してキャッシュをバイパスできます。また、NDB コンテキストのデフォルトのキャッシュ ポリシーを変更することもできます。その方法はこの後で説明します。

注意: 管理コンソールの Datastore ビューアを使用して Datastore のコンテンツを変更した場合、キャッシュ内の値は更新されません。したがって、キャッシュが一致しない場合があります。インコンテキスト キャッシュでは、通常これは問題になりません。Memcache の場合は、管理コンソールを使用してキャッシュをフラッシュすることをおすすめします。

Context オブジェクト

キャッシュの管理には、Context という名前のクラスが使用されます。各スレッドと各トランザクションは新しいコンテキストで実行されます。受信 HTTP リクエストは新しいスレッドを開始するため、各リクエストは新しいコンテキストで実行されます。現在のコンテキストにアクセスするには、ndb.get_context() 関数を使用します。

注意: Context オブジェクトを複数のスレッドまたはリクエストで共有しても意味はありません。コンテキストをグローバル変数として保存しないでください。ローカル変数またはスレッド ローカルな変数として保存することは問題ありません。

Context オブジェクトには、キャッシュ ポリシーの設定やキャッシュ操作を行うためのメソッドがあります。

インコンテキスト キャッシュ

インコンテキスト キャッシュは、単一スレッドの存続期間だけ持続します。つまり、受信 HTTP リクエストには新しいインコンテキスト キャッシュが与えられ、そのリクエストを処理するコードのみが参照可能になります。リクエストの処理中にアプリケーションが追加のスレッドを生成した場合、それらのスレッドには新しい別のインコンテキスト キャッシュが与えられます。

インコンテキスト キャッシュは高速で、メモリ内に存在します。NDB 関数が Datastore に書き込むと、インコンテキスト キャッシュにも書き込まれます。NDB 関数がエンティティを読み込むと、まずインコンテキスト キャッシュを確認します。エンティティがあった場合は、Datastore の操作は行われません。

NDB 関数が Datastore にクエリを送信すると、そこから結果リストが取得されます。ただし、個々の結果がインコンテキスト キャッシュにある場合は、この値が Datastore クエリで取得された値の代わりに使用されます。キャッシュ ポリシーによっては、クエリの結果がインコンテキスト キャッシュに書き込まれることもあります(Memcache には書き込まれません)。

バックグラウンド タスクで長時間実行されるクエリを実行すると、インコンテキスト キャッシュが大量のメモリを消費する可能性があります。これは、現在のコンテキストで取得または格納されているすべてのエンティティのコピーがキャッシュに保存されるためです。長時間実行されるタスクでメモリ例外を回避するには、キャッシュを無効にするか、最もメモリを消費するエンティティを除外するポリシーを設定します。

Memcache

Memcache は App Engine の標準のキャッシュ サービスで、Datastore よりずっと高速ですが、インコンテキスト キャッシュより遅くなります(ミリ秒に対してマイクロ秒)。

デフォルトでは、非トランザクション コンテキストは memcache 内のすべてのエンティティをキャッシュに保存します。アプリケーションのすべてのコンテキストは同じ memcache サーバーを使用し、一貫したキャッシュの値のセットを参照します。

Memcache はトランザクションをサポートしません。このため、Datastore と memcache の両方に適用されるはずの更新が、どちらか一方にしか適用されない可能性があります。このような場合に一貫性を維持するために(パフォーマンスが犠牲になることもありますが)、更新されたエンティティを memcache から削除して、その後 Datastore に書き込みます。それに続く読み取りオペレーションは、memcache にエンティティがないため Datastore から取得し、読み込みの副次的効果として memcache を更新します。また、NDB のトランザクション内の読み込みは Memcache を無視します。

エンティティがトランザクション内で書き込まれるとき、memcache は使用されません。トランザクションが commit されると、そのコンテキストは memcache からそのようなエンティティをすべて削除しようとします。ただし、障害が起きるなどして、削除が行われない場合があります。

ポリシー関数

自動キャッシュは多くのアプリケーションにとって便利ですが、アプリケーションによっては、一部またはすべてのエンティティで自動キャッシュをオフにしたい場合もあるでしょう。ポリシー関数を設定することによって、キャッシュの動作を制御できます。インプロセス キャッシュ用のポリシー関数は次のように設定します。

context = ndb.get_context()
context.set_cache_policy(func)

memcache 用は次のように設定します。

context = ndb.get_context()
context.set_memcache_policy(func)

各ポリシー関数はキーを受け取り、ブール値を返します。False を返す場合、そのキーで識別されたエンティティは対応するキャッシュに保存されません。たとえば、インプロセス キャッシュをすべての Account エンティティでバイパスする場合、次のようにすることができます

context = ndb.get_context()
context.set_cache_policy(lambda key: key.kind() != 'Account')

(ただし、同じことがもっと簡単にできます。この後を続けてお読みください)。便宜上、同じ値を常に返す関数の代わりに、True または False を渡すことができます。デフォルトのポリシーはすべてのエンティティをキャッシュに保存します。

Datastore に書き込まれるエンティティを管理する Datastore ポリシー関数もあります。

context = ndb.get_context()
context.set_datastore_policy(func)

これはインコンテキスト キャッシュと memcache ポリシー関数と同じように機能します。Datastore ポリシーが指定されたキーに対して False を返す場合、対応するエンティティは Datastore に書き込まれません(そのポリシー関数が許す場合、インプロセス キャッシュまたは memcache に書き込まれることがあります)。これは、キャッシュに保存したいエンティティのようなデータを持っているものの、Datastore に保存する必要がない場合に便利です。キャッシュ ポリシーの場合と同様に、常に同じ値を返す関数の代わりに、True または False を渡すことができます。

Memcache は、メモリが切迫してくると、自動的にアイテムを無効にします。memcache のタイムアウト ポリシー関数を設定して、エンティティのキャッシュ内での存続期間を決定できます。

context = ndb.get_context()
context.set_memcache_timeout_policy(func)

この関数はキーを引数にして呼び出され、最大存続期間を秒単位で指定する整数を返します。0 または None は存続期間が無期限であることを意味します(ただし、memcache サーバーに十分なメモリがある場合に限ります)。便宜上、同じ値を常に返す関数の代わりに、整数の定数を渡すことができます。タイムアウトの詳細については、memcache のドキュメントをご覧ください。

注: インコンテキスト キャッシュには個別の存続期間ポリシーがありません。キャッシュの存続期間は、そのコンテキストと同じで、単一の受信 HTTP リクエスト中ということになります。ただし、次の呼び出しを行うことで、インプロセス キャッシュをクリアできます。
context = ndb.get_context()
context.clear_cache()

新しいコンテキストが、空のインプロセス キャッシュで始まります。

ポリシー関数は非常に柔軟性がありますが、実際には、ほとんどのポリシーはシンプルです。次に例を示します。

  • 特定のモデルクラスに属するエンティティをキャッシュに保存しません。
  • このモデル クラス内のエンティティの memcache タイムアウトを 30 秒に設定します。
  • このモデルクラスのエンティティは Datastore に書き込みません。

些細なポリシー関数を継続的に更新する(あるいは、コンテキスト オプションを使用するオペレーションごとにポリシーをオーバーライドする)手間を省くために、デフォルトのポリシー関数は、渡されたキーからモデルクラスを取得し、その後、特定のクラス変数を検索します。

クラス変数 種類説明
_use_cache bool エンティティをインプロセス キャッシュに保存するかどうかを指定します。デフォルトのインプロセス キャッシュ ポリシーをオーバーライドします。
_use_memcache bool エンティティを memcache に保存するかどうかを指定します。デフォルトの memcache ポリシーをオーバーライドします。
_use_datastore bool エンティティを Datastore に保存するかどうかを指定します。デフォルトの Datastore ポリシーをオーバーライドします。
_memcache_timeout int memcache 内のエンティティの最大保存期間です。デフォルトの memcache タイムアウト ポリシーをオーバーライドします。

注: これは、ポリシーごとのデフォルト ポリシー関数の機能です。独自のポリシー関数を指定しながら、デフォルトのポリシーにフォールバックしたい場合は、デフォルトのポリシー関数を Context クラスの静的メソッドとして明示的に呼び出します。

  • default_cache_policy(key)
  • default_memcache_policy(key)
  • default_datastore_policy(key)
  • default_memcache_timeout_policy(key)