Jul 13, 2006

MT 3.31の MTEntryTagsの性能バグ

Movable Type 3.3日本語版がリリースされてから2週間ほど経ち、3.31が出てしまったがまだ移行できないでいる。なぜできないかと言えば再構築がとても遅いからなのである。Apache 2.2.2 + mod_fcgidの組み合わせですべての個別エントリーアーカイブを再構築したとき、3.2では要する時間は40秒程度。ところが3.3では3分強かかる。ありていに言って4~5倍。これでは話にならない。

少し追っかけてみて...分かった。

実は3.3で導入された「タグ」を使わなければこの問題は表面化しない。なので最初、TagSupplementals Pluginmt-keywords2tagsのせいだと思っていたのだが、まったくそうではなかった。

そうではなくて、MTEntryTagsコンテナのレンダリング時の処理に問題がある。

まず、MTEntryTagsコンテナを含むテンプレートの再構築のたびに、該当ブログの全タグ情報をデータベースから読み込む。実際にはこの全タグ情報はキャッシュされるので、CGIの場合には「再構築のリクエスト回数」だけ同じ処理が行われると考えればよい。例えば、エントリー総数1000で、EntriesPerRebuildの設定が40のとき、25回全タグをデータベースから読み込むことになる。ただし、FastCGI環境の場合には継続中のアプリケーションインスタンスが再利用されることで1~数回で済む公算がある。

次に、読み込んだ(あるいはキャッシュにある)全タグ情報に対して、各タグを利用しているエントリーの個数を数え上げている。この処理はMTEntryTagsをレンダリングするたびに行われるので、CGIでもFastCGIでもエントリー総数1000の場合には1000回この処理を行うことになる。インデックステンプレートなどでMTEntryTagsを繰り返し呼び出す場合も悲惨。

総合的に考えると、タグの総数はエントリーの総数に比例するのでO(n2)の時間がかかるはず、ということになる。

この処理はそもそも全タグ情報をキャッシュしたり、全タグのランク(MTTagRank)を取得したりする目的で行われている。例えばMTEntryTagsコンテナを利用した後、MTTagsコンテナを利用した場合などには前処理を幾分省略できる。だが、MTTagsコンテナの処理でも同様にキャッシュを行っているし、エントリーで全タグ情報を参照することは、MTEntryTagsの中でMTTagRankを使う場合を除いてはない。だから安直に考えれば処理そのものをなくせば大幅に高速化される。実際、以下の変更を加えれば3.2 + Tagwire Pluginと遜色ない速度で動作する。

--- MT-3.31-ja/lib/MT/Template/ContextHandlers.pm.bak Fri Jul  7 02:11:02 2006
+++ MT-3.31-ja/lib/MT/Template/ContextHandlers.pm Thu Jul 13 01:50:05 2006
@@ -559,34 +559,10 @@
     
     require MT::ObjectTag;
     require MT::Entry;
-    my $type = MT::Entry->datasource;
     my $entry = $ctx->stash('entry');
     return '' unless $entry;
     my $glue = $args->{glue} || '';
 
-    my $blog_id = $ctx->stash('blog_id');
-    my $tags = _tags_for_blog($blog_id, $type);
-    my $min = 0;
-    my $max = 0;
-    foreach my $tag (@$tags) {
-        my $count = MT::Entry->count({
-            status => MT::Entry::RELEASE()
-        }, { join => [ 'MT::ObjectTag', 'object_id', {
-                       tag_id => $tag->id,
-                       blog_id => $blog_id,
-                       object_datasource => $type
-                     } ],
-             unique => 1
-        });
-        $tag->{__entry_count} = $count;
-        $min = $count if ($count && ($count < $min)) || $min == 0;
-        $max = $count if $count && ($count > $max);
-    }
-    @$tags = grep { $_->{__entry_count} } @$tags;
-
-    local $ctx->{__stash}{tag_max_count} = $max;
-    local $ctx->{__stash}{tag_min_count} = $min;
-    
     my $iter = MT::Tag->load_iter(undef, { 'sort' => 'name',
         join => ['MT::ObjectTag', 'tag_id',
         { object_id => $entry->id, blog_id => $entry->blog_id, object_datasource => MT::Entry->datasource }, { unique => 1 } ]});

上の変更では、MTEntryTagsの中でMTTagRankが使えなくなってしまうので本当は良くない。tag_max_count, tag_min_countなどの計算をMTTagRankの最初のレンダリング時まで遅延すれば、タグの振る舞いを変えることなくMTTagRankを含まないMTEntryTagsを高速化できる。この修正はここに掲載するには少し長いので以下で参照のこと。

MT_3_31_ja_UO_Patch - ogawa - Google Code

2006-08-06追記: 3.32のbranchにマージされたっぽい。これなら安心して使えるかな。

実はこの問題が解決しても簡単には乗り換えられないもう一つの理由がある。

それはFastCGI環境で利用した場合のResidentのサイズがかなり大きいということだ。3.2では16MB弱程度なのだが、3.3では20MBに達する。また、再構築などを行うと3.2より早くResidentが巨大化する。例えば、個別エントリーアーカイブの全再構築を2回ほど行うと50MBにも達する。以前のバージョンから常駐環境でリクエストごとに発生するメモリリークは大なり小なりあった問題だが、3.3ではアプリケーション自体の巨大化も相まってFastCGI化を断念せざるを得ない水準になっているというのが正直な感想だ。

About Me

My Photo

つくばで働く研究者

Total Pageviews

Amazon

Copyright 2012 Ogawa::Buzz | Powered by Blogger
Design by Web2feel | Blogger Template by NewBloggerThemes.com