たった1つのファイルの差し替えで再構築が約20%高速になる魔法の MT::Builder
公開日 : 2016-12-25 20:04:39
この記事は Movable Type Advent Calendar 2016 の最終日の記事です。
Movable Type Advent Calendar は2012年から参加していて毎年最終日を担当させていただいています。過去の記事はこちら。
- やはりお前らのMTは間違っている! (2015年)
- 部屋と Movable Type と私 (2014年)
- Movable Type をめっちゃ高速化する20の方法 - 2013年Xmasバージョン (2013年)
- github.com/alfasadoで公開しているMTのプラグイン/ツールのドキュメントを一挙公開! (2012年)
2016年のネタ。2017年、MTはもっと高速になる!
今年は何を取り上げようかと思っていましたが、丁寧に記事を書くモチベーションと時間が足りず、とはいえ負けるのも(誰に?)悔しいので、以前から気になっているMTの箇所にパッチを当てて再構築のスピードを速める実験をしてみました。
静的ファイルジェネレーターとしての Movable Type
少しだけもったいつけて前振りを。静的サイトジェネレーター Advent Calendarで Movable Typeの中の人である長内さんが記事を書かれていますね。そう、MTは静的ファイルを吐き出すことのできるCMSです(ダイナミックパブリッシングも可能、部分的ファイナミックパブリッシングも可能、もちろんダイナミック処理ではキャッシュの仕組みを備えています)。
今年は第2回CMSプロレス 多言語サイトタイトルマッチ - CMS SUNDAYにお呼ばれして、チームMT(一人だけど)として参加、見事に勝利してきました。Movable Type以外の参加CMSはDrupal、concrete5、TYPO3、WordPress。Movable Typeだけが異質だと思いませんか?
- MT以外のCMSはすべてダイナミック前提
- MT以外のCMSはすべてPHP(MTは基本Perl、ダイナミックはPHP)
PHP、Perlの違いはまぁ置いておいて、ダイナミックとスタティック(静的サイト)だと、文化が違うというか話がかみ合わないんです。同じく中の人の澤さんに「勘所がわからん!」とか言ってますね。
そう、動的、ダイナミックなCMSが「高速ですよ」っていったらみんなキャッシュの話。静的ファイルジェネレーターでキャッシュって、あるとしたらCDNやwebサーバーチューニングとかの話です。もちろんダイナミックも持ってるからキャッシュの話ついていけるけど、URLリライト系の話も盛り上がるみたいなんですよねあっちの文化では。てことで勘所あんまりわかりませんでした前半は。
静的ファイルジェネレーターで「高速」ってのは一般にはいわゆるパブリッシュ(再構築)の話ですよね。Movable Typeの再構築はしばしば待ち時間が嫌だとかそういう話になります。ただ、エンタープライズCMSの世界では、そこあんまり話題にならないんですよね。CMS操作の重さより、公開サイトの遅さや負荷とかそっちを気にされます。ま、そういうのも含め(使われているサイトの規模や用途も含め)て文化の違いとしか表現しようがありません。ま、なんとか勝ててよかったです。
MTの再構築はまだ速くなる余地がある
今年、ある案件で公開側のサイトのダイナミック処理の速度が思ったように出ずに調査していた時に、テンプレートのコンパイルキャッシュが非効率な処理になっているケースを発見しました。以下の記事にその顛末をまとめています。
その時に「あれ? 静的ファイルの再構築処理の際の MTタグ(MTML)のコンパイルってどうなってんだっけ? って見たんです。どうもキャッシュされている気配がない。
で、少し手を入れてみたんですがうまく行かず、途中のファイルをFogBugzに投げてそこで止まってました。
MT::Builder の compile に手を入れる
MT::Builder の compile てのは、乱暴にひと言で言えば、テンプレートのMTタグをパースしてコンパイルして{tokens}を組み立てているところです。そのあと、テンプレートはコンテキストに沿ってビルドされます。MTはSQLの実行結果やビルドしたブロックやモジュールなどはマメにキャッシュしていますが、このコンパイル結果をキャッシュしていません。以下、見てもらえますか!? そう、MTタグを正規表現でパースしているんですよ、ここ。
while ( $text
=~ m!(<\$?(MT:?)((?:<[^>]+?>|"(?:<[^>]+?>|.)*?"|'(?:<[^>]+?>|.)*?'|.)+?)([-]?)[\$/]?>)!gis
)
{
ご存知の通り(知らない人も多いでしょうけど)、Movable Typeでは、再構築処理を行う際に1つのCGIへのリクエストあたり40記事ファイルずつ再構築を行い、リダイレクトを繰り返してすべてのファイルを再構築します。この40という数字は環境変数で変えることができます。
つまり、1リクエストでの再構築処理で同じテンプレートが少なくとも40回パースされるわけです。こいつを一回で済ませてしまえば。
mt/lib/MT/Builder.pm
12a13
> use Data::Dumper;
32a34
> my $is_tmpl;
34a37,39
> if ( $ctx->id ) {
> $is_tmpl = $ctx->id;
> }
73a79,88
>
> my $cache_key;
> my $chached_state;
> if ( $is_tmpl && $text ) {
> $cache_key = 'compiled-cache-' . MT::Util::perl_sha1_digest_hex( $text );
> if ( $opt ) {
> $cache_key .= '.' . MT::Util::perl_sha1_digest_hex( Dumper $opt );
> }
> $chached_state = MT->request( $cache_key );
> }
82a98,101
>
> if (! $chached_state )
> {
>
291a311,316
>
> } else {
> $state = $chached_state->{ state };
> $depth = $chached_state->{ depth };
> $errors = $chached_state->{ errors };
> }
306c331,338
<
---
> if ( $cache_key ) {
> if (! $chached_state ) {
> $chached_state = { depth => $depth,
> state => $state,
> errors => $errors };
> MT->request( $cache_key, $chached_state );
> }
> }
ソースファイル(Builder.pm)
マシンスペックの高い環境では、EntriesPerRebuildの値を大きくするほどその効果が顕著になり処理も速くなります。
計測結果
このブログをエクスポートして、最新版のMT6.32にテーマ Rainier 1.22 を入れた環境を用意して計測しました。「デフォルト、すべてのファイルの再構築計測結果(ケース1)」と、「記事アーカイブのみ、EntriesPerRebuildに1000を指定した場合(ケース2)」各5回の計測結果はこちら。
計測ケース | ケース1 | ケース2 | ||
---|---|---|---|---|
Builder.pm | オリジナルBuilder.pm | 改良Builder.pm | オリジナルBuilder.pm | 改良Builder.pm |
1回目 | 331 | 243 | 216 | 155 |
2回目 | 302 | 254 | 270 | 170 |
3回目 | 285 | 234 | 211 | 180 |
4回目 | 301 | 255 | 189 | 155 |
5回目 | 281 | 245 | 170 | 155 |
合計(秒) | 1500 | 1231 | 1056 | 815 |
結果 | 約18%高速 | 約22%高速 |
2017年、もっとMTが速くなるための宿題
あまり時間もなかったので、今日のところはこれまでです。引き続き以下のような部分を検討して実装できれば2017年、MTの再構築はもっと速くなることでしょう!
- まずは、このコードの影響度など、問題ないかの確認・検証
- 現状template_id 指定がある場合のみキャッシュしているが、これ以外にキャッシュできるケースがないかの検討
- リクエストをまたがって memcachedやMTのキャッシュ(MT::Session)などへのキャッシュ
それでは、メリークリスマス、そして、良いお年をお迎えください!