Jul 24, 2006

mysqldump2email公開

MySQLデータベースのダンプファイルをzipアーカイブして、メールサーバに送る、わりとアリガチなスクリプトを必要に迫られて書いたのでついでに公開。

mysqldump2email - ogawa - Google Code

手前味噌だけど結構便利。ダンプデータを平文メールで送るのに抵抗を感じるので、zipのencryption機能も使えるようにしてある。1時間おきに意味なくダンプしたりすると、Gmailのスプールと言えどもみるみる埋まっていってユカイ。

例によって試してはいないけど、Gmailの30日でexpireするTrash機能と組み合わせると、バックアップとしての実用度がさらに高まるかもしれないと思った。例えば、

  • username+daily@gmail.com宛に毎日ダンプする。Gmailの設定でこのアドレスへのメッセージはTrashに振り分ける。
  • username+monthly@gmail.com宛に毎月ダンプする。このアドレスへのメッセージはTrashに振り分けない。

という組み合わせで使うと、過去30日間のバックアップと月ごとのバックアップがGmailスプール上に保持し続けられる。仮に1バックアップが10MBくらいのメッセージサイズで済むとすると、2GBのGmailスプールに14,5年継続してバックアップを取り続けられるという計算になる。

…それまでGoogleが存続していれば。

ちなみにzipしても軽く100MB超えるぜ、という人はこんなところで油を売っていないでもっとましなこと(ストレージの二重化など)を考えてください。

そうそう。思い出した。subversionのダンプファイルもGmailでバックアップするというのはどうだろう?

2006-07-28追記:
ここギコ!: サーバBackUpをGmailに保存ってことなので、0.02でgSpaceに対応してみた。けれどもgSpaceはGmail for your domainでは使えないみたいなので、私にとってはやや無意義ではある。

一般的なファイルで対応するというのももちろんできる。しかし、base64で33%増量されることもあって、通常の用途ではサイズが大き過ぎる(Gmailスプールが小さ過ぎる)のではないかと危惧している。差分を取らないとやってられないとすると「一メッセージ、一バックアップ」という安楽さが損なわれて痛し痒しというところ。そういうわけで、この方法が適している対象は、真面目に日々保管しているとしゃれにならない数・量になるが、一個ずつのサイズはせいぜい数100KB~数MBで済むものかなという気が今はしている。MySQLのバックアップとかApacheのログとかね。

Jul 21, 2006

mod_fcgidで Internal Server Error

ここギコ!: Apache2.2とFastCGI、Subversion

私も同じようなことで嵌った。

Apache 1.3.x + mod_fastcgiのときは問題なかったのだけど、Apache 2.2.2 + mod_fcgidに移行した後、FastCGI + MT 3.31 + SQLiteで月別アーカイブを再構築するたびに500 Internal Server Errorが発生する。MT 3.2やMySQLだと起きない。

なんでや~としばらく悩んだ末、IPCCommTimeoutオプションを設定すればよいことが分かった。このオプションは、FastCGIアプリケーションの通信タイムアウト時間を設定するものでデフォルトでは20秒になっている。これが多分短すぎるのだろうと思って長めに取ってみたら、Internal Server Errorはおさまった。

IPCCommTimeout 60

mod_fastcgiではFastCgiConfigオプションなどのappConnTimeoutパラメータで指定できるが、デフォルトではblocking connectが有効になっている。結局のところOSの規定するTCPタイムアウトに制約されるだけなんじゃないかな。Linuxなら3分くらいだっけ。サーバに使っているFreeBSDはよく分からないけど。

Jul 18, 2006

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

Jul 17, 2006

KeywordsAsTags Plugin公開

エントリーのキーワード欄の入力内容を自動的にMovable Type 3.3のタグとして登録する機能を実現するKeywordsAsTags Pluginを公開します。

KeywordsAsTags - ogawa - Google Code

Movable Type 3.3でタグ機能がネイティブ対応されましたが、既存のXMLRPCやAtomPPを使ったブログ管理ツールからはこのタグ機能が利用できません。そこで、KeywordsAsTagsプラグインは、これらのブログ投稿ツールからも簡単にタグ登録できるように、エントリーキーワードに指定した文字列を自動的に3.3以降で実現された「タグ」に変換して登録する機能を提供します。

このプラグインは実験目的で作ったものです。お手持ちのブログ投稿ツールで機能するかどうか確認していただけると幸いです。

また、現在のところキーワード欄とタグを完全に同期する手段は用意していません(気が向いたら実装するかもしれませんが)。単にエントリー保存時にキーワード欄の内容をパーズしてタグとして登録するだけなので「キーワード」→「タグ」の一方通行になります。例えば、MTのエントリー編集画面からタグを変更してもキーワードの内容は変更されません。

2006-07-17追記:
例によって(笑)、一足早くBlogWriteはタグ機能をサポートしているそうです。他のブログ投稿ツールも徐々に対応することを期待しています。言わずもがなですが、KeywordsAsTagsプラグインはそれまでの「つなぎ」的なツールだと、私は考えています。

汐見荘@伊豆北川温泉

せっかくの連休なので思い立って伊豆北川(いずほっかわ)へ。

車だと西湘バイパスとか真鶴道路とかダルい。帰りはさらにダルい。箱根ターンパイク~伊豆スカイラインを使う手もあるけどターンパイクに入る以前の小田原厚木道路の出口ですらダルい。ので、東京~熱海(東海道線快速アクティー・自由席グリーン車)、熱海~伊東~伊豆北川(伊東線・伊豆急)で3時間かけて移動。踊り子号だと伊豆高原の次は伊豆熱川にしか止まらないからね。

伊豆北川駅はなかなか味わいのある(鄙びた)駅。

駅のホームから見下ろした1kmにも満たない海岸通りだけで、有名な黒根岩風呂、温泉宿街、港が完結してしまう、そんな北川のコンパクトさにぐっとくる。地形的に山が海岸線に迫っている上、もう十分開発されていてこれ以上大きくなりようもない。

お世話になった宿は汐見荘という港の真ん前の宿。5部屋しかない家族経営の民宿だけど、施設は新し目だし清潔・快適なのがありがたい。部屋はこれ以上望むべくもない、一面オーシャンビュー。↓妙になごむ。

獲れ獲れ舟盛り、うまーっ!!

メインはあくまで舟盛りなんだけど実はそれ以外の料理もかなり良かった。正直なところ冷製トマトのカッペリーニで食欲をかき立てるとか、新鮮な魚介類の食し方としては適さない天ぷらはメニューから外すとかいった、既存の温泉宿料理のプランニングにはない発想に軽く驚かされた。煮びたし(揚げびたしだっけ?)のナスも美味しかった(露地ものは7月から旬だよねとか)。アワビかサザエを追加注文しておくんだったと後悔していたら手頃なサザエ焼きが出てくるし、サザエ入り茶碗蒸し、(なぜかえらく待たされたけど)シッタカ入り味噌汁と畳みかける海の幸コンボには参りましたという他ない。伊豆萬耀 純米吟醸でいい酔い加減。

伊豆高原と熱川の間というついつい見落としがちなエリアだけど、思いがけず良いところだった。汐見荘の皆さんも気さくでとても親切にしていただいた。年に一回くらいのペースでリピートしてもいいなあ。

Picasa アルバム - ogawa - 2006-07 Hokka...


2008年4月26日に伊勢海老付きのプランで再度宿泊。相変わらず良いもてなしだった。汐見荘 伊豆北川 - Google 検索でこのブログがかなり高い順位で表示されるので面割れしているかもしれん。

Jul 13, 2006

MT 3.31の MTEntryTagsの性能バグ

Movable Type 3.3日本語版がリリースされてから2週間ほど経ち、3.31が出てしまったがまだ移行できないでいる。なぜできないかと言えば再構築がとても遅いからなのである。Apache 2.2.2 + mod_fcgidの組み合わせですべての個別エントリーアーカイブを再構築したとき、3.2では要する時間は40秒程度。ところが3.3では3分強かかる。ありていに言って4~5倍。これでは話にならない。

少し追っかけてみて...分かった。

実は3.3で導入された「タグ」を使わなければこの問題は表面化しない。なので最初、TagSupplementals Pluginmt-keywords2tagsのせいだと思っていたのだが、まったくそうではなかった。

そうではなくて、MTEntryTagsコンテナのレンダリング時の処理に問題がある。

まず、MTEntryTagsコンテナを含むテンプレートの再構築のたびに、該当ブログの全タグ情報をデータベースから読み込む。実際にはこの全タグ情報はキャッシュされるので、CGIの場合には「再構築のリクエスト回数」だけ同じ処理が行われると考えればよい。例えば、エントリー総数1000で、EntriesPerRebuildの設定が40のとき、25回全タグをデータベースから読み込むことになる。ただし、FastCGI環境の場合には継続中のアプリケーションインスタンスが再利用されることで1~数回で済む公算がある。

次に、読み込んだ(あるいはキャッシュにある)全タグ情報に対して、各タグを利用しているエントリーの個数を数え上げている。この処理はMTEntryTagsをレンダリングするたびに行われるので、CGIでもFastCGIでもエントリー総数1000の場合には1000回この処理を行うことになる。インデックステンプレートなどでMTEntryTagsを繰り返し呼び出す場合も悲惨。

総合的に考えると、タグの総数はエントリーの総数に比例するのでO(n2)の時間がかかるはず、ということになる。

この処理はそもそも全タグ情報をキャッシュしたり、全タグのランク(MTTagRank)を取得したりする目的で行われている。例えばMTEntryTagsコンテナを利用した後、MTTagsコンテナを利用した場合などには前処理を幾分省略できる。だが、MTTagsコンテナの処理でも同様にキャッシュを行っているし、エントリーで全タグ情報を参照することは、MTEntryTagsの中でMTTagRankを使う場合を除いてはない。だから安直に考えれば処理そのものをなくせば大幅に高速化される。実際、以下の変更を加えれば3.2 + Tagwire Pluginと遜色ない速度で動作する。

--- MT-3.31-ja/lib/MT/Template/ContextHandlers.pm.bak Fri Jul  7 02:11:02 2006
+++ MT-3.31-ja/lib/MT/Template/ContextHandlers.pm Thu Jul 13 01:50:05 2006
@@ -559,34 +559,10 @@
     
     require MT::ObjectTag;
     require MT::Entry;
-    my $type = MT::Entry->datasource;
     my $entry = $ctx->stash('entry');
     return '' unless $entry;
     my $glue = $args->{glue} || '';
 
-    my $blog_id = $ctx->stash('blog_id');
-    my $tags = _tags_for_blog($blog_id, $type);
-    my $min = 0;
-    my $max = 0;
-    foreach my $tag (@$tags) {
-        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
-        });
-        $tag->{__entry_count} = $count;
-        $min = $count if ($count && ($count < $min)) || $min == 0;
-        $max = $count if $count && ($count > $max);
-    }
-    @$tags = grep { $_->{__entry_count} } @$tags;
-
-    local $ctx->{__stash}{tag_max_count} = $max;
-    local $ctx->{__stash}{tag_min_count} = $min;
-    
     my $iter = MT::Tag->load_iter(undef, { 'sort' => 'name',
         join => ['MT::ObjectTag', 'tag_id',
         { object_id => $entry->id, blog_id => $entry->blog_id, object_datasource => MT::Entry->datasource }, { unique => 1 } ]});

上の変更では、MTEntryTagsの中でMTTagRankが使えなくなってしまうので本当は良くない。tag_max_count, tag_min_countなどの計算をMTTagRankの最初のレンダリング時まで遅延すれば、タグの振る舞いを変えることなくMTTagRankを含まないMTEntryTagsを高速化できる。この修正はここに掲載するには少し長いので以下で参照のこと。

MT_3_31_ja_UO_Patch - ogawa - Google Code

2006-08-06追記: 3.32のbranchにマージされたっぽい。これなら安心して使えるかな。

実はこの問題が解決しても簡単には乗り換えられないもう一つの理由がある。

それはFastCGI環境で利用した場合のResidentのサイズがかなり大きいということだ。3.2では16MB弱程度なのだが、3.3では20MBに達する。また、再構築などを行うと3.2より早くResidentが巨大化する。例えば、個別エントリーアーカイブの全再構築を2回ほど行うと50MBにも達する。以前のバージョンから常駐環境でリクエストごとに発生するメモリリークは大なり小なりあった問題だが、3.3ではアプリケーション自体の巨大化も相まってFastCGI化を断念せざるを得ない水準になっているというのが正直な感想だ。

Jul 12, 2006

MoinMoin、はじめました。

Ogawa::Buzz: MySQLのキャラクターセットみたいなこともあって、実はMediaWikiに代わるWikiシステムを求めていた。他にもトラックバック機能をアクティブにしておいたら死ぬほどスパムが溜まってしまってうんざりだったり、溜まったトラックバックをさっくり削除するインタフェース or ツールが見つからなくてさらにげんなりだったり。

もっと言うと私自身PHPのランタイムシステムの仕組みをあまり理解していないことがある。PHPでstandaloneなWebサーバを書いたという話は過分にして聞かない。不勉強のせいかもしれないけれどもPHPのメモリ管理についてもあまり聞かない。extensionsを作る人はどうしても知らざるを得ないが、ユーザから見るとリクエストごとに破棄されてしまうのでどんなメモリ管理システムでも構わないから話題にならないのだろうか?でもきっとRCなんだろうと想像する。スレッドの話も耳にしないし、extensionsをスレッドセーフに作るという話も聞かない。

つまりはrubyやperlやpythonに比べて言語のセットが小さいのだろう。しかし、言語システムは言語システムである。言語システム自体の議論をそれほど見かけない一方で、簡単なので(それに加えて内部関数やextensionsが妙に充実しているので)みんな使っているという状況は、私に違和感を覚えさせるには十分だ(PHPの実装にコミットされている方からの非難は当然だろうけれど)。

脱線はこのあたりにして、できればPHPでないWikiシステムに乗り換えようと思ったわけ。ちょっとお仕事的にPythonのコードを読まざるを得ない状況に陥っていることもあって、MoinMoinはどうなのよと思ってみた。

MoinMoinWiki - MoinMoin

…思ったが早いかMediawikiに置いていたコンテンツを移行してみた(念のため、元のMediawikiコンテンツのURLは自動的にMoinMoinのコンテンツにredirectされるようにしてある)。

いやこれ、いいね(笑) とっても分かりやすいわ。富豪的な「カテゴリー」の実現方法もとても楽しい。しばらく遊べばPythonも上達するかな。

Jul 5, 2006

再び東京ドームで野球を観る

勝手に完治祝いということで、約一年ぶりに(Ogawa::Buzz: 東京ドームで野球を観る)野球観戦に行ってきた。

Yahoo!プロ野球 - 2006年7月5日 巨人vs.中日 結果
2006シーズン 公式戦/試合結果

6連続完投がかかる佐藤充が先発だった。完投くらいはできそうな出来だったが、相手ピッチャーが調子の良いパウエルだった(下手すると完封されそうだった)し、7回にタイムリーで先制されてしまったこともあって8回の攻撃で交代させられてしまった、残念。斉藤雅樹の11連続完投というのは本当にすごい記録だと思った。

7回裏に先制されて8回表に逆転し、高橋聡文・岩瀬が抑えるという中日ファンにとっては言うことないゲームだったわけだけど、要はどっちの打線もヘボヘボだってことだわな。今日のハイライトは、パウエルに続投させておけば1-0完封だったのに高橋尚とか西村に継投させたこと。勝つつもりなら豊田を出すものだろ、たとえ先月打たれたとは言え。あとは偉大なクレーマー監督・落合の初退場と、ジョージ・アリアスがゲームに花を添えた。

おまけ:

Jul 4, 2006

MySQLのキャラクターセット

ドキュメントを読まない私が、いまさらMySQLのキャラクターセットで結構面倒な作業をさせられたのでその記録。

  • もともとMySQL 4.0.xを使っていた。
  • 「skip-character-set-client-handshake」オプションが導入された4.1.15が出たタイミングで、4.1.15に移行した。
  • このとき、「--with-charset=latin1」でコンパイルされ、かつmy.cnfなどでdefault-character-setを特に指定していないMySQL 4.1.15で、4.0からアップグレードした。したがって各データベースのテーブルには、「DEFAULT CHARSET=latin1」が付与された。
  • 「skip-character-set-client-handshake」付きでmysqldを起動していたので付与されるDEFAULT CHARSETがなんだろう構わないと思って、特に気に留めていなかった。強いて言えばphpMyAdminで文字化けするくらいの問題しかなかった。

と、ここまでが現状。

今回、MySQL 5.0.22にアップグレードした。「skip-character-set-client-handshake」付きならまったくそのままで構わないのだが、そろそろ4.1以降で導入されたキャラクターセット機能を使ってみようかと思ったわけだ。

ひとまず、DBの中身は全部utf-8なので特に考えることはない。my.cnfに以下のように追加してmysqldを再起動する。

[mysqld]
default-character-set = utf8
[mysql]
default-character-set = utf8
[mysqldump]
default-character-set = utf8

次に、どうやって各テーブルに付いたDEFAULT CHARSETをlatin1からutf8に変更するのかなと思った。

普通に考えて、ALTER TABLEを使って、

ALTER TABLE hageTable CHARACTER SET utf8;

とやると修正できるような気がした。しかし、実際やってみるとhageTableのDEFAULT CHARSETは間違いなく変更されるのだが、今度はフィールドに元のcharsetがセットされてしまう場合があるようでどうもおかしい(すでにINSERT済みのレコードがあるときにそうなるのかな?)。TABLEの数だけALTER TABLEを実行しなくてはならないのも腹立たしい。

次に、そうだ、mysql40互換形式でダンプしてからリストアすればいいじゃんか、と思った(思っちゃ駄目!)。

$ mysqldump -uUser -pPassword --compatible=mysql40 hageDb > hage.sql
  (drop & create)
$ mysql -uUser -pPassword hageDb < hage.sql

で、これをやると何が起こるかというと、フィールドのauto_increment修飾子が落ちてしまって致命的におかしなことになる。

仕方がないのでもう少しだけ深追いしようと思って、面倒だけど普通にダンプしてsedで書き換えてからリストアしてみることにした。

$ mysqldump -uUser -pPassword --compact --default-character-set=binary hageDb > hage.sql
$ sed 's/DEFAULT CHARSET=latin1/DEFAULT CHARSET=utf8/g' hage.sql > hagehoge.sql
  (フィールドにcharsetが付いている場合にはその分も変換すること)
  (drop & create)
$ mysql -uUser -pPassword hageDb < hagehoge.sql

ひとまずちゃんと動いているようだ。しかし、なんだ、やり方が美しくない。とても不満が残る。

もっとうまいやり方があったら教えてください。

About Me

My Photo

つくばで働く研究者LV15

Total Pageviews

Amazon

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