ダメ人間オンライン

あまり信用しないほうがいい技術メモとか備忘録とかその他雑記

Amon2::Plugin::Web::PageCacheというのを書いた

レスポンスのHTMLをまるっとキャッシュするplugin書いた。書いたというか別のWAFで使ってたやつをAmon2用に書き直した。キャッシュ先はmemcachedです。

dameninngenn/p5-Amon2-Plugin-Web-PageCache · GitHub

やりたかったこと

  • requestのpath毎にキャッシュするか否か設定できるようにしたい
  • requestのpath毎にキャッシュのexpire設定できるようにしたい
  • /path/to?p=1 と /path/to?p=2 は別のものとしてキャッシュしたい
    • 想定してないクエリがついてきた場合はスルーしてクエリがついてないものと同じ扱いにしたい
  • requestのpath毎にキャッシュ削除ができるようにしたい
  • HTMLだけじゃなくてJSON返してる場合もキャッシュしたい
  • そこそこお手軽に使えるようにしたい

使い方

モジュールをインストールして、pluginを読み込んで、config.plに設定書くだけです。

    package MyApp::Web;
    use Amon2::Web;

    __PACKAGE__->load_plugin('Web::PageCache');
# config.pl

'PAGE_CACHE' => +{
    enable => 1,
    memcached => +{
        servers => [
            '127.0.0.1:11211'
        ],
        namespace => 'page_cache:',
    },
    path => +{
        'MyApp::Web' => +{
            '/'                         => { expire => 60 },
            '/detail'                   => { expire => 300, query_keys => [qw/p/] },
            '/member/{id:[0-9]+}'       => { expire => 60 },
            '/list'                     => { expire => 60, query_keys => [qw/p rows/] },
        },
        'MyApp::Mobile' => +{
            '/'                         => { expire => 60 },
        },
    },
},
enable

pluginを有効にするか無効にするか指定します。1だと有効0だと無効。
開発環境とかでサッと有効無効を切り替えられるように。

memcached

memcachedの設定です。Cache::Memcached::Fastを使ってるのでそれに渡す設定を書きます。

path

path毎の設定です。
context名とpathとそれに対する設定項目を書きます。PC用とスマートフォン用とかで分けててもそれぞれ設定できるようにしてます。

pathに指定できるフォーマットは Amon2::Web::Dispatcher::RouterSimple(Router::Simple::Route) で使えるものと同じです。

/blog/{year:\d{4}}
/blog/:year
/blog/*/*
normal string

/blog/:year と指定した場合 /blog/2012 と /blog/2013 は当然ですが別のものとしてキャッシュされます。
ここで指定していないpathはキャッシュの対象外になります。あと200番以外のステータスコードだった場合キャッシュしません。

expireはキャッシュが切れるまでの時間を秒で指定します。
expire => 60 と指定するとキャッシュが作られてから60秒間は同じ内容のものが返ります。

query_keysに何も指定しなければどんなクエリがついてこようとも同じ内容を返します。
query_keysに何か指定されていれば指定されたクエリについては別物として扱います。
例えば query_keys => [qw/p/] と指定した場合 /path/to?p=1 と /path/to?p=2 でそれぞれキャッシュされます。
指定したクエリ以外がついてきた場合 /path/to と /path/to?unknown=1 は区別されません。

キャッシュの削除

pluginを読み込むと delete_page_cache というメソッドが生えるのでこれを使います。
例えば

sub edit {
    my ($self, $c) = @_;

    # 何かupdateとかするやつ
    $c->nanika_update_suruyatu( $c->req );

    # 該当部分のキャッシュを削除してすぐ反映されるように
    $c->delete_page_cache({ name => 'MyApp::Web', path => '/detail' });

    $c->redirect('/dokoka');
}

こんな感じでpath毎にキャッシュを削除できます。/detail?p=2 /detail?p=3 みたいにクエリがついてる場合も上記の書き方で全部キャッシュ消してくれます。
ここらへんの仕組みは Catalyst::Plugin::PageCache を参考にしました。

Amon2標準で用意されている BEFORE_DISPATCH と AFTER_DISPATCH トリガーで動きます。なので特に書き換えない限りはキャッシュを返すだけの時も AFTER_DISPATCH のやつは動くので他になんか AFTER_DISPATCH のとこでやっててそこを通らせたくないって時はなんやかんやして下さい。

あとページキャッシュをpathによらず全消ししたい時は普通にflush_allすればいいと思ってるので全消しのメソッドは用意してません。pageキャッシュ用のmemcachedは他のと共用じゃなく別portとかで立てておけばめんどくさいこと考えなくていいんじゃないでしょうか。

モヤモヤ

あまりめんどくさいことせずに誰でもパッと使えるようにしたかったのでconfig.plに設定もろもろ書くようにしたけど、設定したいpathが増えてきた時にconfig.plの見通しが非常に悪くなるのでどうしたもんかなと。

delete_page_cacheは delete_page_cache('MyApp::Web', '/detail') にするかとか迷ったけど今の指定の仕方にした。

ドキュメントの英語がひどい。writing力低すぎて泣きそう。