Jul 18, 2006

MT 3.31 + SQLiteがベラボーに遅い件について。

MT 3.31 + SQLiteの組み合わせはベラボーに遅い場合がある。普通に再構築しただけでも猛烈に時間がかかるため、簡単に500 Internal Server Errorを発生させることができる。

「Movable Type 3.31にアップグレードして遅くなった」という理由でこれからMySQLからSQLiteに移行しようとしている方は少し思いとどまってください。この記事では現時点でSQLiteに移行すべきでないことを理由付ける技術的な説明とその問題の解決が書いてあります。よく内容が理解できない場合には安易にここに書かれた方法を試すことよりはMySQLでの運用をひとまず継続することを薦めます。次のマイナーバージョンアップでの抜本的な対策を待った上でSQLiteに移行する方が賢明です。すでにSQLiteを使っている場合にはこの記事が参考になると思います。

例によって、少し追いかけてみると、lib/MT/Template/ContextHandlers.pmの_hdlr_tagsや_hdlr_entry_tagsの以下の部分がベラボーに遅い(念のため、Ogawa::Buzz: MT 3.31の MTEntryTagsの性能バグで示しているパッチを当てている場合には、_hdlr_entry_tagsのこの部分は_hdlr_tag_rankの中に移動しているはず)。

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
});

ベラボーに遅いこの部分は、要するに下のようなSQLを実行している。

SELECT count(*) FROM mt_entry, mt_objecttag
    WHERE objecttag_blog_id = 1 AND objecttag_tag_id = 237 AND
          objecttag_object_datasource = 'entry' AND entry_id = objecttag_object_id AND
          entry_status = 2;

何でもないJoin演算だが、これの実行がSQLiteではとんでもなく遅い。あり得ないくらい遅い。私の環境で5秒弱もかかる(MySQLでは観測不能なくらい速い)。しかもこの部分は、object_tag_idの値を変えながらタグの個数分だけ実行されるので、100個もタグがあればこの部分だけで8分を超えるってことだ。Internal Server Errorが発生するのもむべなるかな。

SQLiteの名誉のために言っておくと、以下のように書けばチョッパヤで答えを出してくれるのでSQLiteが悪いわけではなく、使い方が悪い(MySQLが速いのは、たまたまinner joinを最適化して実行してくれるからに過ぎない)。

SELECT count(*) FROM mt_objecttag NATURAL LEFT OUTER JOIN mt_entry
    WHERE objecttag_blog_id = 1 AND objecttag_tag_id = 237 AND
          objecttag_object_datasource = 'entry' AND entry_id = objecttag_object_id AND
          entry_status = 2;

このSQL文を生成させるには、以下のような修正を加えればよい。ただし、この変更の影響の及ぶ範囲を精査していないので意図せぬ動作をする可能性は残る。

--- MT-3.31-ja/lib/MT/ObjectDriver/DBI/sqlite.pm.bak Sat Apr 22 12:18:10 2006
+++ MT-3.31-ja/lib/MT/ObjectDriver/DBI/sqlite.pm Tue Jul 18 18:11:13 2006
@@ -83,7 +83,7 @@
         my $j_tbl = $j_class->datasource;
         my $j_tbl_name = 'mt_' . $j_tbl;
 
-        $sql = "from $tbl_name, $j_tbl_name\n";
+        $sql = "from $j_tbl_name NATURAL LEFT OUTER JOIN $tbl_name\n";
         ($w_sql, $w_terms, $w_bind) =
             $driver->build_sql($j_class, $j_terms, $j_args, $j_tbl);
         push @$w_terms, "${tbl}_id = ${j_tbl}_$j_col";
2006-07-25追記: お、3.32のbranchに入ったぽい。

次善の策としてはjoinせずにごり押しする方法。この方法ではInternal Server Errorは避けられるかもしれないが、SQLエンジンで実行するよりかなり効率悪いのでちょっと二の足を踏むところ。

my @otags = MT::ObjectTag->load(undef, {
    tag_id => $tag->id,
    blog_id => $blog_id,
    object_datasource => $type,
});
my @eids;
push @eids, $_->object_id foreach (@otags);
my $count = MT::Entry->count({ status => MT::Entry::RELEASE(), id => \@eids });

About Me

My Photo

つくばで働く研究者

Total Pageviews

Amazon

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