MT3でなぜエントリの追加に時間がかかるようになるのか
Ogawa::Memoranda: Movable Type 3.0のIndividual Entry Archiveの命名方式の問題点の落ち穂拾いで、私の好きそうなネタです。
気が付いている人がいるかどうかは分かりませんが、MT3では使っているうちに、徐々にRebuildに要する時間が増大するのはもちろんですが、1エントリの追加にかかる時間も増大します。一般的に言って、増大する度合いがエントリ数に比例する程度なら問題ありませんが、エントリ数の2乗、3乗に比例するようだと速度低下が目に付くようになります。
これは以前も述べたmake_unique_basenameという手続きが原因で起きます。
MT::Util::make_unique_basenameは新しいエントリを作るたびに必ず1回呼び出され、そのエントリのbasenameを決定します。このmake_unique_basenameは以下のような手続きからなっています。
sub make_unique_basename {
my ($entry) = @_;
my $blog = $entry->blog;
my $title = $entry->title;
unless ($title) {
if (my $text = $entry->text) {
$title = MT::Util::first_n_words($text, 5);
}
$title = 'Post' unless $title;
}
my $base = substr(MT::Util::dirify($title), 0, 15);
$base =~ s/^_+//;
$base =~ s/_+$//;
$base = 'post' if $base eq '';
my $i = 1;
my $base_copy = $base;
while (MT::Entry->count({ blog_id => $blog->id,
basename => $base })) {
$base = $base_copy . '_' . $i++;
}
$base;
}
この手続きをおおまかに説明すると、まずタイトルもしくは本文をdirifyした文字列の最長15文字を取り出します。次にこの文字列をbasenameに持つエントリがないかどうかを、「SELECT COUNT(*) FROM mt_entry WHERE basename=...」の戻り値が、0(=マッチするエントリなし)か、0でないか(=マッチするエントリあり)かによって判定します。
マッチするエントリがあれば、文字列に「_1」を付加して再度SELECT COUNT(*)を実行します。これを「_2」、「_3」、…と繰り返し、マッチするエントリがない状態になったらそれをベースネームとして返します。
例えば、このBlogではタイトルがすべて日本語からなるエントリが400個あります。これらのエントリのbasenameはpost, post_1, post_2, ..., post_399と付けられているわけです。次にすべて日本語からなるタイトルを持つエントリを作ったとすると、401回SELECT COUNT(*)を実行して初めてpost_400というbasenameが付けられるわけです。
少し形式的な書き方をすると、エントリ数をNとすると、一回のインデックススキャン(SELECT COUNT(*))の処理時間は最良でO(1)、平均でO(log N)、一回のmake_unique_basenameはこのシーケンススキャンをO(N)回(※)行います。したがって、エントリを1個追加するのに要する時間はO(N log N)となります。Acceptableかどうかはギリギリというところでしょう。念のためこれはMySQL, PostgreSQLの場合であり、BerkeleyDBではO(N2)になる可能性があります。
どうしてもO(N)にしたければ以下のようなコード(正確な動作はオリジナルと異なります)にすればよいわけですが、普段たいていO(1)で済んでいた英語国民には不幸になります。というかこっちはこっちで重い操作をやっていて、試しに書いてみたというだけのものです。お使いの環境によっては高速化されるとは限りません。
sub make_unique_basename {
my ($entry) = @_;
my $blog = $entry->blog;
my $title = $entry->title;
unless ($title) {
if (my $text = $entry->text) {
if (MT::ConfigMgr->instance->DefaultLanguage eq 'ja') {
$title = MT::I18N::first_n_text($text, 10);
} else {
$title = MT::Util::first_n_words($text, 5);
}
}
$title = 'Post' unless $title;
}
my $base = substr(MT::Util::dirify($title), 0, 15);
$base =~ s/^_+//;
$base =~ s/_+$//;
$base = 'post' if $base eq '';
my @bnames = grep {/^$base(\_[0-9]+)?$/} map {$_->basename}
MT::Entry->load({ blog_id => $blog->id });
return $base unless @bnames;
my $max = -1;
foreach my $bname (@bnames) {
my $bidx = ($bname =~ /$base\_(.*)/) ? $1 : 0;
$max = $bidx if $max < $bidx;
}
return $base . '_' . ++$max;
}
よくある質問とその答え
Q1. 「個別のアーカイブへのリンクに以前の形式(id)を利用」したり、アーカイブファイルの形式を指定すれば、この問題を回避できますか?
A1. できません。エントリを追加した際にmake_unique_basenameが必ず呼ばれてしまいます。回避するとすれば、make_unique_basenameの定義を書き換えるか、プラグインなどで上書きする必要があります。
Q2. そんなに遅くなった気がしないのですが?
A2. ここで述べている問題は「下書き」状態でエントリを保存するのにかかる時間、あるいはエクスポートデータのインポートにかかる時間、に関するものだと考えてもらった方が直感に合っています。make_unique_basenameはそのエントリが「下書き」にしろ「公開」にしろ最初に保存されたときに一回だけ呼び出されるのですから。
もっともエントリの追加と同時に公開+再構築を行う場合などは、make_unique_basenameの処理時間は一般に無視できるかもしれません。なぜなら再構築はテンプレートの解釈やファイルI/Oを伴うもっとずっと重い操作ですから。しかし、再構築の処理オーダーがO(N)であるのなら、いずれmake_unique_basenameの処理オーバーヘッドが顕在化することも予想できます。
このエントリーのトラックバックURL: http://as-is.net/mt/mt-tb.cgi/180
日本語(2バイト文字ってやつですか)は何かと不便ですねぇ。
MTなどブログツール(っていうのかな?)にとってどんどん重くなるって言うのは宿命なんですかねぇ?
単に仕様の煮詰め方が足りないせいだと思いますね。post_400とかpost_1000とかなってしまう状況は想定外なのでしょう。
うーん、unique_basenameは便利でもあると思うのですが、
使用する文字によって(というか英語圏以外の人間)は、
仕様上無駄な処理が増えるだけになってしまうんですね…。
<$MTEntryDate format="%Y%b%d%k%M"$>
のような感じの作成時刻をunique_basenameにするように、
書き換えてしまえばいいんですかね?(美しくないですが)。
内容に誤りがありました。すみません。
日付を使うことに関してですが、tugaaさんの書かれている形式を採用するのもありでしょう。ただし、make_unique_basenameはそのエントリが作成された時点で呼び出されるので後で日時を変更してもbasenameには反映されません。このbasenameというのはほとほと「使えない奴」なわけです。
basenameを使わないことが前提なら、常に固定文字列を返すようにしてしまうのが一番手っ取り早い高速化方法です。
Keywordなどを使ってpermalinkを(export/importでも)不変にしてるようなところは、unique_basenameは不要ですよね。
そのようなときは、「ウェブログの設定」で「以前の形式の個別アーカイブへのリンクをつかう」をチェックしておけばmake_unique_basenameは呼び出されないような気がしますがどうでしょうか?(確認していません。すみません)
不要ですが、EntryをDBに保存する時点で必ず呼び出されます。使うかどうかは関係ありません。
どうもはじめまして。Movable Typeの使用にあたって、こちらのサイトには度々お世話になっておる者です。有用な情報をありがとうございます。
先ほどトラックバックさせてもらったのですが、文字化けしてしまいました。申し訳ございません…(^_^;
これまでの話とは視点が違いますが、個人的にかなり気になる問題点に思いますので送らせていただきました。