« 2007年05月 | メイン | 2007年07月 »
*設定方法等大きく変更しましたので、以下の別エントリーを立てました。ダウンロードもこちらから可能です。
一通りの実装とテストが終わったのでベータ版として公開します。
まだまだ荒削りな部分もあるかと思いますがお気づきの点等ありましたらご指摘いただけると幸いです。
追記:
Unix+Apache+MT3.xで動作します。Windows環境では動作検証は行っていません。
インデックスアーカイブ, カテゴリーアーカイブ, 月別(週別)アーカイブ等は静的生成、エントリーアーカイブは動的生成(ダイナミック・パブリッシング)とし、動的生成のページに対しては「最初にそのページにアクセスがあった時」に再構築(静的HTMLファイルを生成)を行います。ダイナミックパブリッシングによる再構築の負荷軽減と静的生成による閲覧時の負荷軽減の両方のメリットを享受できる方式です。
MySQL用のテーブル作成スクリプト(php)を付けました(by 当社のK君)。その他のDBの場合も以下の構造のテーブルを追加していただければ動作可能かと思います。
| permalink_id | int(11) |
|---|---|
| permalink_blog_id | int(11) |
| permalink_permalink | varchar(255) |
| permalink_modified_on | timestamp |
* ダウンロードは以下のエントリーのページから。
use lib qw (/path/to/mt/lib/);
use lib qw (/path/to/mt/extlib/);
my $mt = MT->new(Config => '/path/to/mt/mt-config.cgi');
my $base_url = 'http://example.com';
my $base_pth = '/path/to/htdocs';
1行目のPerlのパス及び4〜8行目を環境にあわせて修正。

ErrorDocument 404 /mtview.cgi
ErrorDocumentのパスをアップロードしたmtview.cgiのパスにあわせてください。
エントリーアーカイブを再構築してもエントリーアーカイブは基本的にダイナミックパブリッシングなので静的なファイルは生成されません。
エントリーアーカイブへのリクエストがあった場合、ページが存在しないので.htaccessで設定されたmtview.cgiが呼び出されます。mtview.cgiによってページが構築されデータをブラウザへリプライすると同時に静的HTMLをディスクへ書き出します(*)。
*但し、UserAgentにGooglebot,Yahoo! Slurp, msnbotのいずれかの文字列が含まれる場合はHTTP_IF_MODIFIED_SINCEとEntryのmodified_onを比較し、エントリーが更新されていなければNot Modifiedを返します(ページの生成は行いません)。
ファイルがディスクへ書き出された後のリクエストに対しては静的HTMLが使われるためmtview.cgiは呼び出されなくなります。
構築されたエントリーアーカイブは以下のタイミングで削除されます(その後再度ページへのアクセスがあった時に再生成されます)。

すべてのファイルを削除したい場合(つまり、全てのページを最新のものにしたい場合は、管理画面の各ブログトップ(mt.cgi?__mode=menu&blog_id=x)及びエントリー一覧の画面(mt.cgi?__mode=list_entries&blog_id=x)の「プラグイン」アクションの「エントリーキャッシュの全消去」をクリックします(すべてを再構築した直後に行うか、全てを削除した後に全再構築を行うと良いでしょう)。
MT4対応しました。MovableType4には対応していません。動作確認済みのバージョンはMovableType3.3のみですがバージョン3系であれば動作するのではないかと思います。
私が代表をつとめるアルファサード有限会社ではこんな風にMovableTypeに関する様々な課題解決が得意です。MTは使いやすいけれど「もっとこんな風にならないかなぁ...」「こんなことができるといいのに...」とお感じになられている企業や団体の皆様はお気軽にお問合せください。
カテゴリー: MovableType
動的生成・静的生成がちょっとした話題になっている? わけですが、拙作のプラグインBackground Rebuilderも某所でとりあげられてたりしてますね(一時アクセスが増えました)。
少しこのプラグインについて補足しておきます。
まず、ちょっと誤解を招く書き方をこれまでしていたかもしれませんが、このプラグインで全再構築を行った場合、全再構築をコマンドラインで行った時とほぼ同様の負荷がかかります。ですのでプロセスが一定時間を超えるとkillされる環境ではこのプラグインを入れてもおそらく解決はしないでしょう。
『「CMSの体感速度を上げる」ことで制作者のストレスを軽減する』のがこのプラグインのコンセプトだと考えてもらった方が良いでしょう。
むしろ全再構築よりも各エントリーの保存時の体感速度の速さが僕には重要です。静的・動的の話の際に書き忘れましたが、MTをBlogではなくサイト制作ツールとして使う場合(しかも運用ではなくオーサリングプラットフォームとして使う場合は)閲覧者はクライアントと制作会社だけです。ページの閲覧よりもCMSを操作している時間の方が長いわけです。
こういった場合は、制作する側がストレスがないのが一番です。動的生成でも良いのですが、「MTを使用して静的生成ベースでサイトを制作している際に制作者のストレスをなくし作業効率を上げること」がこのプラグインを作った一つ目の理由です。
以前以下のエントリーを書きましたが、このエントリーの最後で書きかけのまま放置していた事実? についてここで紹介しておきます。
(要は、MovableType Background Rebuilder Plugin に手を入れて、再構築の順番を変えたのだ。)
高速化のために<$MTInclude file=...による共通部分の外部ファイル化という方法があるわけですが、共通部分をインデックス・アーカイブとしている場合に一つ問題が起こります。

新規のエントリーを保存時する瞬間は、外部の共通ファイルは古いままです。例えばこのブログの「最近のエントリー」等が反映されないのです。全再構築を行う際も「エントリー」が先に再構築され「インデックス」が最後に再構築されますから、右側の共通部分は普通に再構築を行う限りは常に1バージョン古いものになってしまいます(インデックスが先に公開されると時間差の関係で一瞬リンク切れ状態になりますのでCMSの振る舞いとしては正しいのだと思いますが)。
Background Rebuilderプラグインでは、エントリーの保存時、あるいは全再構築時に「インデックス・アーカイブ」を先に再構築するようにしています。こうすることで常に最新のファイルがインクルードされるようになります。
インクルードファイル化したことで「こっちを先に再構築しなきゃ」といったことを意識する必要がなくなります。
これが僕がBackground Rebuilderを作った「もう一つ」の理由です。
こちら↓のエントリーで書かれていることは、つまりはこういうことではない? ...のかな?
試しにやってみようとソースコードを見てみましたが、まだ良くわかっていません。テンプレートを外部ファイル化して、外でスクリプトを使うか人手でのテンプレート修正と組み合わせでやるほうが楽そうです。使い勝手的にはプラグインに出来ればベストかもしれません。
カテゴリー: MovableType
追記(2007年7月2日):
一連のエントリーで作成したものを取りまとめて公開しました。
驚くのはまだ早い!(なんて言ったりして)
そして、今回頂いた反応の中で、一番驚いたのがこれ。
* ↑トラックバックがHTTP error: 403 Throttledで弾かれる...
ここまで来たら...やっちゃえ!
まぁ10分で出来るのはこれまでに書きためたプラグインの各種パターンがあるからですが、テストやドキュメント、設定のUIとかそのあたりは手抜きで。
今回の件でいえば「再構築」の代わりが「全削除」です。全削除しておくとあとは各ページが最初に閲覧された段階でそれぞれ再構築されます。リクエストのないページに対する無駄な再構築もありません。
my $plugin = new MT::Plugin::RebuildAt1stView({
name => 'RebuildAt1stView',
app_methods => {
'MT::App::CMS' => {
'remove_all_entry_archive' => ¥&_remove_all_entry_archive
},
},
});
MT->add_plugin($plugin);
MT->add_plugin_action('blog',
'../'.MT->config('AdminScript').'?__mode=remove_all_entry_archive',
'Remove All Entry Archives'
);
MT->add_plugin_action('list_entries',
'../'.MT->config('AdminScript').'?__mode=remove_all_entry_archive',
'Remove All Entry Archives'
);

全削除の場合はプログラム書くだけじゃなくて管理画面へのインターフェイスが必要です。add_plugin_action を使うと簡単です。
mt.cgi?__mode=remove_all_entry_archive;from=blog_home;blog_id=3
上記のように呼び出して動作させます。app_methods についてはこちらのエントリーに少し書いていますのでそちらも参考にしてください。
全削除は以下の通り。たかだか250程度のエントリーですが、感覚的には1秒以内で全削除できます(本当はちゃんとHTML組み立てて結果を返した方が良いですしエラーも拾った方がいいですけど)。
*ちょっと修正。is_superuserでスーパーユーザーかどうかチェックするようにした。
sub _remove_all_entry_archive {
my $app = shift;
my $user = $app->user;
if ($user->is_superuser) {
my $blog_id = $app->param('blog_id');
my $iter = MT::Entry->load_iter({
blog_id => $blog_id},{
sort => 'modified_on',
direction => 'descend',
});
if (defined $iter) {
while (my $entry = $iter->()) {
$app->publisher->remove_entry_archive_file(Entry => $entry, ArchiveType => 'Individual');
}
return 'エントリーアーカイブを削除しました!';
} else {
return 'エントリーが見つかりません。';
}
} else {
return '権限がありません。';
}
}
あとは各エントリーの保存や削除時に前々回のエントリーで作成した mt_permalink テーブルの更新が必要ですが、CMSPostSave.entry, CMSPostDelete_entryコールバックが呼ばれたタイミングで処理させます(MT4ではコールバック名が変更になっているかもしれませんがおそらく同等のコールバックは用意されていると思います)。
MT->add_callback('CMSPostSave.entry', 10, $plugin, ¥&_post_save_entry);
MT->add_callback('CMSPostDelete_entry', 10, $plugin, ¥&_delete_entry);
sub _post_save_entry {
my ($eh, $app, $entry, $original) = @_;
my $permalink = MT::Permalink->load({id => $entry->id});
if ($entry->status != 2) {
$app->publisher->remove_entry_archive_file(Entry => $entry, ArchiveType => 'Individual');
if (defined $permalink) {
$permalink->remove or die "Removing permalink failed: ", $permalink->errstr;
}
} else {
if (defined $permalink) {
$permalink->permalink($entry->permalink);
$permalink->modified_on($entry->modified_on);
$permalink->save or die "Saving permalink failed: ", $permalink->errstr;
} else {
$permalink = MT::Permalink->new;
$permalink->id($entry->id);
$permalink->blog_id($entry->blog_id);
$permalink->permalink($entry->permalink);
$permalink->modified_on($entry->modified_on);
$permalink->save or die "Saving permalink failed: ", $permalink->errstr;
}
}
}
sub _delete_entry {
my ($eh, $app, $entry) = @_;
my $permalink = MT::Permalink->load({permalink => $entry->permalink});
if (defined $permalink) {
$permalink->remove or die "Removing permalink failed: ", $permalink->errstr;
}
$app->publisher->remove_entry_archive_file(Entry => $entry, ArchiveType => 'Individual');
}
ということで、ソースはこちらに晒しておきます。一旦削除します。後ほどアーカイブとして最新のものをアップします。ライセンスはGPLです! (しつこい?)
っていうか誰か完成させてください。以下残りタスクを挙げておきます。
カテゴリー: MovableType, プログラミング
良く考えてみたら。
「コスト」をそのまま「コスト」と解釈すれば、
例えば
と言い換えることもできるかもしれない。
CMSの場合は「制作者」もユーザーだから「再構築が重い」とかいう議論になるわけだが、そもそも何のためのウェブサイトであり何のためのブログなの? 「サイトに来てくれる人のためにどのようなホスピタリティ・アクセシビリティを提供するか」が基本だよな。
だから、例えばコストを2万円かけてサーバーのメモリを増やす、っていう選択肢もありだし、レンタルサーバーのプランをワンランク上げて快適にするという選択肢もある。
すべてはサイトを訪れてくれるあなたのために、という姿勢で考えるべきじゃないんだろうか、などと考えてみたり。
追記(2007年6月29日):
一連のエントリーで作成したものを取りまとめて公開しました。
何でこんなことやってんだろ(笑)。
まぁこういう負荷軽減のためのあれこれって結構楽しいわけです。動けばえーやん、から一歩進むための色んなコツみたいなものがあって、昔非力なMac、速度の出ないスクリプト言語(AppleScriptとかREALbasicとか)で工夫しながらやっていたことがこんなところで使えるとは...
今回はMovableTypeというよりHTTPヘッダを使ったクライアントキャッシュのコントロールが本題。ステータス404をちゃんと返さないと弾さんに怒られちゃうし。
前々回, 前回のはそのあたり全く考慮していないので、以下きちんと実装する。
*ETagは使わない。参考:14 rules for fast web pages (Skrentablog)
今回は理屈の説明はなし! (説明書くと15分かかるので10分間Cookingにならないし!)
チェックしているUserAgentはとりあえずGooglebotのみ。
touch しているのは、この方法だとファイルのタイムスタンプは常に modified_on よりも新しいものになってしまうので2回目のアクセスの時にHTTP_IF_MODIFIED_SINCEと時間があわなくなってしまうのでその対策。
#!/usr/bin/perl -w
use strict;
use lib qw (/path/to/mt/lib/);
use lib qw (/path/to/mt/extlib/);
use MT;
use MT::Blog;
use MT::Entry;
use MT::Builder;
use MT::Template;
use MT::TemplateMap;
use MT::Template::Context;
use MT::Permalink;
use HTTP::Date;
my $mt = MT->new(Config => '/path/to/mt/mt-config.cgi');
my $base_url = 'http://junnama.alfasado.net';
my $base_pth = '/path/to/htdocs';
my $blog_id = 3;
my $request = $ENV{"REDIRECT_URL"};
my $url = $base_url.$request;
my $permalink = MT::Permalink->load({permalink => $url});
if (defined $permalink) {
my $modified_on = $permalink->modified_on;
my $yymmdd = substr($modified_on, 0, 8);
my $hhmmss = substr($modified_on, 8, 8);
my $yymmddhhmm = substr($modified_on, 0, 12);
my $ss = substr($modified_on, 12, 2);
$modified_on = $yymmdd.'T'.$hhmmss.'Z';
my $touch = 'touch -t '.$yymmddhhmm.'.'.$ss.' ';
$modified_on = &str2time($modified_on);
my $http_if_mod = $ENV{'HTTP_IF_MODIFIED_SINCE'};
$http_if_mod = &str2time($http_if_mod);
my $ua = $ENV{'HTTP_USER_AGENT'};
if ($ua =~ /Googlebot/i) {
if ($http_if_mod && ($http_if_mod >= $modified_on)) {
print "Status: 304 Not Modified¥n";
print "Last-Modified: $modified_on¥n¥n";
exit;
}
}
my $entry = MT::Entry->load({id => $permalink->id});
if (defined $entry) {
my $tmap = MT::TemplateMap->load(
{ blog_id => $blog_id,
archive_type => 'Individual',
is_preferred => 1
},);
my $template = MT::Template->load({id => $tmap->template_id});
my $page_tmpl = $template->text;
my $blog = MT::Blog->load({ id => $blog_id });
my $ctx = MT::Template::Context->new;
$ctx->stash('entry', $entry);
$ctx->stash('blog', $blog);
$ctx->stash('blog_id', $blog_id);
my $build = MT::Builder->new;
my $tokens = $build->compile($ctx, $page_tmpl);
my $html = $build->build($ctx, $tokens);
if ($html) {
$modified_on = &time2str($modified_on);
my $len = length($html);
print "Status: 200 OK¥n";
print "Last-Modified: $modified_on¥n";
print "Content-Length: $len¥n";
print "Content-Type: text/html¥n¥n";
print $html;
my $buildfile = $base_pth.$request;
$touch .= $buildfile;
open (OUT,">$buildfile");
print OUT $html;
close(OUT);
my $res = system($touch);
exit;
}
}
}
print "Status: 404 Not Found¥n";
print "content-type: text/html¥n¥n";
print "404 Not Found.";
さらにさらに続きはこちら↓。
カテゴリー: MovableType, プログラミング
追記(2007年6月29日):
一連のエントリーで作成したものを取りまとめて公開しました。
もうちょっとだけやります。えーっと必要な方は是非完成させてください(で、完成させたら僕にください!)。前回のエントリーの続き。
さらに続きまで書いてしまった...
先のエントリーで何だか面倒だったのがbasenameからentryを検索するところ。File:: Basenameを使うともう少し楽か...。ただbasenameは一意な値じゃないからやっぱり検索は非効率です。
そこで、データベースを拡張することにします(実は本題はここだったりする)。
データベースにmt_permalinkというテーブルを追加し MT::Objectのサブクラスを作成して利用します。
| permalink_id | int(11) | entry_idと対応したユニークな数字 |
|---|---|---|
| permalink_blog_id | int(11) | ブログID |
| permalink_permalink | varchar(255) | URL(Permalink) |
| (追加) permalink_modified_on | timestamp | 更新日時 |
データベースはMySQL。もちろんPostgreSQLでもSQLiteでも良いです(そこがMTの特長)。
CREATE TABLE `mt_permalink` (
`permalink_id` int(11) NOT NULL,
`permalink_blog_id` int(11) NOT NULL,
`permalink_permalink` varchar(255) collate utf8_unicode_ci NOT NULL,
`permalink_modified_on` timestamp NULL default CURRENT_TIMESTAMP,
UNIQUE KEY `permalink_id` (`permalink_id`),
KEY `permalink_blog_id` (`permalink_blog_id`),
KEY `permalink_modified_on` (`permalink_modified_on`),
KEY `permalink_permalink` (`permalink_permalink`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
続いて MT::Objectのサブクラス「MT::Permalink」を作成。
Permalink.pm として mtディレクトリの lib/MT/ 以下に置きます。(mt/lib/MT/Permalink.pm)
package MT::Permalink;
use strict;
@MT::Permalink::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
column_defs => {
'id' => 'integer not null',
'blog_id' => 'integer not null',
'permalink' => 'string(255)',
'modified_on' => 'integer',
},
indexes => {
blog_id => 1,
permalink => 1,
modified_on => 1,
},
child_of => 'MT::Blog',
datasource => 'permalink',
primary_key => 'id',
});
1;
これで下準備OK。エントリーのpermalink, id, blog_id をデータベースに登録するスクリプトを書いてシェルから実行。
#!/usr/bin/perl -w
use strict;
use lib qw (/path/to/mt/lib/);
use lib qw (/path/to/mt/extlib/);
use MT;
use MT::Entry;
use MT::Permalink;
my $mt = MT->new(Config => '/path/to/mt/mt-config.cgi');
my $blog_id = 3;
my $iter = MT::Entry->load_iter({
blog_id => $blog_id,
status => 2 },{
sort => 'modified_on',
direction => 'descend',
});
while (my $entry = $iter->()) {
my $permalink = MT::Permalink->new;
$permalink->id($entry->id);
$permalink->blog_id($entry->blog_id);
$permalink->permalink($entry->permalink);
$permalink->modified_on($entry->modified_on);
$permalink->save;
}
phpMyAdminで確認する。

ちゃんと入っているな。
これで, 前回のmtview.cgiの前半部分がすっきりして無駄な検索処理もなくなる。
my $request = $ENV{"REDIRECT_URL"};
my $url = $base_url.$request;
my $blog = MT::Blog->load({ id => $blog_id });
my $permalink = MT::Permalink->load({permalink => $url});
if (defined $permalink) {
my $entry = MT::Entry->load({id => $permalink->id});
if (defined $entry) {
...
新しいテーブルを作るのは面倒...な場合はサイトの運用にあわせて使っていないフィールド(entry_keywordsとか)に対してINDEX指定しておいてpermalinkを放り込んでおくという手もあるか。まぁ本来の? 使い方ではない方法で拡張するのは気持ち悪いし、MT::Objectのサブクラスの作成, 利用方法を理解していると何かと便利。SQL文を書かなくて良い(データベースの種類にかかわらず同じ書き方ができる)ところがMTのGoodなところ。
今回は本当に10分間クッキング。でもやっぱりエントリー書くのに15分かかった...
MT::Objectについては以下のページを参照くださいませ。
post_save_entry, post_delete_entry(だったかな?)コールバックが呼ばれた時にそれぞれ「更新」「削除」すれば良いでしょう。
さらに続きはこちら
カテゴリー: MovableType, プログラミング
追記(2007年6月29日):
一連のエントリーで作成したものを取りまとめて公開しました。
もうちょっとだけ掘り下げてみたいと思います。というかあれこれ考えているよりTryしてみる方が早いし建設的かな。WordPressは触ったことがないのですがMTならこんな解決法もあるよ、ということで。
再構築はインデックスやRSS, カテゴリー・アーカイブ等に限定し、エントリー・アーカイブはダイナミック生成とする。但し最初のリクエストがあった時にスタティックなファイルを生成してしまう(DBにキャッシュを保存するのではなく、そのhtmlを生成してしまう)。
※まだクライアントキャッシュのところ出来ていないのでGoogleさんに来られると嫌なので rel="nofollow" 付きリンクですが, テスト用サイトを用意してみた。そのうち削除するかも。
まず現状のサイト(エントリー数250程度)をMTからエクスポート、新規ブログを作成してインポート。テンプレートは適当。
#!/usr/bin/perl -w
use strict;
use lib qw (/path/to/mt/lib/);
use lib qw (/path/to/mt/extlib/);
use MT;
use MT::Blog;
use MT::Entry;
use MT::Builder;
use MT::Template;
use MT::TemplateMap;
use MT::Template::Context;
my $mt = MT->new(Config => '/path/to/mt/mt-config.cgi');
my $base_url = 'http://junnama.alfasado.net';
my $base_pth = '/path/to/htdocs';
my $blog_id = 3;
my $request = $ENV{"REDIRECT_URL"};
# /online/2007/06/mt4movabletypebookmarklet.html
my $permalink = $base_url.$request;
my @pathes = split(/¥//,$permalink);
my $counter = @pathes;
my $lastpth = $pathes[$counter-1];
my @basenames = split(/¥./,$lastpth);
my $base_len = @basenames;
my $basename = '';
my $lastnum = $base_len-1;
for (my $i = 0; $i < $lastnum; $i++) {
$basename .= $basenames[$i];
if ($lastnum-1 != $i) {
$basename .= '.';
}
}
# basename => mt4movabletypebookmarklet
my $blog = MT::Blog->load({ id => $blog_id });
my $iter = MT::Entry->load_iter({
blog_id => $blog_id,
basename => $basename},{
sort => 'modified_on',
direction => 'descend',
});
if (! defined $iter) {
$iter = MT::Entry->load_iter({
blog_id => $blog_id},{
sort => 'modified_on',
direction => 'descend',
});
}
my $build_entry;
while (my $entry = $iter->()) {
if ($entry->permalink eq $permalink){
$build_entry = $entry;
last;
}
}
# read template
if (defined $build_entry) {
my $tmap = MT::TemplateMap->load(
{ blog_id => $blog_id,
archive_type => 'Individual',
is_preferred => 1
},);
my $template = MT::Template->load({id => $tmap->template_id});
my $preview_tmpl = $template->text;
my $ctx = MT::Template::Context->new;
$ctx->stash('entry', $build_entry);
$ctx->stash('blog', $blog);
$ctx->stash('blog_id', $blog_id);
# build entry
my $build = MT::Builder->new;
my $tokens = $build->compile($ctx, $preview_tmpl);
my $html = $build->build($ctx, $tokens);
print "content-type: text/html¥n¥n";
if ($html) {
# replay and write static file
print $html;
my $buildfile = $base_pth.$request;
open (OUT,">$buildfile");
print OUT $html;
close(OUT);
}
} else {
print "content-type: text/html¥n¥n";
print "404 Not Found.";
}
ErrorDocument 404 /dynamic/mtview.cgi
10分じゃ...終わらなかったけど、まぁ30分コース。
再構築時にはインデックス、カテゴリーアーカイブ等は再構築され、エントリー・アーカイブの再構築は行われない。
最初にリクエストがあったときに「ファイルが存在しなければ」NotFound、つまりmtview.cgiにリクエストが渡されるので、ページを生成してクライアントに返す(同時にファイルを生成する)。
アクセスがあるかどうかわからない古い奥の階層のページまで再構築する必要がないし、データベースへのアクセスは最初の1リクエストのみ、以後は生成されたスタティックなページが使われるのでCGIの起動もデータベースへのアクセスもない。
もちろんこれで完成ではない。「再構築」の代わりに「全消去」のプログラムが必要になる。ただMTのプラグインとか書いたことのある方ならこの処理は簡単なことがわかるだろう。
全消去が出来たらとりあえず運用は可能だけど、「ページをビルドするコストは最初に見た人に支払ってもらう」どころじゃなくてこのままじゃGoogleが全再構築してしまうので、あといくつかの工夫とチューニングが必要。
絶対にやっておきたいのはクライアント・キャッシュを有効に使う方法。
更新されていないページへGoogleが2回目以降のアクセスにやってきたらステータス304を適切に返すこと。
あとは、Permalinkからエントリーを特定するのが非効率的なので別途Permalinkとentry_idを紐付けるようなデータベーステーブルを作っておけば処理も速くなるでしょう。
その他にも再構築の範囲や全消去する時にどこまでを対象とするかを検討したり(このあたりはまさにサイトの内容とポリシーによって最適解を検討していけば良いと思う)。
WordPressだMTだってのは「ダイナミックだスタティックだ」ってな議論とは別の部分で行うべき。逆にWordPressでもこのような考え方をすればスタティックなファイル生成も割と簡単に実現できるんじゃないだろうか。
以上、MovableType30分Cookingのコーナーでした(エントリー書くのに15分かかった...)
続きあります。
カテゴリー: MovableType, プログラミング
以前のバージョンからブックマークレットの記述方法が一部変更になります。お手数をおかけしますが再度以下の手順に従って設定お願いします。
※ http://example.com/mt/mt.cgi の部分を環境にあわせて書き換え。
<a href="javascript:window.document.location.href='http://example.com/mt/mt.cgi?quickedit=1&permalink='+document.location.href;">=>Quick Edit</a>
編集したいエントリーをブラウザで表示している状態で、ブックマークレットをクリックすると該当エントリーの編集画面にジャンプします。
エントリー以外(例えばトップページ等)の場合は単に管理画面のトップへリダイレクトされます。
パブリック・ドメイン
新たな実行モード(例: mt.cgi?__mode=foo)を追加する方法について。
3.3までは動作していた以下の書き方($app->add_methods(%arg))はできなくなったようだ。
sub init_app {
my $plugin = shift;
my ($app) = @_;
return unless $app->isa('MT::App::CMS');
$app->add_methods(quickedit => ¥&_quickedit);
}
$app->add_methods(%arg)
アプリケーション・クラスに、利用可能な実行モードのリストと、それぞれのモードのコード・リファレンスを提供します。%argは、メソッド名とそれに対応するコード・リファレンスのハッシュでなければなりません。たとえば次のように呼び出します。
$app->add_methods( 'one' => ¥&one, 'two' => ¥&two, 'three' => ¥&three, );
4.0では以下のように書く(というかこれまでも(3.3で確認)既にこの書き方はできた)。
my $plugin = new MT::Plugin::Quickedit({
name => 'Quickedit',
version => $VERSION,
app_methods => {
'MT::App::CMS' => {
'quickedit' => ¥&_quickedit
},
},
description => 'Quick Jump to Entry Editor.',
author_name => 'Junnama Noda',
author_link => 'http://junnama.alfasado.net/online/',
});
カテゴリー: MovableType
実のところ、僕がMovableTypeを初めて触ったのは昨年の夏以降のことです。まだ一年経っていません。プラグイン書いたりあれこれ学び出したのは秋以降です。"自分の中では"「埃を被っていたPerl」を過去の記憶から引っ張り出してきて、今や「このツールでウチのクライアントの要求は100%解決できる」自信があります。
きっかけは、去年の春に遡ります。
「CMSを導入したい。予算はあんまりない(←ありがち?)」
ここで、ウチのスタッフにオープンソースのCMSを中心に商用製品も含めて調査せよ、要件にあう物をピックアップせよと指示を出しました。
ここで選択肢に上がったのが Drupal, Nucleus。Drupalは実際にある案件で導入もしました。
この時口を酸っぱくして言ったのは「オープンソースを使うのならば、そのオープンソースコミュニティに自分がコミットしていくくらいでなければ駄目だ。クライアントが望むことがそのCMSで実現できなければ "お前が解決する" そのくらいの意識でないと駄目なんだ。」ということです。
オープンソースでなく商用製品の場合も「選択したお前が責任を持つこと(もちろん最終的には僕が責任を持つ)」を求めました。
結局、様々な条件を検討した結果(主にスタティックなファイル生成の必要が必須であった理由から) MTを選択することになりました(MODxやTypo3も選択肢にあがりましたが(注1)。
さて、そこからです。
クライアントの要望、担当者のやりとりについて報告を受けているときに「それはできません」が結構多いことが気になりました。例をあげると「ページの表示順をコントロールできない」ことや「"ゴミ"ファイルができてしまう」ことなどです。
特に、「PDFファイルをアップロードして「新着情報」に載せる時、新着情報に掲載するためにダミーのエントリーを生成して追記(more)欄にPDFのURLを入力する」みたいなトリッキーな方法(文章だとわかりにくいかもしれませんがMT使っている制作会社系の人はわかると思います)で設計するものですから、PDFひとつにつき1つのゴミ(ダミー)HTMLファイルが出来る。
表示順の制御は概要(excerpt)欄に番号を入れる。入れ替えることを考慮して番号は「001-0010」「001-0020」...(こうしておくと順番の入れ替えが可能だから)。
「ありえんやろ!」
これは上司である僕の見解です。担当者にしたら「MTじゃ出来ないんだよ。無茶いいやがって気楽な身分だよなあんたは。」という気分だったことでしょう。
もちろんコストの問題や納期の問題はあるにしても、クライアントが「できるんじゃない?」といったこと(しかもすごく合理的で良くあるであろう要求)が出来ないと簡単に言うことが僕には苦痛です。クライアントから期待されているプロなのだから。
MTについて自分で色々やりはじめたのはそこからです(結局その時点の彼(担当者)はプロではなかったし、その責任はもちろん僕にあります)。
カスタマイズや拡張できる方法はないか? 結果、殆どのことは出来ることがすぐにわかりました。ライセンスの問題はあるにしてもPerlスクリプトなのですからソースを追っていけばどのように動いているかは理解できますし、APIのリファレンスも公開されています。検索すれば様々なノウハウも公開されていますから。
この時以来あれこれ制作してきましたが、そのあたりを再度整理し直したものがこのページで紹介しているものです(一部です)。
自分たちが選択したのだから自分たちはその製品のプロである必要がある。結局、僕の「プロ意識」っていうのはそういうところにあるのでしょう。もしMT3.3のセキュリティ脆弱性が発見された時にSixApartがサポートを終了していたら、僕はパッチを書いて送りつけるでしょう。それで自分の顧客が不利益を被るのであればそうします(注2)。
オープンソースであればパッチを送るもなにもなくさっさと直せば良いのです。そういう面で僕はMTが(製品とは別の何かであっても)GPL化されることに期待しています。
注1)
アウトソースも検討して実際にTypo3やMODxについて扱っている(とWebサイトで謳っている)何社かに声をかけました。でも結局僕の問いに明確に答える程ツールを熟知している人ではありませんでした。つまり、僕の定義する「プロ」ではなかったということになります。
注2)
MTのような「既にコードにアクセスできる」ものがオープンソース化される場合というのは例えばNetscapeのソースコードが公開されたっていうのとちょっとニュアンスが違います。MTの場合はソース自体はこれまでも誰でも見ることができました(よって、どこに問題があるかは第三者が見つけることが可能です。もちろん修正方法を見つけることも可能です)。あとは純粋にライセンスの問題です。
但し、コードをオープンソースにすることの価値においては、それがCだろうがPerlだろうが変わりはないと思います。
ちょっと補足。
Mobavle Typeを顧客に導入しているウェブ屋の大半が同様なんじゃないかな。SI業界だったらありえないだろうな。まだまだウェブ業界は未成熟ということなんだろうけど。
from: Movable Type 3のサポート期限での議論にみるウェブ屋としてのプロ意識というもの その2
iwaiさん流の表現なんだろうと思うわけですが、どうなんでしょう? 業界の人。
はい皆さん(誰?)「MovableTypeとの出会い, 僕のウェブ屋としてのプロ意識。」というお題でエントリーを書くこと。宿題ね(嘘)。
この話題はGPLとの件とは違うわけですが、以下のOgawaさんのご指摘にも繋がることなんだろと思っています。MT3→MT4の変化が劇的であればある程混乱する現場が出てくる。
あとはオープンソースってどうなるのでしょう。オープンソース版を商用利用するSOHO業者がGPLを理解できないせいで発生するであろう混乱が今から目に見えるようです。面白いので私の公開しているプラグインもGPLにしてやろうかな(笑)。
from: Movable Type 4ベータテスト - Ogawa::Memoranda
石川さんとは今度飲みに行って(笑)お話するとして(札幌かぁ...)、このあたりは(特にプラグインの話)はやっぱり脇が甘かった(僕ほどじゃないけど)かも...というか、プラグイン公開している人にすると, 例えばフリーで公開しているプラグインなんかにサポートとか新バージョン対応を求められても困る。かといって、これをシックス・アパート側に言うのも違うでしょう。
○実現したいページ生成・動作を、あれこれたくさんのプラグインを使って解決している ○「管理画面をより使いやすく」といったオーダーが結構ある
ゆえ、シックス・アパートに確認した、
Q3:サードパーティのプラグインは正常に使えるのですか?
from: Movable Typeが優れものであることとシックス・アパートへ要望があることは別な話
プラグインの作者へ不信感を表明するわけにもいかないでしょうから「プラグインの導入」についても「ライフサイクル」や「サポート」について考えておかないといけない。そういう点で選択したお前が責任を持つこと(もちろん最終的には僕が責任を持つ)
ということになるわけですね。
かといって、サポートが云々で何でもかんでもリスク避けて...ばっかり考えていても面白くない。それ以上にそのツールに惚れ込んだのならそこは一歩踏み込んで行くのが面白いのだし自分たちの成長、他社と比較した優位性につながっていくのだと思うのです。
だからウチの場合は「基本的にプラグインは社内開発。その他のサードパーティーのものを入れる場合はライセンスを要確認(いざとなったら改編可能なら自社責任で導入OK、そうでなければ導入はNG)」となります。
あとは、ちゃんと対話すること(粘り強く, 前向きにね)。MT4Betaについてはこれまでに3通フィードバックしましたがちゃんとすべてに明確な返事をいただいています。バグレポートだけじゃなくて、今後もプラグインに関する互換性の持たせ方とかデベロッパーへの情報提供とかを求めていくつもりです。またそれだけじゃなくて僕は僕なりに分かったことをこのBlogなりで今後も書いていこうと思います。
カテゴリー: MovableType, 駄文・雑文
(追記)一連のエントリーの成果品? が出来ました。
MT3/4両対応。エントリーアーカイブへの最初のアクセスがあった時点で再構築(静的ファイル生成)を行うMovableType用のプラグイン+αのプラグラム群です。(追記ここまで)
「静的生成だとリビルドに時間がかかりすぎる。記事数が増えるにつれ、どんどん時間がかかるようになって来た。このツールは重すぎて使えない。動的生成の CMS に乗り換えたら快適になった。動的生成最高。」
「動的生成だから軽い。だからWordPressだよね!」っていうのは「俺のBlogアクセス殆どねぇから」という発言とセットである場合は正しいわけです。
アクセスが多い場合、そうは言っていられません。再構築は不要ですが、逆に閲覧時に負荷がかかります。Yahoo! Newsなんかにとりあげられたら(僕のBlogではそんなことまずあり得ませんが)データベースが悲鳴を上げることは間違いありません。
サイトの特性やアクセス状況等の前提の議論もなく「再構築不要、動的生成マンセー!」って言っている人はバカです。
動的生成と静的生成どっちが良いってのは、状況とサイトの性質、ポリシーによるのです。
「1000ページあるBlogがあって、毎日1エントリー追加。1000ページのうち900ページは5日に1回しかアクセスされない。最新の100ページはそこそこアクセスされる」というケースで、毎日1000ページすべてを再構築するのは時間の無駄ですから。
(こういったケースでは、比較的アクセスの多い100ページは静的ページを再構築してその他のページを動的生成にすると幸せになれます。)
表示されりゃぁOK! であれば別の解決方法もあります。
その昔、JavaScriptが登場した時に(こんな話すると年齢がばれるわけですが)、「JavaScriptはクライアントマシンで実行されるからサーバーでCGIを起動させる負荷が生じない」という説明がなされていてそれは正しいってことが改めて証明されている昨今ですね。
『各ページ共通部分で更新の頻繁な「最近のエントリー」とかいっそのことJSONかXMLで構築してJavaScriptで引っ張ってくればいいのに』っていうのがもうひとつの解決策です(もっと単純にObject要素でインクルードしてしまう手もあります)。
「やっぱりHTMLとしてStrictなものであるべきだろうしそれってアクセシビリティ的にどうなのよ」みたいな考え方はGoogle AdSense(このblogもそう)やその他のブログパーツを貼りまくったことでクライアント側が「重いなぁ」って感じるサイトにおいては何の説得力も持ちません。
もちろん再構築の負荷や時間を短縮するための正攻法ってのも存在します。まずはやれることからやるべきでしょう。このBlogでは以下のような考え方で再構築時間を短縮しています。
一方で動的生成の場合はサーバー・クライアント双方でのキャッシュの使い方次第で負荷軽減が可能です。サーバー側のキャッシュについては後述しますが、クライアントマシンのキャッシュを上手に使うためにはHTTPヘッダの扱いを工夫します。
以前に少し触れましたが、「動けばオッケー」的なWebアプリではHTTPヘッダとか殆ど意識されないケースが多いようですが(自分が作る時もそういう傾向は無きにしもあらず、ですが本当に負荷やレスポンスを考えるのであれば真っ先に検討すべき課題です)、この部分(HTTPヘッダによるクライアントのキャッシュの利用)の工夫をするかどうかでレスポンスや負荷は大きく変わって来ます。
但し、この方法の有効度合いもサイトの性質やユーザーの性質によって変わって来ます。
リピーターが比較的多い場合には、このようにクライアント・キャッシュを有効に扱う効果が最大限発揮されます。一方で、「リピーターはあまりいない」のであればこの方法はあまり効果を発揮しません。
そう、つまりはサイト次第なのです。
「何を重視するか」によっても変わって来ます。
「とにかくページが見られること」が最優先であれば断然静的生成です。データベースが悲鳴を上げてもページは閲覧できますし、サーバーが死んでもファイルのバックアップをどこかにコピーすれば見ることができます。
一方で、動的に動く部分(BBSやフォーラム、Wikiとか主体で動的に動く部分)が死んだらサイトとして成立しないような場合、例えばコミュニティサイト等なんかであれば、ページが見えても「使えない」わけですから、負荷はおさえる工夫はするにしても動的生成で運用すべきです。というか動的生成でないと使えません。
最後に(ここが本論なのですが)、サーバー、クライアントのキャッシュを上手に使いつつ、最初の動的生成時に静的ファイルを生成してしまう、って言う方法が実は一番合理的じゃないかと思っています。つまり、はじめてそのページにアクセスしたユーザーが「再構築」の負荷を負担する、という方法です。この方法であれば「アクセスの無い古いコンテンツ」は再構築の必要もなく、コンテンツ制作者にも全体再構築の負担がなく、最初のアクセス以後は静的生成されたページを送り返すだけですから。
このあたりの思想? をCMS(多分MTになるんでしょうが、他にも選択肢はあるでしょう)に反映させたようなソリューションを作りたいと考えています。
追記:
キャッシュの実装方法にも色々あるけれど、昔MacOS9でWebサーバーを動かしていたとき(10年くらい前) は「すべてのコンテンツをメモリに入れてしまう」という方法をとっていたなぁ。かなり強引なやり方だけどそりゃぁ(当時としては)高速だった。
カテゴリー: 駄文・雑文
このエントリーの続き。
Google Newsとかも結局は他人のコンテンツを引っ張って来て機械的に処理してリンクを貼って「Media」を「Generate」しているわけだが、結局のところ(MashUpとかとも繋がると思うが) 他者のコンテンツを引っ張ってこようがどうしようがわかりやすい「付加価値」をユーザーに提供できればそれはスパムサイトにはならないと思うのだ。
また様々な権利的な絡みもあるだろうがGoogle Newsには直接的に広告は表示されていない。
スパムサイトが鬱陶しいのは読む価値がなく、検索した際にもノイズになってしまって邪魔だからである。
W3Cによると, 現在(2007年6月23日現在) 「WWW上には4秒に1つのスパムサイトが産まれており、15秒に1つのスパムサイトが閉鎖されている」という。 (嘘です、ゴメンナサイ!)
まぁ嘘というか根拠はないけれど、機械的に生成されるスパムコンテンツは本当に人間の書き手の作るコンテンツをはるかに上回る勢いで増殖しているように思う。検索エンジンの質が落ちたとはいわないが、ブログ検索ではどのサービスを使ってもノイズが多くなっている。もちろんスパムとノンスパムの境界ギリギリのコンテンツもあるだろう。
スパムとノンスパムの違いはそのコンテンツに価値を認める人がいるかどうか(広告主やサイト運営者を除いて)。
特にマッシュアップというか他のコンテンツを再利用して構成し直すタイプのコンテンツの場合、そこに「編集」や「表現」といった意図がなければ無価値であるしノイズでしかない。「価値の提供に対する収入を得る」ということから逃げ出している人を見ていると怒りを通り越して悲しくなってくる。
またそういった人を対象としたスパム生成マシーンを作っている似非プログラマの人たちに言いたい。別に他者のコンテンツを加工しても構わない。でもどうせやるなら価値を提供できるアルゴリズムを作って大いに儲けたまえ! あなたも明日のGoogleだ!
ということで、「編集」と「表現」の一例を示す!?
アルゴリズムは秘密手動。何かにツッコミを入れたブロガーの記事の中から最も印象深いセンテンスをピックアップして淡々と紹介する。さらにインパクトの強い語をさらに強調する、という感じで以下のようになる。
皆さんネタにしてすいません。申し訳ないのでトラックバック飛ばしません!
(ちょっとした編集アルゴリズムを加えることで価値!? を付ける例)
これはひどい。こういうことを書くからSEO屋さんは信用できない。
From: Googleの順位決定にAnalyticsのデータが使われていたなどという事実は判明していない - Ogawa::Memoranda
SEOmoz | Proof Google is Using Behavioral Data in Rankings ( Googleの順位決定にGoogle Analyticsのデータが使われていたことが判明! | Web担当者Forum (日本語版)) の内容に対する小川宏高さんのツッコミ。
わかってない。わかってない。わかってねえ!
Rubyクックブック @ 2007年04月 @ ratio - rational - irrational @ IDMのあるパラグラフに対する小飼弾さんのツッコミ。
ニュースを見てこんなに腹が立ったのは久しぶりです。
Winny特別調査員2(ネットエージェント)のリリースに対する吉澤さんのツッコミ。
ブログの世界はこれからどんどんつまらなくなる。
From: 深町秋生の新人日記 - ブログ衰退論
資本主義の大波がブログ界に押し寄せていること、ブログが広告化していることなどに対する深町秋生さんのツッコミ。
馬鹿は死ねと言いたい。
日経ビジネスオンラインの記事「ウィニーこそ史上最強の「ジャーナリスト」?」武田徹氏の記事に対する高木浩光さんのツッコミ。
さあ、GIVE ME A TRUTH あなたの真実は?
表題のサイトに対するWeb屋のネタ帳さんのツッコミ(エントリーの締めくくりの一文)。
それって結局「大衆ウケしないと意味ない」ってのとほぼ同義だろ。
ゲームプログラマとWebプログラマについて? 最速インターフェース研究会さんのツッコミ。
ね、ちょっと手を加えるだけでこんなに素敵になったでしょ!?(違)
というかスパムサイトは死ねといいたい。
カテゴリー: 駄文・雑文
えー、SOHO事業者でWeb屋で有料ではないけどCCライセンスとかでプラグイン公開しちゃってた人として、これはこれでちゃんと考えておかねばなるまい。
Movable Type 4ベータテスト - Ogawa::Memoranda
あとはオープンソースってどうなるのでしょう。オープンソース版を商用利用するSOHO業者がGPLを理解できないせいで発生するであろう混乱が今から目に見えるようです。面白いので私の公開しているプラグインもGPLにしてやろうかな(笑)。
MovableTypeのラインセンスがGPL化されることが意外と知られてない/理解されてないらしい - Web屋のネタ帳
システム屋からすると「ふーん、ようやくGPLになりますかそうですか」程度のことなのだが、Web屋(デザイン寄りの人)からするとGPLという言葉の意味自体からして???なのかもしれない。
hiromasa.zone :o) » WordPress と MovableType と GPL と
無料になってわーいっていう感じもありますが、ふと有料プラグインとか作っている人たちが微妙に困ったことになるような気がしてきました。
今日 (おそらく初めて) シックス・アパート社 (日本法人) から「オープンソース」についてのリリースがなされた。
重要なポイントは先のエントリーにも書いたが、以下の部分である。
Movable Type のオープンソース版は、これまでMovable Typeとは異なる領域への、新たな挑戦となります。まず、誤解のないように定義しますと、Movable Type 4 と Movable Type オープンソースは、別のプロダクトとなります。もちろん多くの部分は共有されることになるかと思いますが、それぞれのプロダクトは、異なる目標をもっています。
すんごく申し訳ない見解ですが「なかのひと」が「ブログ大好きな」個人ブロガーであるかどうかは(多分)あまり関係ない(SOHO Web屋的事業者にとっては)。多くのSOHO事業者にとってはライセンスの問題は結構大きいんじゃないかと思う。
(追記)↑良く考えてみたら大した問題じゃ