必然的に (Inevitably)、アプリケーション開発やデプロイの際に何らかのタス クが完了するのに非常に時間がかかることがあります。このような場合、処理 を速くする最も良い方法が caching です。
Pylons にはキャッシュミドルウェアが有効な状態で付属しています。それはセッ ションの取り扱いを提供するのと同じパッケージである Beaker. の一部です。 Beaker はいくつかの異なる種 類のキャッシュバックエンドをサポートします: メモリベース, ファイルシス テム, そして特別な memcached です。
Pylons では、速度低下が起こる場所に応じて、データをキャッシュするいくつ かの方法があります:
Note
後者はまるまる 1 ページをキャッシュできる場合にだけ有効です。 (訳注: 後者が何を指しているのか不明)
キャッシュを行うときに覚えておくべき 2 つの基本概念は i) キャッシュには 名前空間 があり、 ii) 名前空間の下でキャッシュは キー を持つことが できるということです。この理由は、単一のテンプレートに対して複数のテン プレートのバージョンがそれぞれ自身のキャッシュされたバージョンを必要と するかもしれないからです。名前空間におけるキーは バージョン です。 そしてテンプレートの名前は 名前空間 です。 これらの値の両方とも、 Python 文字列でなければなりません。
テンプレート中では、キャッシュの「名前空間」はレンダリングされるテンプ レートの名前に自動的に設定されるでしょう。基本的なキャッシュに対して他 には何も必要ありません。例外は、テンプレートがどれくらい長い間キャッシュ されるかを開発者が制御したい場合、かつ/または、テンプレートの複数のバー ジョンのキャッシュを維持したい場合です。
Stephen Pierzchala の Caching for Performance (stephen@pierzchala.com) も読んでください。
コントローラの中では、 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')
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.cache で beaker_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 引数でさらにキャッシュキーをカスタマイズすることができます。
任意の関数で 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_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 ...
最初に、キャッシュしたいと思う何らかの 遅い 関数と共に始めましょう。 この関数は遅くありませんが、それがいつキャッシュされたかが分かるので、 期待通りにいろいろなことが働いているのを見ることができるでしょう:
import time
def slooow(myarg):
# some slow database or template stuff here
return "%s at %s" % (myarg,time.asctime())
キャッシュされた関数があるとき、複数の呼び出しを行うことでキャッシュさ れたバージョンか新しいバージョンのどちらを見ているかが分かります。
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() を呼び出せばすべて がリセットされることを覚えておいてください。
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 された辞書を格納します)。これは、問題が限られたキーと多くの名前 空間を扱っているとき、より効率的なアプローチです — セッションのように。
キーの数が多く、事前にキーをルックアップするのにコストがかかる (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)