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

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の以下の部分がベラボーに遅い(念のため、MT 3.31のMTEntryTagsの性能バグ - Ogawa::Memorandaで示しているパッチを当てている場合には、_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 });

このエントリーのトラックバックURL: http://as-is.net/mt/mt-tb.cgi/418

Links referred to this entry

Comments (5)

  1. なるほど・・・自分 ロリポップ+MT+SQLite のセットでしばらくになりますが最近は 1 ポストするにもストレスを感じ MySQL に移行しようかと考えていたのですが。。
    申し遅れましたが(もう長いこと)影ながら小川様にはお世話になっているものです。感謝です。
    >ドラゴンズ
    孝介が間違いないことは周知な事(自分は日生時代何度も観戦に・・)ですが朝倉健太の復活は幸せではないでしょうか?

  2. はじめまして。私は最近3.31にアップデートして,データベースをSQLiteに変更しました。ogawaさんのコンバータにお世話になりました。ありがとうございます。SQLiteはMySQLと比べると,サイト再構築が5分の1くらいの時間で済むので,喜んでいます。それでも1分以上はかかります。ogawaさんの5秒弱というのはびっくりですが,サイトの再構築が5秒で完了するのでしょうか。ちなみに私が使っているサーバーはロリポップです。

  3. いいえ、本文中に書いたクエリー一回に5秒近くかかるっていう話です。通常DBのクエリーは単純なものであればその1000分の1以下で済むものなのですよ。ちなみに私の場合エントリー927個、タグ337個の環境で試していますから、MTTagsコンテナを一回レンダリングするのに(5 * 337 + α)秒、ラフに見積もって30分かかります。ベラボーだ!と言いたくもなるでしょう。

    >ドラゴンズ
    朝倉健太、いいですねー。岐阜出身だし。

  4. 早合点してしまいまして,失礼しました。30分かかるとは大変ですね。私のところはエントリー260個で約1分強です。MySQLの時は5分以上かかっていましたし,エラーも時々出ていましたので,現在は非常に満足しています。エントリータグはほとんど入れておりません。

  5. はじめまして。
    今回mt-db-convert.cgiを使わせて頂きました。で、その時MTも3.3にアップしたのですが、後でこのエントリーをみつけて自分の情報の薄さに参ってしまいました(笑
    せっかくなのでしばらくはこのまま試そうと思ってます。
    mt-db-convert.cgiエントリーの方にTBさせてもらいました。お世話になりました。

Post a comment

Remember me?