複雑なキャッシュとその効果

Movable Type 4.2では、テンプレートモジュールをキャッシュする機能が追加されています。
この機能を活用することで、再構築の処理時間を大幅に短縮することができます。

ただ、テンプレートモジュールによっては、キャッシュのやり方に注意が必要なものがあります。
その一例として、「個々のブログ記事のページに、その記事が属するカテゴリーの記事一覧を出力する」というテンプレートモジュールと、そのキャッシュの効果を紹介します。

1.テンプレートモジュールの内容

ここでは、「個々のブログ記事のページに、その記事が属するカテゴリーの記事一覧を出力する」というテンプレートモジュールを取り上げます。
このテンプレートモジュールは、ブログ記事がカテゴリーAに属している場合、そのページのサイドバーにカテゴリーAの記事をすべて表示する、といった動作をするものです。

テンプレートモジュールの内容は、以下のようなものです。
Movable Typeの標準テンプレートセットのサイドバーに組み込むことを想定しています。
このテンプレートモジュールを、「ブログ記事」テンプレートのサイドバーに組み込むことで、目的どおりの動作になります。

<MTIfNonEmpty tag="EntryCategory">
    <MTSetVarBlock name="entry_cat"><MTParentCategories glue=":"><MTCategoryLabel></MTParentCategories></MTSetVarBlock>
    <div class="widget">
        <h3 class="widget-header"><MTEntryCategory>カテゴリのブログ記事</h3>
        <div class="widget-content">
            <MTCategories>
            <MTSetVarBlock name="test_cat"><MTParentCategories glue=":"><MTCategoryLabel></MTParentCategories></MTSetVarBlock>
            <MTIf name="entry_cat" eq="$test_cat">
                <MTEntries>
                    <MTEntriesHeader>
                    <ul class="widget-list">
                    </MTEntriesHeader>
                    <li class="widget-list-item"><a href="<$MTEntryPermalink$>" title="<$MTEntryTitle$>"><$MTEntryTitle$></a></li>
                    <MTEntriesFooter>
                    </ul>
                    </MTEntriesFooter>
                </MTEntries>
            </MTIf>
            </MTCategories>
        </div>
    </div>
</MTIfNonEmpty>

なお、このテンプレートモジュールは、小粋空間のこちらの記事を参照して作りました。

2.キャッシュの必要性

このテンプレートモジュールを個々のブログ記事のページに組み込むと、各記事にカテゴリー内の記事のリストが出力されます。
記事数が増えれば、それに比例してリストを再構築する時間も長くなると考えられます。
例えば、記事数が2倍になれば、個々の記事ごとに、このテンプレートモジュールの再構築にかかる時間も2倍程度になると考えられます。

キャッシュを行わないと、このテンプレートモジュールは、すべてのブログ記事ページで再構築されます。
もし記事数が2倍になると、個々の記事ごとに、テンプレートモジュールの再構築時間が約2倍になります。
しかも、記事数が2倍になっていますので、テンプレートモジュールが再構築される回数も約2倍になります。
となれば、ブログ全体でのこのテンプレートモジュールの再構築時間は、約4倍(=2倍×2倍)になってしまいます。

このように、このテンプレートモジュールのブログ全体での再構築時間は、記事数の二乗に比例し、加速度的に増大すると考えられます。
記事数の伸び方以上に再構築時間が伸びることになり、これは何としても避けたいところです。

ここで、このテンプレートモジュールをキャッシュします。
すると、1回目の再構築は記事数に比例して時間がかかるものの、2回目以降はキャッシュから読み込むだけで済み、大幅に時間を短縮することができます。
その結果、ブログ全体でのこのテンプレートモジュールの再構築時間も、ほぼ記事数に比例する程度に抑えることができます。

このテンプレートモジュールは、カテゴリーが変化したときや、ブログ記事が変化したときに、再構築しなおす必要があります。
そこで、キャッシュの設定では、「作成または更新後に無効にする」をオンにし、「ブログ記事」と「カテゴリ」のチェックをオンにします。

↓キャッシュの設定
catcache.png

2.キーを指定してキャッシュする

このテンプレートモジュールは、ブログ記事が属するカテゴリーによって、出力結果が異なります。
例えば、カテゴリーAに属する記事でこのテンプレートモジュールを再構築すると、カテゴリーAの記事一覧が出力されます。
一方、カテゴリーBに属する記事でこのテンプレートモジュールを再構築すると、カテゴリーBの記事一覧が出力されます。

ところが、このテンプレートモジュールを単純にキャッシュすると、最初に再構築された記事のキャッシュがすべての記事で使われ、出力が正しくなくなります。
例えば、最初に再構築した記事がカテゴリーAに属しているとすると、すべての記事のページに、カテゴリーAの記事一覧が出力されてしまいます。

そこで、このテンプレートモジュールをキャッシュする際には、カテゴリーごとにキャッシュを作り分ける必要があります。
それには、MTIncludeタグに「key」というモディファイアを指定します。
ここで取り上げている例だと、カテゴリーごとにキーの値を変えることで、カテゴリーごとにキャッシュを分けることができます。

このテンプレートモジュールに「同一カテゴリーのブログ記事」と名づけたとすると、MTIncludeタグまわりの書き方は、以下のようにします。
2行目のMTSetVarBlockタグで、「cache_key」という名前の変数に、「cat(記事のカテゴリーID)」のような値を代入しています。
そして、3行目のMTIncludeタグのkeyモディファイアで、変数cache_keyの値をキーとして使うことを指定しています。
また、このテンプレートモジュールはブログ記事テンプレートにだけ組み込みたいので、1行目のMTIfArchiveTypeタグを使って、ブログ記事テンプレートのときだけ処理されるようにしています。

<MTIfArchiveType archive_type="Individual">
<MTSetVarBlock name="cache_key"><MTEntryPrimaryCategory>cat<$MTCategoryID$></MTEntryPrimaryCategory></MTSetVarBlock>
<MTInclude module="EntryCategoryEntries" key="$cache_key">
</MTIfArchiveType>

3.キャッシュの効果

このテンプレートモジュールをキャッシュするかどうかで、再構築時間がどの程度変化するかを調べてみました。
MTBooterプラグインを使ってテスト用記事を100、200、...、800件と100件ずつ増やして作成し、以下の3つのケースで、ブログ記事アーカイブの再構築時間を比較しました。
なお、いずれのケースも、「カテゴリアーカイブ」と「月別アーカイブ」のウィジェットをキャッシュしています(これらのウィジェットも、記事数によって再構築時間が伸びると考えられるので、その影響を排除するため)。

ケース内容
ケースAこのテンプレートモジュールを組み込まない
ケースBこのテンプレートモジュールを組み込み、キャッシュを行う
ケースCこのテンプレートモジュールを組み込むが、キャッシュを行わない

結果は以下の通りです(単位は秒)。

記事の
件数
ブログ記事アーカイブ全体の
再構築の所要時間(秒)
記事1件あたりの
再構築の所要時間(秒)
ケースAケースBケースCケースAケースBケースC
1001417220.1400.1700.220
2003032540.1500.1600.270
30042501020.1400.1670.340
40061671480.1530.1680.370
50076842080.1520.1680.416
600911002780.1520.1670.463
7001061173650.1510.1670.521
8001211334660.1510.1660.583

↓ブログ記事アーカイブ全体の再構築の所要時間のグラフ
catcache2.png

↓記事1件あたりの再構築の所要時間
catcache3.png

ケースB(キャッシュした場合)では、テンプレートモジュールを組み込まない場合に比べて、再構築時間の増加はわずかです。
また、記事数が増えても、記事1件あたりの再構築時間はほぼ一定しています。

一方、ケースC(キャッシュしない場合)では、記事が増えると加速度的に再構築時間が伸びています。
テンプレートモジュールの再構築時間が長くなるため、記事1件あたりの再構築時間が伸び、さらに記事数も増えているので、加速度的な伸びになってしまいます。

4.まとめ

記事数の増加によって再構築時間が伸びるテンプレートモジュールは、可能な限りキャッシュすることをお勧めします。
また、そのテンプレートモジュールの出力が状況によって変化する場合は、MTIncludeタグにkeyモディファイアを指定して、キーごとにキャッシュが作られるようにします。