キャッシュ

必然的に (Inevitably)、アプリケーション開発やデプロイの際に何らかのタス クが完了するのに非常に時間がかかることがあります。このような場合、処理 を速くする最も良い方法が caching です。

Pylons にはキャッシュミドルウェアが有効な状態で付属しています。それはセッ ションの取り扱いを提供するのと同じパッケージである Beaker. の一部です。 Beaker はいくつかの異なる種 類のキャッシュバックエンドをサポートします: メモリベース, ファイルシス テム, そして特別な memcached です。

Pylons では、速度低下が起こる場所に応じて、データをキャッシュするいくつ かの方法があります:

  • ブラウザサイドのキャッシング - HTTP/1.1 は丸ごと 1 ページの再生成を必 要とする代わりにブラウザが自身のキャッシュを使用できるようにする ETag キャッシングシステムをサポートしています。 ETag ベースの キャッシュは、コンテンツを繰り返し生成することを避けますが、それでも ブラウザがそれまでに一度もページを参照したことがないと、ページが生成 されます。したがって、 ETag キャッシュをここに記述された他のキャッシュ タイプのひとつと組み合わせて使用することで、最適のスループットを達成 して、資源集約的な操作での不必要な呼び出しを避けることができるでしょう。

Note

後者はまるまる 1 ページをキャッシュできる場合にだけ有効です。 (訳注: 後者が何を指しているのか不明)

  • Mako/Myghty テンプレート - MakoMyghty テンプレートエンジンの両方で 内蔵のキャッシュオプションが利用可能です。それらはテンプレート全体の キャッシュだけでなく、テンプレートのある一定のセクションだけの粒度の 細かいキャッシュを許します。

キャッシュを行うときに覚えておくべき 2 つの基本概念は i) キャッシュには 名前空間 があり、 ii) 名前空間の下でキャッシュは キー を持つことが できるということです。この理由は、単一のテンプレートに対して複数のテン プレートのバージョンがそれぞれ自身のキャッシュされたバージョンを必要と するかもしれないからです。名前空間におけるキーは バージョン です。 そしてテンプレートの名前は 名前空間 です。 これらの値の両方とも、 Python 文字列でなければなりません。

テンプレート中では、キャッシュの「名前空間」はレンダリングされるテンプ レートの名前に自動的に設定されるでしょう。基本的なキャッシュに対して他 には何も必要ありません。例外は、テンプレートがどれくらい長い間キャッシュ されるかを開発者が制御したい場合、かつ/または、テンプレートの複数のバー ジョンのキャッシュを維持したい場合です。

Stephen Pierzchala の Caching for Performance (stephen@pierzchala.com) も読んでください。

cache オブジェクトを使う

コントローラの中では、 cache オブジェクトが利用可能です。リソースや時 間を集中的に使用するアクションまたはブロックがコード中にあれば、結果を キャッシュすることは有効な場合があります。 cache オブジェクトは pickle 可 能などんな Python 構造もキャッシュすることができます。

あるアクションについて、時間を費やしたりリソースの集中的な参照をしたり して pickle できるオブジェクト (リスト, 辞書, タプルなど) を返す何らか のコードをキャッシュしたいとします:

# Add to existing imports
from pylons import cache


# Under the controller class
def some_action(self, day):
    # hypothetical action that uses a 'day' variable as its key

    def expensive_function():
        # do something that takes a lot of cpu/resources
        return expensive_call()

    # Get a cache for a specific namespace, you can name it whatever
    # you want, in this case its 'my_function'
    mycache = cache.get_cache('my_function', type="memory")

    # Get the value, this will create the cache copy the first time
    # and any time it expires (in seconds, so 3600 = one hour)
    c.myvalue = mycache.get_value(key=day, createfunc=expensive_function,
                                  expiretime=3600)

    return render('/some/template.myt')

createfunc オプションには callable オブジェクトまたは関数を渡します。 引数に対する値がキャッシュ中に存在しないか有効期限を過ぎていた場合は、 常にキャッシュによってそれが呼び出されます。

createfunc は引数なしで呼ばれるので、リソースまたは時間を大量消費する 関数もそれに対応して引数をとることはできません。

その他のキャッシュオプション

キャッシュはキーを指定してキャッシュされた値を削除することをサポートし ます。また、リセットする際に必要となる、キャッシュの完全なクリアもサポー トします。

# Clear the cache
mycache.clear()

# Remove a specific key
mycache.remove_value('some_key')

render に対するキャッシュキーワードを使う

Warning

Needs to be extended to cover the specific render_* calls introduced in Pylons 0.9.7

すべての render コマンドは、 キャッシュ機能を内蔵しています。それを使用するには、単に render 呼び出 しに適切なキャッシュキーワードを加えてください。

class SampleController(BaseController):

    def index(self):
        # Cache the template for 10 mins
        return render('/index.myt', cache_expire=600)

    def show(self, id):
        # Cache this version of the template for 3 mins
        return render('/show.myt', cache_key=id, cache_expire=180)

    def feed(self):
        # Cache for 20 mins to memory
        return render('/feed.myt', cache_type='memory', cache_expire=1200)

    def home(self, user):
        # Cache this version of a page forever (until the cache dir
        # is cleaned)
        return render('/home.myt', cache_key=user, cache_expire='never')

キャッシュデコレータを使う

Pylons はまた、関数呼び出し全体の結果をキャッシュする (memoizing) ため に、 pylons.cachebeaker_cache() デ コレータを提供します。

beaker_cache デコレータは、 render 関数と同じ (それらから cache_ プ リフィックスを除いた) キャッシュ引数を取ります。

from pylons.decorators.cache import beaker_cache

class SampleController(BaseController):

    # Cache this controller action forever (until the cache dir is
    # cleaned)
    @beaker_cache()
    def home(self):
        c.data = expensive_call()
        return render('/home.myt')

    # Cache this controller action by its GET args for 10 mins to memory
    @beaker_cache(expire=600, type='memory', query_args=True)
    def show(self, id):
        c.data = expensive_call(id)
        return render('/show.myt')

デフォルトでは、 beaker_cache デコレータはキャッシュキーとしてデコレー ト対象の関数のすべての引数を合成したものを使用します。 query_args オ プションが有効なときは、代わりにキャッシュキーとして request.GET クエ リ引数を合成したものを使用することができます。

key 引数でさらにキャッシュキーをカスタマイズすることができます。

Caching Arbitrary Functions

任意の関数で beaker_cache() デコレータを 使用できますが、追加のオプションを渡す必要があります。デコレーターは response オブジェクトをキャッシュするため、非コントローラメソッ ドでステータスコードやヘッダーをキャッシュしなければならないことはほと んどありません。そのようなデータをキャッシュするのを避けるために、 cache_response キーワード引数は false に設定されるべきです。

from pylons.decorators.cache import beaker_cache

@beaker_cache(expire=600, cache_response=False)
def generate_data():
    # do expensive data generation
    return data

Warning

When caching arbitrary functions, the query_args argument should not be used since the result of arbitrary functions shouldn’t depend on the request parameters.

ETag キャッシュ

ETag によるキャッシュは、 ETag ヘッダーをブラウザに送ることでブラウザが ページのキャッシュされたコピーを保存し、(アプリケーションがそれを送る代 わりに) ブラウザ自身のキャッシュが使用できると知らせることを含みます。

ETag キャッシュはブラウザにヘッダーを送ることに頼っているので、上述した 他のキャッシュ機構とはやや異なる方法で働きます。

ブラウザにページのコピーがまだなければ、 etag_cache() 関数は適切な HTTP ヘッダが セットされた Response オブジェクトを返します。そうでなければ 304 HTTP Exception が投げられ、これは Paste ミドルウェアによって捕捉されてブラウ ザへの適切な 304 レスポンスになります。これにより、ブラウザはそれ自身の 持つコピーを使用するようになります。

etag_cache() は レガシー目的のために Response を返します (代わりに Response を直接使用すべきです)。

ETag ベースのキャッシュは ETag HTTP ヘッダでブラウザに送られる単一のキー を必要とします。 HTTP ヘッダの RFC 仕様 では、 ETag ヘッダーは文字列であることだけが要求されています。ブラウザ自身がキャッ シュを使用するかどうかを決定するため、この値はあらゆる URL でユニークで ある必要はありません。その決定は URL と ETag キーに基づいて行われます。

def my_action(self):
    etag_cache('somekey')
    return render('/show.myt', cache_expire=3600)

または、response の他の側面を変える場合:

def my_action(self):
    etag_cache('somekey')
    response.headers['content-type'] = 'text/plain'
    return render('/show.myt', cache_expire=3600)

Note

この例では ETag キャッシュに加えてテンプレートキャッシュも使用して います。新しい訪問者がサイトを訪れた場合、キャッシュされたコピーが 存在しているならテンプレートを再レンダリングすることを避けます。そ して、そのユーザが再びそのページに訪れたなら ETag キャッシュの引き 金となるでしょう。さらにこの例では ETag キーは決して変わらないので、 ブラウザがキャッシュを持っているなら常に使用されるでしょう。

ETag キャッシュキーを変更する頻度は、 Web アプリケーションによって、そ してブラウザに対してどのぐらい頻繁にページの新しいコピーを取得させたい かに関する開発者の判断によって決まるでしょう。

Warning

Stolen from Philip Cooper’s OpenVest wiki after which it was updated and edited ...

Beaker Cache の内部

Caching

最初に、キャッシュしたいと思う何らかの 遅い 関数と共に始めましょう。 この関数は遅くありませんが、それがいつキャッシュされたかが分かるので、 期待通りにいろいろなことが働いているのを見ることができるでしょう:

import time
def slooow(myarg):
  # some slow database or template stuff here
  return "%s at %s" % (myarg,time.asctime())

キャッシュされた関数があるとき、複数の呼び出しを行うことでキャッシュさ れたバージョンか新しいバージョンのどちらを見ているかが分かります。

DBM キャッシュ

DBM キャッシュはレスポンスを dbm スタイルのデータベースに保存します (実 際には pickle します)。

必ずしも明白でないことは、キーに 2 つのレベルがあるということです。それ らは原則として、一つは関数またはテンプレート名のために (名前空間と呼ば れます)、一つは名前空間の中での「キー」のために (キーと呼ばれます) 作成 されます。そのため Some_Function_name に対しては 1つの dbm ファイル/ データベースとして作成されたキャッシュが存在します。その関数が異なった 引数で呼ばれるなら、それらの引数は dbm ファイルの中のキーになります。 最初にキャッシュを作成してデータを投入してみます。このキャッシュは 3 つ の異なる引数 x, yy, zzz によって3 回呼び出された Some_Function_name 関数のためのキャッシュとみなすことができます:

from beaker.cache import CacheManager
cm = CacheManager(type='dbm', data_dir='beaker.cache')
cache = cm.get_cache('Some_Function_name')
# the cache is setup but the dbm file is not created until needed
# so let's populate it with three values:
cache.get_value('x', createfunc=lambda: slooow('x'), expiretime=15)
cache.get_value('yy', createfunc=lambda: slooow('yy'), expiretime=15)
cache.get_value('zzz', createfunc=lambda: slooow('zzz'), expiretime=15)

まだそんなに新しいことはありません。キャッシュを作成した後は、 Beaker ドキュメントに従ってキャッシュを使用できます。

import beaker.container as container
cc = container.ContainerContext()
nsm = cc.get_namespace_manager('Some_Function_name',
                               container.DBMContainer,data_dir='beaker.cache')
filename = nsm.file

ファイル名を取得しました。ファイル名は(get_cache 関数呼び出しで使われ た) コンテナクラス名と関数名を繋げた文字列の sha ハッシュです。その戻 り値は以下のようになるでしょう。

'beaker.cache/container_dbm/a/a7/a768f120e39d0248d3d2f23d15ee0a20be5226de.dbm'

そのファイル名を使って、キャッシュデータベースの中身を直接見ることがで きます (ただし教育目的とデバッグ経験のために限ります。 not your cache interactions!)

## this file name can be used directly (for debug ONLY)
import anydbm
import pickle
db = anydbm.open(filename)
old_t, old_v = pickle.loads(db['zzz'])

データベースは単に古い時刻と値を含むだけです。有効期限や、値を作成したり アップデートしたりする機能はどこにあるのでしょうか? それらはデータベー スまで到達することはありません。それらは上の get_cache 呼び出しから返 された cache オブジェクトに備わっています。

createfunc と expiretime の値が get_value の最初の呼び出しの時に保存 されることに注意してください。その後の呼び出しで (例えば) 異なる有効期 限を渡しても、その値は更新 されません 。これは、キャッシュの tricky な部分ですが、異なるプロセスは事実上異なるポリシーを持つことにな るので、おそらく良いことです。

これらの値に関して困難があれば、 cache.clear() を呼び出せばすべて がリセットされることを覚えておいてください。

Database キャッシュ

ext:database キャッシュタイプの使い方。

from beaker.cache import CacheManager
#cm = CacheManager(type='dbm', data_dir='beaker.cache')
cm = CacheManager(type='ext:database',
                  url="sqlite:///beaker.cache/beaker.sqlite",
                  data_dir='beaker.cache')
cache = cm.get_cache('Some_Function_name')
# the cache is setup but the dbm file is not created until needed
# so let's populate it with three values:
cache.get_value('x', createfunc=lambda: slooow('x'), expiretime=15)
cache.get_value('yy', createfunc=lambda: slooow('yy'), expiretime=15)
cache.get_value('zzz', createfunc=lambda: slooow('zzz'), expiretime=15)

これは CacheManager の作成における唯一の違いを除き、上述のキャッシュ の使用法と同じです。 beaker コードの外でキャッシュを見るのは非常に簡単 です (繰り返しますが、これは啓発とデバッグのためであり、api の使用法で はありません)。

この場合は SQLite を使用しました。 SQLite データファイルは SQLite コマ ンドラインユーティリティか Firefox プラグインを使用することで直接アクセ スできます:

sqlite3 beaker.cache/beaker.sqlite
# from inside sqlite:
sqlite> .schema
CREATE TABLE beaker_cache (
        id INTEGER NOT NULL,
        namespace VARCHAR(255) NOT NULL,
        key VARCHAR(255) NOT NULL,
        value BLOB NOT NULL,
        PRIMARY KEY (id),
         UNIQUE (namespace, key)
);
select * from beaker_cache;

Warning

データ構造は Beaker 0.8 では異なっています ...

cache = sa.Table(table_name, meta,
                 sa.Column('id', types.Integer, primary_key=True),
                 sa.Column('namespace', types.String(255), nullable=False),
                 sa.Column('accessed', types.DateTime, nullable=False),
                 sa.Column('created', types.DateTime, nullable=False),
                 sa.Column('data', types.BLOB(), nullable=False),
                 sa.UniqueConstraint('namespace')
)

これは、アクセスタイムを含んでいますが、名前空間/キーの組み合わせ 1 つ に対して 1 列ではなく、名前空間 1 つに対して 1 列ベースで列を格納します (pickle された辞書を格納します)。これは、問題が限られたキーと多くの名前 空間を扱っているとき、より効率的なアプローチです — セッションのように。

memcached キャッシュ

キーの数が多く、事前にキーをルックアップするのにコストがかかる (expensive pre-key lookups) 場合、 memcached は良い方法です。

memcached がデフォルトの 11211 ポートで動いているなら:

from beaker.cache import CacheManager
cm = CacheManager(type='ext:memcached', url='127.0.0.1:11211',
                  lock_dir='beaker.cache')
cache = cm.get_cache('Some_Function_name')
# the cache is setup but the dbm file is not created until needed
# so let's populate it with three values:
cache.get_value('x', createfunc=lambda: slooow('x'), expiretime=15)
cache.get_value('yy', createfunc=lambda: slooow('yy'), expiretime=15)
cache.get_value('zzz', createfunc=lambda: slooow('zzz'), expiretime=15)
Read the Docs v: v1.0.1rc1
Versions
latest
v1.0.1rc1
v0.9.7
Downloads
PDF
HTML
Epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.