Mar 27, 2005

AllKeywords Plugin とMT-XSearchの連携

AllKeywords PluginはTagwire Pluginに開発が引き継がれました。下記のエントリーをご参照ください。

AllKeywords Pluginは、エントリーのキーワード部分をハンドリングするための機能を追加するだけでなく、MT-XSearchと連携して軽量なキーワードサーチを行うための機能も持っている。キーワードサーチはMovable Typeに付属しているmt-search.cgiを改造することもでも実現できるが、改造してしまうと他の用途に利用できないというデメリットもある。一方、MT-XSearch*1はさまざまな検索サービスを追加できるフレームワークで、どのような検索を実現するかはプラグインとして分離することができる。AllKeywords Pluginはその一例となっている。

このエントリーではAllKeywords PluginとMT-XSearchとの連携方法とその高速化手法について述べる。

*1 Tim Appnelの作。昨年のDevelopper's Contestの受賞作の一部で、Movable Typeに拡張性のある検索サービスを追加するためのフレームワーク。運悪くmovabletype.org : Developer's Contest Plugin Pack: 2004のページからplugin packへのリンクがなくなっており、Appnelも(パッチは公開しているが)公開していないので現在Webでは入手不能。公開してくれよとは言っているのだが...。そういうわけで残念ながらAllKeywords Pluginの連携機能の効果は限定的なものとなる。

2005-06-11追記: MT-XSearch自体はDownload mt-plus | Appnel Internet Solutionsから入手できる。

連携方法

すでにAllKeywords Pluginはインストール済みとする。

  1. MT-XSearchをインストールする。具体的にはプラグインパッケージに含まれるMT-XSearch0.3.zipmt-plus-1.01.zipをアンパックしてその中のmt-xsearch.cgi、plugins/mt-xsearch.pl、extlib/MT/XSearch.pmをそれぞれ適切なディレクトリにアップロードする。mt-xsearch.cgiはCGIとして実行できるように実行権限を与える必要がある。
  2. mt-xsearch.cgiはそのままでは日本語がうまくハンドリングできないので、以下のパッチを当てる。
    --- mt-xsearch.cgi.bak Fri Aug 27 12:06:24 2004
    +++ mt-xsearch.cgi Sat Jun 11 02:58:33 2005
    @@ -41,7 +41,8 @@
         $ctx->stash('CGI',$q);
         my $out = $tmpl->build($ctx)
             or die "Building search template failed: ".$tmpl->errstr;
    -    print $q->header.$out;
    +    my $charset = $mt->{cfg}->PublishCharset;
    +    print $q->header(-charset=>$charset).$out;
     };
     if ($@) {
         print "Content-Type: text/html\n\n";
    
  3. 2005-06-11追記: plugins/mt-xsearch.plはそのままではdivision by zeroエラーが起きるので、以下のパッチを当てる。
    --- plugins/mt-xsearch.pl.bak Sat May 14 06:01:19 2005
    +++ plugins/mt-xsearch.pl Sat Jun 11 00:31:39 2005
    @@ -63,7 +63,7 @@
         my $pages = $limit ? ($count-($count % $limit)) / $limit : 1;
         $pages += ($limit && $count % $limit) ? 1 : 0;
         my $offset = $xsearch->args->{offset} || 0;
    -    my $current = $offset / $limit + 1;
    +    my $current = $limit ? ($offset / $limit + 1) : 1;
         $ctx->stash('MT::XSearch::current_page',$current);
         $ctx->stash('MT::XSearch::pages',$pages);
         my $builder = $ctx->stash('builder');
    
  4. 「XSearch AllKeywords」という名前のテンプレートモジュールを作り、例えば以下のように記述する。
    <html>
    <body>
    <form method="get" action="<$MTCGIPath$>mt-xsearch.cgi">
    <input type="hidden" name="blog_id" value="<$MTBlogID$>" />
    <input type="hidden" name="search_key" value="AllKeywords" />
    <label for="search" accesskey="4">Search this site:</label>
    <input id="search" name="search" size="20" value="<$MTSearchString decode_url="1" encode_html="1"$>" />
    <input type="submit" value="Search" />
    </form>
     
    <MTSearchResults>
    <MTSearchHeader>
    Results found: <$MTSearchResultCount$> 
    <ol>
    </MTSearchHeader>
    <li><a href="<$MTEntryLink$>"><$MTEntryTitle$></a></li>
    <MTSearchFooter>
    </ol>
    <p>Searched on: <em><$MTSearchString decode_url="1"$></em></p>
    </MTSearchFooter>
    </MTSearchResults>
    <MTNoSearch><p>No search performed.</p></MTNoSearch>
    <MTNoSearchResults><p>Nothing found.</p></MTNoSearchResults>
     
    </body>
    </html>
    
    テンプレートモジュールは好きなようにカスタマイズできる。

使い方

インデックステンプレートやアーカイブテンプレートに以下のように追加すれば、キーワード検索用のフォームを作ることができる。

<form method="get" action="<$MTCGIPath$>mt-xsearch.cgi">
<input type="hidden" name="blog_id" value="<$MTBlogID$>" />
<input type="hidden" name="search_key" value="AllKeywords" />
<label for="search" accesskey="4">Search this site:</label>
<input id="search" name="search" size="20" value="<$MTSearchString decode_url="1" encode_html="1"$>" />
<input type="submit" value="Search" />
</form>

また、以下のようにフォームを生成せずに特定のキーワードの検索結果へのリンクを作ることもできる。

<$MTCGIPath$>mt-xsearch.cgi?blog_id=<$MTBlogID$>&search_key=AllKeywords&search=Blog

.htaccessに以下のように記述すれば、「http://blog.your.domain/tag/Keyword」のように簡便なURLで特定のキーワードの検索結果を得られる。

RewriteEngine on
RewriteRule ^tag/(.*)$ /mt/mt-xsearch.cgi?blog_id=1&search_key=AllKeywords&search=$1 [QSA,L]

以下は2005-06-11に追記したものです。

高速化の方法

上記で説明した方法ではリクエストのたびにCGIを起動し、検索結果を取得して表示しているため、動作が遅いことは否定できない。しかしタグには滅多に変更がないことを前提にするなら、一定期間検索結果をキャッシュしたり、データの転送を抑制したりすることで大幅な高速化が可能になる。

ここでは手っ取り早くCGI::Cacheを使って高速化を実現する方法を紹介する。具体的には、検索結果を一定時間(例では1時間)キャッシュし、なおかつHTTP HeaderのLast-Modifiedにキャッシュした時刻を格納して返す。つまり、以下の二つの効果が期待される。

  • キャッシュを作成してから一定時間内は再検索しないことによる負荷の軽減
  • この期間内に二回以上アクセスするブラウザには304 Not Modifiedを返すことによる、データ転送の削減

方法は単純で、CPANからCGI::Cacheモジュールをインストールし、「連携方法」の2.で示したパッチの代わりに以下のパッチをmt-xsearch.cgiに適用するだけでよい。赤字で示した部分はそれぞれ「キャッシュの作成場所」「キャッシュの生存期間」を表す。下記の例ではそれぞれ「mt-xsearch.cgiのあるディレクトリの直下のcacheディレクトリ」「1時間(=3600秒)」を指定してあるので必要に応じて変更するとよい。

--- mt-xsearch.cgi.bak Fri Aug 27 12:06:24 2004
+++ mt-xsearch.cgi Sat Jun 11 03:44:32 2005
@@ -17,6 +17,7 @@
 }
 
 use CGI;
+use CGI::Cache;
 use MT;
 use MT::ConfigMgr;
 use MT::Template;
@@ -27,10 +28,13 @@
     my $mt = MT->new( Config => $MT_DIR . 'mt.cfg', Directory => $MT_DIR )
         or die MT->errstr;
     my $q = new CGI;
+    CGI::Cache::setup({ cache_options => { cache_root => './cache', default_expires_in => 3600 } });
     my $blog_id = $q->param('blog_id') or
         die "Missing parameter blog_id";
     my $key = $q->param('search_key') or
         die "Missing parameter key";
+    CGI::Cache::set_key($q->Vars);
+    CGI::Cache::start() or exit;
     my $search = MT::XSearch->execute($q);
     my $tmpl = MT::Template->load( { 
                         name=>'XSearch '.$key, 
@@ -41,7 +45,14 @@
     $ctx->stash('CGI',$q);
     my $out = $tmpl->build($ctx)
         or die "Building search template failed: ".$tmpl->errstr;
-    print $q->header.$out;
+    my $charset = $mt->{cfg}->PublishCharset;
+    my @m = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+    my @w = qw(Sun Mon Tue Wed Thu Fri Sat);
+    my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime(time);
+    my $now = sprintf("%3s, %02d %3s %04d %02d:%02d:%02d GMT",
+        $w[$wday], $mday, $m[$mon], $year+1900, $hour, $min, $sec);
+    print $q->header(-charset=>$charset,-Last_Modified=>$now).$out;
+    CGI::Cache::stop();
 };
 if ($@) {
     print "Content-Type: text/html\n\n";

Mar 24, 2005

サーバーを移転した件について。

思うところあってLolipopを使い続けるのを断念して、VPS(Virtual Private Server)に移行しました。ある程度スキルのある人でないと管理者さんに徒に労力をかけるだけになってしまうでしょうから、ここではあえて移転先は書きませんが(と思ったらリンクされていたみたいなので書いておきます、VPS7です)、tracerouteしてみるととんでもないところにあるのが分かります。今のところ価格・性能・サポートともに非常に満足しています。何せ昨日夕方思い立って申し込んで、そして今ちゃんと動いてしまっているわけですから。

さて、VPSにしろFull-managed Hostingにしろ専用サーバーにしろ自宅サーバーにしろ、ネットワーク(遅延・バンド幅)、CPU、メモリーなどといった資源をどのようなバランスでどれくらいのコストを支払って貸借するか、という問題に収斂します。すべてを求めるなら自宅サーバーが現在対費用性能上ベストで、私の場合には住環境上その選択ができません。Lolipopなどはネットワーク遅延においてのみ優れたソリューションでそれ以外の要求を極小化することによってコスト低減を図っており、これが私のニーズにはミートしません。

一般的に言って、Webアプリケーションの高度化は資源への要求を増大させ続けているため、低価格のホスティングサービスはそうしたニーズに決して応えることはありません。代わりにディスク容量の大容量化などの本質的でないサービスに向かいつつある現状を考えると、これらのサービスが美味しかった(大多数のユーザーがCPU・メモリー資源をほとんど使わなかったため、たまたま使いたいユーザーにとっては資源に余裕があった)時期は過ぎてピークオフが近づいているのでしょう。要は既存のISPやポータルサイトの提供してきたホームページ作成サービスがあまりにpoorだったため、そのパイを奪うことができた、という現象が観測されただけなのかもしれません。ISPなどにとってこうした事業はそもそも不採算であってパイを奪われることによるデメリットもほとんどなかったでしょうし、そういうサービスに移行するだけのmobilityとliteracyを備えた層は他のサービスへの移行も早いことは十分に予想できます。

一方で専用サーバーはどうかというとコストが問題です。集約的なDCのフェアなネットワーク性能がFTTHに劣るという現状ではむしろDCの実現する付加価値がコストに見合うかどうかだけが問題です。信頼性は確かにあるのでしょうが、ウイルスチェックをすると月何万円増しだの、毎日バックアップを取ると月何万円増しだの、と言った割高な料金体系が個人やSOHOの財布を開かせることはありません。彼らにとって自宅サーバーの方が圧倒的に望ましい解なのです。

VPSはDCがもともと持っていた信頼性と、低コストを両立させる一つの解でしょう。正直VPS7の料金は驚きですが。個人的には、jailやUMLを用いたこうしたサービスの基盤となる技術のpoorさに不満を感じないではありません。もっとフェアにリソーススケジューリングすべきだし、ハイヴを簡単にバックアップしたり移動したりする容易さも必要です。管理者コストにしても単体サーバーを管理するだけでなく、そこで動作する複数のVPSハイヴを管理する必要があります。ただ、現状のオープンソースソフトウェアだけで十分にビジネスに足るサービスを提供できているのも事実で、この方向性に未来があるのだろうなというのが今のところの私の印象です。

少し未来を考えると、IntelのVanderpool Technologies(Intel® Virtualization Technology - Intel Corporation)に代表される仮想化技術が一般化すると予想されます。ハードウェアサポートによってVPSがより一般的になり、そして今のVPSより自由度も効率も高いVPSが登場することは想像に難くありません。今から心待ちにしています。

閑話休題。

それにしてもFreeBSD環境は本当に久し振りなのです。以前使っていた頃はportsとか全然信用ならないので、後から追加するソフトウェアは必ず自分の手でインストールしていました。そのことを思うと、今はpkg_add -rでほとんどすべて事足りてしまってかなり拍子抜けです。

さらに昔、大学の共用のフロッピードライブ付きSun 4を使って、最新のXやらgccのソースアーカイブをtarで固め、それを1.44MBフロッピーにddでぎりぎり書き込み可能なサイズに分割し、ddでフロッピーに書き込み、そいつを家に持って帰ってFreeBSDマシンを使ってddで読み出し、tarに戻してそれを展開、それをスワップさせまくりながらコンパイル、とか信じられない手間をかけていたのでした。あの埃とトナーに塗れた計算機室が少し懐かしいです。

昔話はさておき、いまだにDNSの更新が遅延しているところ(例えば私の自宅)もあるようです。したがって、FeedBurnerのフィードは更新されているように見えるのに私のブログにアクセスしてみると(依然Lolipopのサーバーが見えるため)更新されていない、という愉快な現象が観測されるはずです。この記事をWebブラウザで無事に目にされているのであればその環境には問題はないということです。

Mar 19, 2005

AllKeywords Plugin

AllKeywords PluginはTagwire Pluginに開発が引き継がれました。下記のエントリーをご参照ください。

エントリーのキーワードをイワユル「タグ」として利用するためのさまざまな機能を提供するプラグイン。

all-keywords.zip

0.10 (2005.03.19):
  • 公開開始。
0.11 (2005.03.20):
  • case_sensitiveオプションの追加。
0.12 (2005.03.26):
  • コードクリーニング。
  • MT-XSearchとの連携をサポート。MT-XSearchとの連携方法に関しては別エントリーで述べる予定。

Ogawa::Buzz: 四畳半フォークソノミーの実現に向けてのアイディアを実現すべく、Movable Typeのエントリーのキーワード部分を使ったさまざまな便利機能を提供する。

具体的にはこのプラグインはカテゴリーの場合と同様に、利用されているキーワードやキーワードの個数をリストアップする機能、指定したキーワードにマッチするエントリーをリストアップする機能、類似度が高い(現在のエントリーとマッチするキーワードの数が多い)エントリーをリストアップする機能などを柔軟に実現する。

ひとまず生半可なドキュメントができたので公開してみた。長いよ。

MTAllKeywordsコンテナタグ

ブログ内のすべてのキーワードをリストアップするコンテナタグ。

オプション:

sort_by="keyword|count"
リストアップする順序をキーワードのアルファベット順か出現頻度順かを選択する。デフォルトではkeyword。
sort_order="ascend|descend"
リストアップする順序を昇順か降順かを選択する。デフォルトでは昇順(ascend)。
lastn="N"
リストをN個まで表示する。デフォルトではすべて表示する(N=0)。
delimiter="区切り文字"
キーワードを切り出す際に使用するデリミタを指定する。「delimiter=","」とするとカンマで区切られた語を一個のキーワードとする。デフォルトでは空白文字(スペース、タブ)をデリミタとする。
case_sensitive="0|1"
キーワードの大文字・小文字を区別するかどうかを指定する。デフォルトでは区別する(case_sensitive=1)。

このコンテナタグの中で利用可能なタグ:

<$MTAllKeyword$>
一個のキーワードを表示する。
<$MTAllKeywordCount$>
一個のキーワードの出現回数を表示する。
<$MTAllKeywordsTotal$>
すべてのキーワードの数を表示する。MTAllKeywordsコンテナの直後でも利用可能。
<$MTAllKeywordsTotalSum$>
すべてのキーワードの数の総和を表示する。MTAllKeywordsコンテナの直後でも利用可能。

また、このコンテナの外部で利用可能なタグも利用できる。

使用例:

出現頻度の高い10個のキーワードをリストアップしてそれぞれをTechnorati Tagにリンクする。

<ul>
<MTAllKeywords sort_by="count" sort_order="descend" lastn="10">
<li><a href="http://www.technorati.com/tag/<$MTAllKeyword$>"
  rel="TAG" title="TAG:<$MTAllKeyword$>">
  <$MTAllKeyword$></a> (<$MTAllKeywordCount$>)</li>
</MTAllKeywords>
</ul>
<ul>
<li>Total Keywords: <$MTAllKeywordsTotal$></li>
<li>Total Keywords(Sum): <$MTAllKeywordsTotalSum$></li>
</ul>

MTEntryAllKeywordsコンテナタグ

エントリーコンテキスト(MTEntriesの内部、または個別アーカイブ)でMTAllKeywordなどを利用可能にするコンテナタグ。

オプション:

delimiter="区切り文字"
キーワードを切り出す際に使用するデリミタを指定する。「delimiter=","」とするとカンマで区切られた語を一個のキーワードとする。デフォルトでは空白文字(スペース、タブ)をデリミタとする。
case_sensitive="0|1"
キーワードの大文字・小文字を区別するかどうかを指定する。デフォルトでは区別する(case_sensitive=1)。

このコンテナタグの中で利用可能なタグ:

MTAllKeywordsと同様なので省略。

使用例:

各エントリーのキーワードをリストアップしてそれぞれをTechnorati Tagにリンクする。

<MTEntries lastn="10">
<h2><$MTEntryTitle$></h2>
 
<ul>
<MTAllKeywords sort_by="count" sort_order="descend" lastn="10">
<li><a href="http://www.technorati.com/tag/<$MTAllKeyword$>"
  rel="TAG" title="TAG:<$MTAllKeyword$>">
  <$MTAllKeyword$></a></li>
</MTAllKeywords>
</ul>
 
<$MTEntryBody$>
</MTEntries>

MTAllKeywordCountも利用できるが残念ながら(当然)1と表示されるだけである。

MTEntriesWithKeywordsコンテナタグ

指定したキーワードを含むエントリーをリストアップするコンテナタグ。

オプション:

keywords="keyword-list"
リストアップする対象となるキーワードを一個以上指定する。
delimiter="区切り文字"
keywordsオプションで指定したキーワードリストからキーワードを切り出す際に使用するデリミタを指定する。「delimiter=","」とするとカンマで区切られた語を一個のキーワードとする。デフォルトでは空白文字(スペース、タブ)をデリミタとする。
case_sensitive="0|1"
キーワードの大文字・小文字を区別するかどうかを指定する。デフォルトでは区別しない(case_sensitive=0)。
sort_by="title|status|created_on|modified_on|author_id|excerpt"
エントリーをリストアップする順序を選択する。デフォルトではエントリーの作成時刻の順(created_on)に従う。
sort_order="ascend|descend"
リストアップする順序を昇順か降順かを選択する。デフォルトでは降順(descend)。
lastn="N"
リストをN個まで表示する。デフォルトではすべて表示する(N=0)。

このコンテナタグの中で利用可能なタグ:

MTEntriesと同様に「MTEntry*」というタグが一通り利用できる。また、MTEntriesの外部で利用可なタグも同様に利用できる。

使用例:

"movable"と"type"というキーワードを含むエントリーをすべてリストアップし、タイトルや本文を表示する。

<MTEntriesWithKeywords keywords="movable type">
<h2><a href="<$MTEntryPermalink$>"><$MTEntryTitle$></a></h2>
 
<$MTEntryBody$>
<$MTEntryExtended$>
 
</MTEntriesWithKeywords>

平たく言うと通常のMTEntriesコンテナと同様に使用できる。

MTMostRelatedEntriesコンテナタグ

エントリーコンテキスト(MTEntriesの内部、または個別アーカイブ)で関連するキーワードを持つ他のエントリーをリストアップするコンテナタグ。関連度が高い(関連するキーワードの個数が多い)エントリーから順にリストアップする。

オプション:

delimiter="区切り文字"
キーワードを切り出す際に使用するデリミタを指定する。「delimiter=","」とするとカンマで区切られた語を一個のキーワードとする。デフォルトでは空白文字(スペース、タブ)をデリミタとする。
case_sensitive="0|1"
キーワードの大文字・小文字を区別するかどうかを指定する。デフォルトでは区別しない(case_sensitive=0)。
lastn="N"
リストをN個まで表示する。デフォルトではすべて表示する(N=0)。

このコンテナタグの中で利用可能なタグ:

MTEntriesと同様に「MTEntry*」というタグが一通り利用できる。また、MTEntriesの外部で利用可なタグも同様に利用できる。

使用例:

MTEntriesで最近の10件をリストアップし、そのそれぞれのエントリーについて関連するエントリーをリストアップする。

<MTEntries lastn="10">
<h2><a href="<$MTEntryPermalink$>"><$MTEntryTitle$></a></h2>
<$MTEntryBody$>
 
<ul>
<MTMostRelatedEntries>
<li><a href="<$MTEntryPermalink$>"><$MTEntryTitle$></a></li>
</MTMostRelatedEntries>
</ul>
 
</MTEntries>

使用上の注意

Movable Type、およびAllKeywords Pluginはキーワード用のインデックスを持たない。したがって、MTAllKeywords, MTEntriesWithKeywords, MTMostRelatedEntriesは、使い方によっては強烈に重い!!ということを自覚して使うこと。エントリー数が増えると生半可なレンタルサーバーでBerkeleyDBなど使っている場合には500 Internal Server Errorを食らうことになるので要注意である。これを改善するアイディアはいくつか思い付いているので今後改善していく予定である。

更新履歴

2005-03-20更新:

case_sensitiveオプションを追加した。MTAllKeywordsとMTEntryAllKeywordsでは区別し、MTEntriesWithKeywordsとMTMostRelatedEntriesでは区別しない、のがデフォルトと設定としてあるので要注意。どうしてこのような設定になっているかというと、前者はキーワードをリストアップし、後者はエントリーをリストアップするという性質の違いがあるためである。

キーワードをリストアップする場合には、大文字・小文字を区別しないとすると例えば「ipOD」と「iPod」を同一のものとみなす。したがってリストアップ時にどのような表現を採るべきか(ipOD? iPod? Ipod? IPOD? ipod?)を自動的に判定せざるを得ない。これは必ずしも望ましくない。

一方エントリーをリストアップする場合には、なるべく多くのエントリーとマッチすることが望ましい。大文字・小文字を区別することにすると、たまたま特定のエントリーのキーワードに「ipod」と登録した場合、他の「iPod」関係のエントリーとの関連性が発見できなくなる。

SEE ALSO

英語版はまだ書いていない。

LICENSE

This code is released under the Artistic License. The terms of the Artistic License are described at http://www.perl.com/language/misc/Artistic.html.

AUTHOR & COPYRIGHT

Copyright 2005, Hirotaka Ogawa (hirotaka.ogawa at gmail.com)

Mar 16, 2005

四畳半フォークソノミーの実現に向けて

一人フォークソノミー改め、四畳半フォークソノミー。一人で一つのブログやってるとは限らないからね。

カテゴリーは鬱陶しい。Movable Typeや他のブログツールを使っていて一番腹が立つのはカテゴリーを設定する、まさしくその瞬間だ。そのインタフェースの悪さもあるが、文章を書くという曲がりなりにも知的な作業と、無理矢理カテゴライズするという非生産的行為とのギャップがひどく煩わしい。

「カテゴリー」に系統的に分類可能な情報というのも確かにある。が、それには対象が限定されている必要がある。ある時は映画評を書き、ある時はプラグインを公開し、そしてまたある時にはレストランの情報を書く。当然トップレベルのカテゴリーは雑駁とした羅列となる。それは果たして意味がある分類なのか、自分にとってあるいは読者にとって。

問うまでもなく意味がある分類などではない。では何なのだ? (分類することで自身を鼓舞するとか)無理にエビデンスを見つけ出すことも不可能ではないが、しかし逆にカテゴリーがなかったとしたら誰か困るのだろうか? ブログへのリーチのほとんどがRSSリーダーやアンテナやサーチエンジンなどの何らかの、広義のアグリゲーションサービスに頼るこのご時勢に? あるいはブログに付属している検索機能では不足か?

そもそもローカルなカテゴリー付けは何のための行為かと言えば、見栄えのするカテゴリーアーカイブを生成するためではない。もしそうなら目的と手段を混同しているのである。そうではない。二人でやっているブログなら相棒は異なる人格とそれを構成する知識とを有する他者である。さらに言えば、去年・先月・昨日の自分は厳密に今日の自分でもない。つまりローカルにも複数の「人々」が存在するのであり、それらの「人々」の異なる経験や異なる感想、異なる知識を関連付け、分類するための行為なのである。

これこそが四畳半フォークソノミーである(と、とりあえず提唱する)。語の連関こそが語の意味であるように、四畳半フォークソノミーによって発見される知識の連関はエントリーの意味をより明確にし、新たな知識の獲得の一助となるだろう。

決めた。私はカテゴリーを捨てる。今思えばカテゴリーを持たないBloggerは慧眼だった。代わりにキーワードかラベル、あるいはそう呼びたければタグ付けを行う。幸いMovable Typeには各エントリーにキーワードを設定できる。これを使って今流行りのフォークソノミーよろしく自分ひとりで自分のためだけにタグ付けするのだ!

以下では四畳半フォークソノミーを支援する機能について考える。既存のカテゴリー分けと遜色のない入出力インタフェースを実現するために少なくとも以下の機能が必要であろうと思われる。

  • キーワードの入力をより簡便にするインタフェース

    Movable Typeを例にとればキーワードの入力はカテゴリーの設定に比べてしづらい。エントリーの編集画面においてキーワードの入力欄はデフォルトでは表示されず、「表示のカスタマイズ」を使って表示させたとしても本文の下の追記の下というごくアクセスの悪い場所になる。また、エントリーの一覧画面ではカテゴリーは表示されてもキーワードは表示されない。これを改善するのが一つの方法。また、はてなダイアリーのように[[キーワード]]と書くとキーワードに登録されるようにするというのも考慮に値する。

  • 既存のカテゴリー名をキーワードとして設定し直す機能

    カテゴリーに未練があるわけではないが、すでにある1000件近い情報を何のラベルもなしにハンドルするには無理がある。過去設定したカテゴリーの名前をキーワードとして設定し直すことで移行を促進する。また、RSS、Atomフィードでカテゴリーをdc:subjectやcategoryとして設定している部分をキーワードとカテゴリーの両方を設定するように変更するなどの移行措置も必要だろう。

  • スコープごとにキーワードをリスティングする機能

    カテゴリーの場合を見れば分かる通り、キーワードをリスティングしたり、キーワードの個数を数え上げることができると便利である。この他、類似度の高い(現在のエントリーとマッチするキーワードの数の多い)エントリーをリスティングする機能もあれば望ましい(c.f., Related Entries Plugin :: Adam Kalsey)。

  • キーワードをキーに、そのキーワードを持つエントリーを抽出する機能

    キーワードをベースにしたアーカイブというものは現実的でない。なぜなら、N個のキーワードがある場合、2^N(2のN乗)個のアーカイブを生成することになるからである。キーワードを一個追加するたびにインスタンスの数は2倍になる。キーワードをキーにエントリーを抽出するには検索インタフェース的なものの方がより望ましい。

引き続いて各機能の実装を粛々と進める予定である。いくつかはもうできているのだが、全部実現するとなると果たしてMovable Typeでやる理由があるのか、と…。

2003-03-16更新:
キーワードをキーにエントリーを抽出する機能と、キーワードをリスティングする機能を実装してみた。前者はエントリータイトルの直下のキーワードリストから確認できる。また、後者はトップページのサイドメニューのKeywordsのところで確認できる。一人でやってても結構楽しいじゃん。これらはそれぞれプラグインとして実現されているので別途エントリーを書く予定。

2003-03-18更新:
仕事が忙しくてアレなんだが、作っているプラグインが万艦飾っぽくなってきた。適当なところでリリースせねばなー。

Mar 15, 2005

THE MAKING

科学技術振興機構(JST)が制作しているサイエンスチャンネルに「THE MAKING」という番組がある。

私の自宅で無料で視聴できるiTSCOMチャンネルでもゲリラ的に放送されているのだが、チャンネルを合わせたときにやっているとついつい見入ってしまう。

ちょっと観てみ。マジですごいから。「原研がめざすもの」なんかどうでもいいから。

身の回りにある鉛筆とか、段ボールとか、ランドセルとか、原子力発電所とか、カニ風味かまぼことかが、原材料からどのように製造されているのか淡々と14分間説明していくだけ(映像と作動音・作業音と字幕だけ)のシンプルな番組が百何十本も!いつだったか「手延べ素麺のできるまで」の回を観ていたらとても食べ物とは思えない製造過程にげっそり、じゃなくて感動を覚えた。ものすごい技術だよ、どこまで機械化するんだよ、という感じ。やっぱり科学技術をおざなりにしてはいかんね、ものつくり大事だね、と思わせてくれる好番組。

下のリンクから一部を除いて視聴できるようだ。

番組(シリーズ)紹介 (THE MAKING)
番組(シリーズ)紹介 (THE MAKINGスペシャル版)

Mar 12, 2005

Accesskeyを修飾する JavaScript

Accesskeyを修飾するためのJavaScriptを作りました。JavaScriptで試しにOOPしてみたかっただけです。

accesskey.js

<label accesskey="k">Access Key</label>

のようなLABEL要素があるときに

<label accesskey="k">Access <em>K</em>ey</label>

のように書き換えます。使い方はHTMLファイルの末尾の方(</body>よりは前)に以下のように追加するだけです。

<script type="text/javascript" src=".../accesskey.js"></script>

仕様

大まかな振る舞いは上に書いた通りですが、正確には以下のような振る舞いをします。

  1. LABEL要素の子要素でaccesskeyの大文字とマッチするものがあれば、その最初のものを修飾します。
  2. もしなければ、LABEL要素の子要素でaccesskeyの小文字とマッチするものがあれば、その最初のものを修飾します。
  3. それもなければ、子要素に「 (K)」(Kはaccesskey)という文字列を付加し、その「K」を修飾します。

カスタマイズ

スクリプトの末尾の部分を以下のように書き換えると、<em>...</em>の代わりに、<strong>...</strong>や<span class="accesskey">...</span>で修飾することができます。

modifyAccessKey();
modifyAccessKey('strong');
modifyAccessKey('span', 'accesskey');

また、LABEL要素以外を修飾の対象としたい場合には、modifyAccessKeyという関数の定義を変更することで対応できます。特にA要素のaccesskeyを明示したい向きはあるでしょう。そのうち修正例を示すかもしれません。

Mar 10, 2005

SQLiteに移行した件について。

SixApart Professional NetworkのMLをチェックされている方はご存知だと思いますが、先月末あたりからTimothy Appnelのエントリーをきっかけに(私自身も含めて)SQLiteネタで盛り上がっていました。それが理由というわけではありませんが、このブログも「MySQL+ダイナミックパブリッシング」から「SQLite+スタティックページ」に移行してみました。

以降では「ダイナミックパブリッシング」「スタティックページ」をそれぞれ「DP」「SP」と略記します。

でなぜ移行を思い立ったかというと、以前から(細かいバグの件は別にして)DPはイマイチだなーと感じていたことがあったからです。少し詳しく説明すると以下のような現象・事実があるのです。

「DPに失敗した際に表示されるエラーページが検索エンジンbotにクロールされてキャッシュされている例を散見する」

ロリポップの場合、時間帯によってはMySQLサーバーが過負荷状態になってクエリーに応えられずDPに失敗することがしばしばあるため、このような現象が観測されます。他のレンタルサーバーでも同じ状況は発生し得ますが、ロリポップは10数万のユーザーに対してわずか6台のMySQLサーバーしか用意されておらず(もちろん全ユーザーがMySQLを利用するわけではありません。おそらく10%以下でしょう。)、極めて状況が悪いと言えるでしょう。

「CGIの実行時間を制限するサーバーでは、再構築時、実はSPよりDPの方がInternal Server Errorを起こしやすい」

例として個別アーカイブをすべて再構築する場合を考えると分かりやすいです。SPでは(mt.cfgの)EntriesPerRebuildで指定された個数(既定では40個)ずつ再構築していくために一回ずつのCGI実行時間は短く保たれます。一方、DPでは個別アーカイブの再構築は行われない代わりにFileInfo(個別アーカイブの動的生成に必要な情報)の更新が行われますが、この作業はEntriesPerRebuildの値に関わらず一回のCGI実行で一度に行います。

前者(SP)の一回ずつのCGI実行時間はエントリー数が増加してもほぼ一定で、またEntriesPerRebuildに小さ目の値を設定することでInternal Server Errorを積極的に回避することもできます。MySQLサーバーがビジーで再構築に失敗しがちな場合にもこの対処が一定程度有効です。しかし、後者(DP)の所要時間はエントリー数の増加とともに単調に増加するのみでユーザー側から制御する手段がありません。

つまり、サーバー環境によってはダイナミックパブリッシングは(仮に使えたとしても)実用的でない場合があるということです。

SQLiteを使う場合、現状ではDPは使えませんから上記の問題は半ば強制的に解決します。また、MySQLサーバーの負荷状況に依存しない(Webサーバーの負荷状況にだけ依存する)という利点もあります。各Webサーバーに一つずつMySQLサーバーを立ち上げてくれればDPでも構わないわけですが、一応バックアップを取ったりデーモンが落ちたら再立ち上げする管理コストや、ストレージ容量の問題があるのでしょうね。

さて、Movable TypeでのSQLiteのおおまかな利用方法に関しては随分前のエントリーに書いてあります。

Ogawa::Buzz: SQLiteをMovable Typeで使ってみる

上記のエントリーはDBD-SQLite 0.31(SQLite 2.8.12相当)を前提に書いたものです。その後DBD-SQLite 1.08(SQLite 3.1.3相当)がリリースされて、Version 3系のSQLiteとMovable Typeを組み合わせて利用できるようになりました(当時はできませんでした)。それに応じて上記エントリーには記述を追加してあります。

また、SQLite2系とSQLite3系の性能差に興味があるSQLiteマニアな人は、下記のエントリーも参考にされるとよいでしょう。AutoCommitをOnにした状態のままでもSQLite3にするだけで4~9%程度の速度改善が期待できます。

Ogawa::Buzz: Benchmark: Importing Multiple Entries

Google Searchとの最近の戦い

このサイトにはGooglebotがクロールに来てくれません。クロールに来てくれるまで私とGoogle Searchとの戦いは続きます。これまでの戦歴は下記を参照のこと。

Ogawa::Buzz: Google Searchとの戦いの日々

簡単に経緯を説明すると、一年ほど前に某サーバーからロリポップ(hassaku.main.jp)に、半年以上前にhassaku.main.jpからas-is.netにドメインを変更し、変更のたびにリダイレクトしていたのですが、これがスパミング行為と取られたか、いろいろ手を尽くしてもas-is.netの方にはGooglebotがクロールに来てくれません。リダイレクトしていても一向に変化がないので旧ドメインへのアクセスはフェイルするようにしていますが状況は変わらず、旧ドメインの古いデータがキャッシュされている状態が続いていて結構不愉快です。

2月27日

404エラーなどでロリポップのエラーページに誘導されるのを停止させてみました。Yahooなどにこのエラーページがキャッシュされている例が見られたためです。徹底的にシンプルにするために以下のように.htaccessに設定してみました。

ErrorDocument 400 "400 Bad Request
ErrorDocument 401 "401 Unauthorized
ErrorDocument 403 "403 Forbidden
ErrorDocument 404 "404 Not Found
ErrorDocument 410 "410 Gone

さらに、旧URL(hassaku.main.jp)に関して残っているキャッシュ情報を削除するためにrobots.txtを作ってすべてのページのクロールを禁止しました。ただし、このrobots.txtは旧URLからアクセスしたときには読め、現URLからアクセスしたときには読めないように.htaccessには以下のように記述しました。

RewriteEngine on
RewriteCond %{HTTP_HOST} ^(hassaku\.main\.jp)(:80)? [NC]
RewriteRule !^robots\.txt$ - [G,L]
RewriteCond %{HTTP_HOST} ^(as-is\.net)(:80)? [NC]
RewriteRule ^robots\.txt$ - [G,L]

これで旧URLに関して残っているキャッシュ情報はいずれ削除されるはずということになります。

3月4日

いずれというのも心許ないので、Googleの自動URL削除システムを使ってみることにしました。このシステムでは(1)robots.txtファイルを使用して個々のページ、サブディレクトリ、画像を削除する、(2)メタタグを使って1ページだけを削除する、(3)無効なリンクを削除する、のいずれかの方法が採れます。ひとまず、(1)の方法で旧URLのキャッシュを削除するように申請してみました。

翌日には削除されました。…だからってクロールに来てくれるわけではありませんが。しかも旧旧URLでのキャッシュが10件分残っているようでこれも消す必要があるのでしょう。しかもGoogle 検索: site:as-is.netとかしてみると、旧旧URLでのキャッシュが10件分見られますが1560件ヒットしているという訳の分からなさ加減です。インデックスの再計算が必要なのかしら。

3月10日

キャッシュされている旧旧URLのデータを削除するべく、(3)の方法で一個ずつURLを指定してやりました。本当に10件だけだとよいのですけど。1560件は無理です。

あと2月27日にやった方法だとas-is.netでrobots.txtを設定できなくなってしまうことに気が付いてしまいました。なので、robots.txtには以下のように記述。

<?php
header('Content-type: text/plain');
if (!strcasecmp($_SERVER['SERVER_NAME'], 'hassaku.main.jp')) {
 echo <<<EOD
User-agent: *
Disallow: /
EOD;
} else {
 echo <<<EOD
User-agent: *
Disallow: /mt/
EOD;
}
?>

.htaccessは↓のように設定。

<Files robots.txt>
AddType application/x-httpd-php txt
</Files>

3月14日

やっぱり(1)の方法でざっくり消してやることにしました。即日反映されました。Google 検索: site:as-is.netとしても何も表示されなくなりました。が、Google 検索: ogawa site:as-is.netすると、blogpeopleのキャッシュとともに「約1,620件」と表示されます。…増えとるがね。

でも勝利は目前のような気がしてきました。


4月18日

目前のような気がしていましたがまだまだでした。今日、Google 検索: ogawa site:as-is.netしてみると何も表示されなくなりました。インデックスが更新されたことは確かですね。しかし…。

ちなみにこのブログ、Inktomi SlurpやMSN Searchbotにはえらく気に入られているらしいのです。

Mar 6, 2005

悪魔のような女

土曜ワイド劇場で菅野美穂・浅野ゆう子でやっていました。

「悪魔のような女(Les Deabolique)」は二度映画化されており、いずれにしてもビビリ役の側の配役がドラマの成否を決定付けるというシビアなサスペンスです。全体にぬるい仕上がりだった96年の映画(S. Stoneの終盤の変心には笑わせられました)では、Isabelle Adjani。今回は菅野美穂。これはもう観るしかないでしょう。ちなみに私の中での菅野美穂の評価は非常に高いです。どんなクソドラマでも彼女がキャスティングできればそれだけで成功の目があると言っても過言ではありません。

で、菅野美穂の期待通りのビビリ芸を堪能させていただき(浅野ゆう子44歳の「彼は私の体が目当てなの」発言にもブハハッ)、大満足なわけですが、ドラマとしてはクソでした。このドラマは「オカルト的な演出をすることを前提にした古式ゆかしいサスペンス劇」であって、オカルト自体の入り込む余地はありません。確かに55年の映画ではラストでオカルト臭い仄めかしがあるわけなのですが、しかし、幽霊になった菅野美穂が復讐を果たすなどという超くだらないオチを付けたらそれだけでドラマとして台無しです。それまでの緊張感が何によって維持されてきたのかを忘れさせられてしまう瞬間でした。エンドロールではやっぱり落合正幸の名前…。

中谷美紀にしろ菅野美穂にしろ作品とスタッフに恵まれないのは気の毒ですが、逆にチャレンジングな作品に出演する機会も増えるわけなんですな…。

Mar 2, 2005

Benchmark: Importing Multiple Entries

This benchmark is intended to make clear the performance characteristics of the underlying DB engines which can be used by Movable Type.

I recognize this is not a typical transaction for Movable Type. My intention of performing this benchmark was to test the hardest transactions for any DBs, because importing has leastly-read but mostly-write operations. Rebuilding takes a long time but it is a mostly-read case and doesn't push DBs so hardly. I believe that the rebuilding time is dominated by Disk I/O rather than DB accesses. Also thinking about other cases such as coutinueously commenting or tbpinging, CGI setup time is supposed to be dominant.

Method

Importing multiple entries (40, 200, 1000) and couting the cpu times and elapsed times by using Benchmark and Time::HiRes modules. In each cases, I ran the benchmark four times and picked up the best case.

Server Specification

  • Intel Pentium 4 2.40Ghz 384MB Memory
  • Fedora Core 3
  • Apache httpd 2.0.52
  • MySQL 3.23.58
  • Movable Type 3.15

Movable Type and MySQL are running on a server.

Importing Data

All entry have "GNU Public License" as their entry body, and they have diffrent titles and same one category.

DB Engines

DBD
Berkeley DB 4.2.52 + DB_File 1.809
MySQL
MySQL 3.23.58 + DBD-mysql 2.903
SQLite2
DBD-SQLite2 0.33
SQLite3
DBD-SQLite 1.0.8
MySQL*
MySQL 3.23.58 + DBD-mysql 2.903 + w/o AutoCommit
SQLite2*
DBD-SQLite2 0.33 + w/o AutoCommit
SQLite3*
DBD-SQLite 1.0.8 + w/o AutoCommit

MySQL*, SQLite2*, and SQLite3* disable AutoCommit option and do "begin_work" and "commit" at the start and end of the importing process.

Results

Importing 40 Entries

Importing 200 Entries

Importing 1000 Entries

Quick Observations

  • BDB is slowest in any cases.
  • SQLite3 is 5%-30% faster than SQLite2. Upgrading SQLite version is meaningful.
  • If "AutoCommit" turns on, SQLite2 and SQLite3 are as slow as BDB.
  • If "AutoCommit" turns off, SQLite3 can be faster than MySQL.
  • For MySQL, the effect of turning "AutoCommit" off is quite small or at least not a positive option.

No AutoCommit Patch

Here is a patch for disabling AutoCommit option, which is used in this benchmark.

diff -ru MT-3.15-full-en_US/lib/MT/ObjectDriver/DBI/sqlite.pm MT-3.15-full-en_US-noautocommit/lib/MT/ObjectDriver/DBI/sqlite.pm
--- MT-3.15-full-en_US/lib/MT/ObjectDriver/DBI/sqlite.pm 2004-08-17 09:40:01.000000000 +0900
+++ MT-3.15-full-en_US-noautocommit/lib/MT/ObjectDriver/DBI/sqlite.pm 2005-03-02 21:17:00.403018954 +0900
@@ -34,9 +34,18 @@
         { RaiseError => 0, PrintError => 1 })
         or return $driver->error("Connection error: " . $DBI::errstr);
     $driver->{dbh}->{sqlite_handle_binary_nulls} = 1;
+    $driver->{dbh}->begin_work;
     $driver;
 }
 
+sub DESTROY {
+    my $dbh = $_[0]->{dbh};
+    if ($dbh) {
+        $dbh->commit;
+        $dbh->disconnect;
+    }
+}
+
 sub _prepare_from_where {
     my $driver = shift;
     my($class, $terms, $args) = @_;
diff -ru MT-3.15-full-en_US/mt-load.cgi MT-3.15-full-en_US-noautocommit/mt-load.cgi
--- MT-3.15-full-en_US/mt-load.cgi 2005-01-25 12:37:45.000000000 +0900
+++ MT-3.15-full-en_US-noautocommit/mt-load.cgi 2005-03-02 21:13:54.835168910 +0900
@@ -298,6 +298,8 @@
         $map->save
             or die "Save failed: ", $map->errstr;
     }
+    MT::Object->driver->{dbh}->commit
+        if $mt->{cfg}->ObjectDriver =~ /^DBI::sqlite$/;
 }
 
 };

Mar 1, 2005

日本語化されたジーメール

何だか唐突にGmailのメッセージが日本語化されてしまった。とても嫌な気分だ。Gmailではほとんど英語のメールしか書かない私にとって、中途半端な日本語化は非常なストレスとなっている。

アカウントの設定のところで以前fluent languageにJapaneseを設定していたせいだと思われるのだが、それはともかく今のところ英語に戻す方法が見つからない。何なんだよ、これは。とりあえずfluent languageにEnglishも追加してもらえるよう一行メールを送りつけてみたところ。

違ったようだ。ブラウザの言語設定(Accept-Languageヘッダ)に従うみたい。GmailのPreferenceで設定できるようにしてくれないかな。BloggerもGmailと同じようにブラウザの言語設定に従ってしまうんだよね。これがGoogleのユーザーインタフェース言語の選択方法のStandardになっていくのかしら。

About Me

My Photo

つくばで働く研究者

Total Pageviews

Amazon

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