MT 3.31 + SQLiteがベラボーに遅い件について。
MT 3.31 + SQLiteの組み合わせはベラボーに遅い場合がある。普通に再構築しただけでも猛烈に時間がかかるため、簡単に500 Internal Server Errorを発生させることができる。
例によって、少し追いかけてみると、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";
次善の策としては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
なるほど・・・自分 ロリポップ+MT+SQLite のセットでしばらくになりますが最近は 1 ポストするにもストレスを感じ MySQL に移行しようかと考えていたのですが。。
申し遅れましたが(もう長いこと)影ながら小川様にはお世話になっているものです。感謝です。
>ドラゴンズ
孝介が間違いないことは周知な事(自分は日生時代何度も観戦に・・)ですが朝倉健太の復活は幸せではないでしょうか?
はじめまして。私は最近3.31にアップデートして,データベースをSQLiteに変更しました。ogawaさんのコンバータにお世話になりました。ありがとうございます。SQLiteはMySQLと比べると,サイト再構築が5分の1くらいの時間で済むので,喜んでいます。それでも1分以上はかかります。ogawaさんの5秒弱というのはびっくりですが,サイトの再構築が5秒で完了するのでしょうか。ちなみに私が使っているサーバーはロリポップです。
いいえ、本文中に書いたクエリー一回に5秒近くかかるっていう話です。通常DBのクエリーは単純なものであればその1000分の1以下で済むものなのですよ。ちなみに私の場合エントリー927個、タグ337個の環境で試していますから、MTTagsコンテナを一回レンダリングするのに(5 * 337 + α)秒、ラフに見積もって30分かかります。ベラボーだ!と言いたくもなるでしょう。
>ドラゴンズ
朝倉健太、いいですねー。岐阜出身だし。
早合点してしまいまして,失礼しました。30分かかるとは大変ですね。私のところはエントリー260個で約1分強です。MySQLの時は5分以上かかっていましたし,エラーも時々出ていましたので,現在は非常に満足しています。エントリータグはほとんど入れておりません。
はじめまして。
今回mt-db-convert.cgiを使わせて頂きました。で、その時MTも3.3にアップしたのですが、後でこのエントリーをみつけて自分の情報の薄さに参ってしまいました(笑
せっかくなのでしばらくはこのまま試そうと思ってます。
mt-db-convert.cgiエントリーの方にTBさせてもらいました。お世話になりました。