Perl

February 24, 2010

既製品の管理ツールを使わないことでウェブサービスの TCO を下げる話について hbstudy#8 で話してきた件

 昨日、hbstudy#8 で話をする機会をいただくことができたので、Nagios や Amanda といった既製品の管理ツールやバックアップツールを使わずに内製したことで「パストラック」の運用コストを下げた、という話をしてきました。

 もちろん、「既製品を使わない」というのもひとつの手段にすぎませんから、それを無闇にお勧めするつもりはありません。ただ、小回りの効くツールを組み合わせる手法にも十分な競争力があるという点、あるいはその事例として参考になれば幸いです。

 スライドはこちら。hbstudy 運営の皆様、話を聞いてくださった皆様、ありがとうございました。

January 18, 2010

監視とは継続的なテストである、という話 (もしくは cronlog とテストスクリプトを組み合わせた監視手法について)

 結論から先に。cronlog を使えば、アプリケーションのテストコードと全く同じ形式で、監視用のスクリプトを書くことができます。プログラマが監視ツールの記法を覚える必要はありません。これは、プログラマが運用も行うケースでは特に有効な手法だと思います。

 先週公開した Kazuho@Cybozu Labs: crontab を使って効率的にサービス監視する方法 というエントリで、crontab と拙作の cronlog を用いてサービス監視を書く手法を紹介しました。しかし、挙げた例はいずれも ping や http のテストといった外形監視の手法です。RDBMS とウェブアプリケーションのみから構成されるサービスならそれだけで十分でしょう。

 しかし、外形監視だけでは、メッセージキューのような非同期処理の遅延を観測することはできません。また、http のログを監視して、エラーレスポンスや平均応答時間の監視も行うことが望ましいです。それらの処理をどう書くか。

 自分は、監視とは継続的なテストである、という観点から、キューの処理遅延等の監視を、アプリケーションのテストスートの一部として書いています。具体的には、以下のような感じ (一部改変)。テストはアプリケーションと設定情報を共有しているので、監視ソフトウェアに様々な設定値を入力する必要もありません。

use strict;
use warnings;

use Test::More;
use Pathtraq;

my $app = Pathtraq->new;

cmp_ok
    do {
        my $row = $app->dbh->selectrow_arrayref(
            'select XXXX', # Q4M 使ってない昔のキュー監視
        ) or die $app->dbh->errstr;
        $row->[0];
    },
    '<',
    1000,
    'delay of updater';

for my $tbl (qw(analyze_page set_page_info analyze_keyword)) {
    cmp_ok
        do {
            my $row = $app->dbh_queue->selectrow_arrayref(
                "select count(*) from $tbl",
            ) or die $app->dbh_queue->errstr;
            $row->[0];
        },
        '<',
        1000,
        "delay of $tbl",
}

done_testing;

 あとは、これらのテストコードを、crontab 上で cronlog を介してテストの実行ツール (perl なら prove) に食わせるだけ。

*/5 * * * * cd /var/webapp && exec setlock -nX /tmp/monitor.lock cronlog -- prove -v monitor.t/*.t 2>&1

 これで、監視対象項目 (=テスト項目) になんらかの問題が見つかった場合には、以下のような感じのメールが届くようになります。

Subject: Cron <user@host> cd /var/webapp && exec setlock -nX /tmp/monitor.lock cronlog -- prove -v monitor.t/*.t 2>&1

host.example.com starting: prove -v monitor.t/queue.t

#   Failed test 'delay of analyze_page'
#   at monitor.t/queue.t line 23.
#     '1396'
#         <
#     '1000'
# Looks like you failed 1 test of 4.
monitor.t/queue.t ..
ok 1 - delay of updater
not ok 2 - delay of analyze_page
ok 3 - delay of set_page_info
ok 4 - delay of analyze_keyword
1..4
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/4 subtests

Test Summary Report
-------------------
monitor.t/queue.t (Wstat: 256 Tests: 4 Failed: 1)
Failed test:  2
Non-zero exit status: 1
Files=1, Tests=4,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.40 cusr  0.08 csys =  0.50 CPU)
Result: FAIL
command exited with code:1

 perl に限らず、テストの実行ツールは一般に、成功した場合はゼロ、失敗した場合は非ゼロを返します (そうしないと make test && make install とか自動化できない)。この特性と cronlog の、非ゼロが返った場合だけ cron を経由してログをメールで送信する、という機能を組み合わせることで、テストコードが監視系に早変わりするのです。さらに外側で setlock -nX しているのは、5分間でテストが終了しなかった場合に、やはりエラーメールを送信するため。

 以上のように、cronlog を使えば、プログラマが使い慣れたテストスートの一部として監視系を書くことができます。パストラックでは更に、リバースプロキシのログ監視を touch_if コマンドとテストコードを連動する形で実施するようにしています。どうぞご覧ください。

blockdiff を使ったお手軽ホットバックアップ環境の構築 (Linux, MySQL, etc.)

 一昨日に開催された hbstudy #7 にバックアップの話を聞きに行ってきました。Amanda を中心にした話で、とても勉強になりました。が、設定がめんどくさそうだなぁ、とも。自分の需要にはあわない感じでした。

 勉強会が終わったあとで、自作のバックアップスクリプト blockdiff に関する話を何人かの方とさせていただいたのですが、思いのほか反応が良かったので、あらためて紹介したいと思います。

 blockdiff は、一言でいうと、パーティションやデータベースのデータファイルの差分バックアップツールです。rsnapshot に似ていますが、rsnapshot ではデータベースのホットバックアップ不可能です。逆に blockdiff はディレクトリ単位でのバックアップには対応していないかわり、ファイルシステムやデータベースを、一貫性を保ちつつ実質無停止で差分バックアップすることができます

 blockdiff の具体的な特徴は以下のとおりです。

  • 設定ファイルが不要
  • 複雑な設定がありません。コマンドライン (あるいは crontab) で、適当な引数をつけてバックアップコマンドを呼び出すだけの簡単インターフェイスです。コマンドラインで動作を制御するため、(ホットバックアップを含む) 様々なバックアップロジックを組むことも可能です。

  • バックアップを取るサーバにソフトウェアのインストールが不要
  • ssh 経由でリモートサーバのバックアップを取ることができます。サーバにバックアップソフトウェアをインストールする必要はありません (バックアップを取るために必要なプログラムは自動的に送り込まれます) 注1

  • フルバックアップと増分バックアップに対応
  • フルバックアップと増分バックアップに対応しています。また、下位レイヤのスクリプト (blockdiff_dump) を直接実行すれば、差分バックアップも可能です。

  • ファイル単位、あるいは LVM スナップショットを使ったバックアップが可能
  • ファイル単位のバックアップと、LVM スナップショットを使ったホットバックアップに対応しています。

  • MySQL のホットバックアップが可能
  • 同梱の mysqllock コマンドと組み合わせることで、LVM スナップショットを使った MySQL データベースのホットバックアップが可能です。PostgreSQL については確認はしていませんが、ロックコマンドを使わなくてもバックアップが取れるんじゃないかと思います。

  • LVM ベースの VM のホットバックアップが可能
  • LVM ベースの仮想ディスクを用いた Xen の DomU のホットバックアップが可能です注2。私は使っていないので試していませんが、KVM の仮想マシンについても同等のことが可能だと思われます。LVM ベースではない VM についても、スナップショット機構によってはホットバックアップが可能だと思われます。

 blockdiff を使った LVM ボリュームのバックアップ方法については Kazuho@Cybozu Labs: リモートからXenのDomUとかLVMやファイルを差分バックアップするスクリプトを書いた で触れたので、ここでは MySQL データベースをホットバックアップする場合の設定を紹介したいと思います。前提として、MySQL のデータが LVM ボリューム上に保存されている必要があります。

 バックアップスクリプトは、以下のような感じになります。

#! /bin/bash

export BLOCKSIZE=16834
export LVCREATE_PREFIX="mysqllock --host=db-host --user=root --password=XXX"
export YEARMONTH=`date '+%Y%m'`

blockdiff_backup /var/backup/db-backup-$YEARMONTH bin/ssh_lvm_dump --gzip \
  root@db-host /dev/mapper/logical_volume_of_mysql

このバックアップスクリプトは以下のことを行っています。

  • バックアップのブロックサイズを InnoDB のブロックサイズである 16KB に設定
  • スナップショットを取る瞬間に mysqllock コマンドを使って FLUSH TABLES WITH READ LOCK を使うことで、MyISAM テーブル等の一貫性のあるバックアップを実現
  • 1ヶ月毎にフルバックアップ。それ以内は増分バックアップ

 バックアップスクリプトを実行すると、最初に db-backup-YYYYMM.1.gz, db-backup-YYYYMM.1.md5, db_backup-YYYYMM.ver という3つのファイルが作成されます。YYYYMM.1.gz がバックアップデータ、YYYYMM1.md5 がブロック単位のチェックサム情報です。次に実行すると YYYYMM.2.* が、その次は YYYYMM.3.* が、という形で増分バックアップが増えていきます。最新のバックアップの番号は .ver ファイルが記憶しているので、バックアップが途中で失敗しても問題ありません。次回バックアップ時に壊れたバックアップファイルが上書きされます。

% ls -l db-backup-201001*
-rw-r--r-- 1 backup backup 50289166539 2010-01-01 05:35 db-backup-201001.1.gz
-rw-r--r-- 1 backup backup   131072004 2010-01-01 05:35 db-backup-201001.1.md5
-rw-r--r-- 1 backup backup 10914423057 2010-01-02 04:32 db-backup-201001.2.gz
-rw-r--r-- 1 backup backup   131072004 2010-01-02 04:32 db-backup-201001.2.md5
-rw-r--r-- 1 backup backup 13648250036 2010-01-03 04:33 db-backup-201001.3.gz
-rw-r--r-- 1 backup backup   131072004 2010-01-03 04:34 db-backup-201001.3.md5
...
-rw-r--r-- 1 backup backup           3 2010-01-18 04:34 db-backup-201001.ver

 バックアップスクリプトが正しく動作することを確認したら、crontab を設定して毎日バックアップを取るようにします。

0 4 * * * setlock -nX /var/backup/backup.lock cronlog -l /var/backup/backup.log -t -- /var/backup/backup.sh 2>&1

 ここでは、daemontools の setlock コマンドを使って万が一の重複起動を抑止しています。また、kaztools の cronlog コマンドを使うことで、バックアップの進捗をログに保存しつつ、バックアップが失敗した場合は cron 経由でアラートメールが送信されるようになっています。

 ちなみに、バックアップのログ (backup.log) は、毎日次のような感じで追記されていきます。データベースの停止期間 (アプリケーションからのクエリがロックされる期間) は 03:35:56 から 03:36:00 までの4秒間であり、実質無停止でのバックアップができていることがわかります。

------------------------------------------------------------------------------
[Sat Jan  9 03:05:02 2010] backup-srv starting: /var/backup/backup.sh
[Sat Jan  9 03:05:02 2010] creating snapshot...
[Sat Jan  9 03:05:07 2010]   Logical volume "lvm_dump" created
[Sat Jan  9 03:05:07 2010] running: bin/ssh_blockdiff_dump --gzip "root@db-host" "/dev/db/lvm_dump"...
[Sat Jan  9 03:19:22 2010] removing snapshot /dev/db/lvm_dump...
[Sat Jan  9 03:19:23 2010]   Logical volume "lvm_dump" successfully removed
[Sat Jan  9 03:19:23 2010] backup completed successfully
[Sat Jan  9 03:19:23 2010] creating snapshot...
[Sat Jan  9 03:35:56 2010] issuing lock statement: FLUSH TABLES WITH READ LOCK
[Sat Jan  9 03:36:00 2010]   Logical volume "lvm_dump" created
[Sat Jan  9 03:36:00 2010] issuing unlock statement: UNLOCK TABLES
[Sat Jan  9 03:36:00 2010] running: bin/ssh_blockdiff_dump --gzip "root@db-host" "/dev/x25m.1/lvm_dump"...
[Sat Jan  9 04:18:44 2010] removing snapshot /dev/x25m.1/lvm_dump...
[Sat Jan  9 04:18:46 2010]   Logical volume "lvm_dump" successfully removed
[Sat Jan  9 04:18:46 2010] backup completed successfully
[Sat Jan  9 04:18:46 2010] command exited with code:0

 このように、blockdiff を使うことで VM 単位のバックアップだけでなく、データベースのホットバックアップも簡単に取ることができます。パストラックでは、これらバックアップスクリプトに処理を追加し、バックアップ中は統計処理を抑止するといったことも行っています。

 blockdiff や kaztools は、いずれも私の github リポジトリ (github.com/kazuho) からダウンロードして perl Makefile.PL && make all test && make install でインストール可能です。あるいは nopan がインストールされていれば、nopan http://github.com/kazuho/blockdiff http://github.com/kazuho/kaztools と1コマンドでインストールすることができます。いずれもパストラックで実際に運用しているツール群ですが、興味のある方は (at your own risk で) お試しあれ。

注1: バックアップを取るサーバに Perl 5.8 以降がインストールされている必要があります。また、公開鍵認証を用いて SSH ログインできる必要があります
注2: XenServer のスナップショット機能とは併用できません

January 14, 2010

crontab を使って効率的にサービス監視する方法

監視とは継続的なテストである、という話 (もしくは cronlog とテストスクリプトを組み合わせた監視手法について)に続きます

 今日ようやく、積ん読状態だった「Software Design 2010年1月号」を手に取ったのですが、特集が「今日から使えるスクリプト満載! [プロ直伝]お手軽サーバ監視術」。興味深く拝読したのですが、もっと楽ができるのにと思うところも。ちょうど、昨年末に運用しているサービス「パストラック」のサーバを移転し、crontab と perl で書かれたスクリプト群を使った監視環境を構築したところなので、そこで使っているスクリプト cronlog を紹介したいと思います。

 特集の前書きにも書かれていることですが、サーバやネットワーク機器が多数ある環境なら、Nagios を始めとする、専ら監視のために作られたソフトウェアを使って、監視システムを構築すべきです。逆に小規模な環境で crontab やスクリプトを監視に使うメリットがあるとしたら、それは、極力覚えることや必要な設定を減らせる、という点でしょう。

 監視項目毎にスクリプトを書くなんて面倒でやってられません。そこで cronlog! cronlog を使うことで、既存のコマンドを crontab を使った監視系に早変わりさせることができます。

 cronlog は、引数として与えられたコマンドを起動し、その終了コードが非ゼロであった場合に、コマンドの出力結果 (stdout と stderr) を標準出力にプリントするコマンドです。何を言っているか良くわからないかもしれませんが、cronlog を使うと、たとえば ping によるサーバ監視は crontab に以下のように書くことで可能となります。

MAILTO=alert@example.com
5 * * * * cronlog -- ping -n 5 my-server 2>&1

 この crontab を解説すると、次のようになります。ping コマンドの終了コードは、応答が返ってきた場合 0、返ってこない場合に 1 です。cronlog は終了コードが非ゼロの場合のみ、実行されたコマンド (ここでは ping) の出力結果を出力、つまり ping がエラーを返した場合にのみ、その内容を cron に受け渡すことになります。cron は何か出力を受け取ると MAILTO に指定されたアドレスに、その内容をメールとして送信します。以上をまとめると、ping 応答が返ってこなかった場合にエラーメッセージを送信するという処理が、上の1行で実現できていることが分かると思います。

 これなら、監視項目ごとにスクリプトを書かなくてもいいので簡単です。

 ping のかわりに HTTP の監視をしたいのなら、

5 * * * * cronlog -- wget -O - http://my-server 2>&1

と書けば OK です。あるいは、ping 監視に成功した場合のみ HTTP 監視を行う、というようなマニアックな処理も、

5 * * * * cronlog -- ping -n 5 my-server 2>&1 && cronlog -- wget -O - http://my-server 2>&1

とすることで実現できます。監視項目が大量にあったり、ワンライナーで書きたくない場合は、スクリプトを書いて、それを cronlog で呼び出せばいいのです。注意すべきは唯一、障害を検知した場合は、非ゼロの値で exit すること。このへんも小技があるのですが、それはさておき。

 cronlog には、ログをファイルに保存したり、ログの各行にタイムスタンプを挿入するオプションもあります (というか、cronで実行するタスクのログを保存しつつ、タスクがエラーを返したらメールを送信する、というのが本来の機能です。監視=副作用のないタスク、なだけ)。詳しくは man cronlog をご覧ください。

PS. cronlog はオレオレ監視スクリプト群 kaztools の一部です。インストール方法は github.com/kazuho/kaztools からダウンロードしてきて perl Makefile.PL && make && make test && make install。興味のある方はどうぞ。

December 15, 2009

[ANN] CGI::Application::Emulate::PSGI

From: Kazuho Oku
To: cgiapp-ml

Hi,

I have just uploaded CGI::Application::Emulate::PSGI onto CPAN.

It's a yet-another adapter for running CGI::Application on top of PSGI. The differences from the older one: CGI::App::PSGI is that CGI::App::Emulate::PSGI is more friendly to existing code in two aspects. From the POD,

uses CGI.pm instead of CGI::PSGI
CGI::Application::PSGI (that uses CGI::PSGI) does not support programs calling CGI.pm in func-style (like CGI::virtual_host()). CGI::Application::Emulate::PSGI sets up environment variables so that codes using CGI.pm will work.
compatible with CGI::Application::Dispatch
The interface of CGI::Application::Emulate::PSGI is different from CGI::Application::PSGI, and is compatible with CGI::Application::Dispatch.

Thanks to MARKSTOS and miyagawa for answering my question (https://rt.cpan.org/Public/Bug/Display.html?id=52756), I agree that the ideal way would be to run CGI::App::Dispatch would be to make it somehow run on CGI::Application::PSGI (since it would be cleaner in the fact that it doesn't modify %ENV for each HTTP request), CGI::App::Emulate::PSGI would be a good solution IMO for running existing CGI::App-based applications on top of PSGI.

December 09, 2009

Hot-deploying Plack applications using Server::Starter

It's time to eat my own dog food.

During YAPC::Asia 2009, I wrote a tiny module called Server::Starter, a superdaemon for hot-deploying TCP servers (see Writing Hot-deployable servers (introduction of Server::Starter) for the benefits of using the module).  On the other hand, I have been involved in developing the http servers in Plack since then.

And time has come.  Having decided to use the two in my new web application server, I wrote a module to glue them together, Kazuho Oku / Plack-Server-Standalone-Prefork-Server-Starter - search.cpan.org.

Although the name is pretty long, it is easy to use :-p

// start server
# start_server --port=80 -- plackup -s Standalone::Prefork::Server::Starter myapp.psgi

// or use setuidgid to drop privileges
# start_server --port=80 -- setuidgid www plackup -s Standalone::Prefork::Server::Starter myapp.psgi

// graceful update
# killall -HUP start_server

Or use svc -h if you are going to manage Server::Starter using daemontools.

December 01, 2009

高度に進化した分散データストアは RDBMS と見分けがつかない? (shibuya.pm #12 スライド)

 昨日開催された shibuya.pm #12 - NoSQL特集で使用したスライドを slideshare にアップロードしました。

 開発しているシャーディングミドルウェアである Incline と Pacific については YAPC::Asia 2009 を始めいろいろな所で話をする機会をいただいてきたので、今回は、なぜ RDBMS ベースのアプローチを採用したのかという背景を中心に説明させていただきました。概念的な話が多くて分かりにくかったと思います(すみません)が、細かな点についてはパフォーマンスとスケーラビリティのためのデータベースアーキテクチャ (BPStudy#25発表資料)を参照いただければと思います。

 また、中で出てきた「実体化ビュー」については、Materialized view - Wikipedia, the free encyclopediaが良くまとまっているかと思います。Incline は一言でいうと、RDBで構成されるshard群の上で read-only かつ eventually consistent な materialized view を実現するためのツールです。

November 17, 2009

リモートからXenのDomUとかLVMやファイルを差分バックアップするスクリプトを書いた

kazuho's blockdiff at master - GitHub

 月曜から XenServer の運用を始めたんですが、以下のような要件のバックアップツールがほしくなりまして。ちょっと調べた範囲で見つからなかったので、書いてみました。

  • スナップショットによる無停止でのバックアップ
  • 差分/多世代バックアップが可能
  • ネットワーク越しにプルベースでバックアップが可能

 元々は去年書いたバイナリファイルの差分バックアップスクリプト (データベースの差分バックアップとウェブサービスのお引っ越し)。これを拡張して、LVMを操作したり、バージョン番号を自動採番するようにしたり、ssh経由での転送機能をつけたりした感じです (ディスクは遅いからギガイーサなら over ssh でも問題ない)。

 詳しいことを知りたい人にはソースコードを読んでいただくとして (といっても300行ちょい)、たとえば XenServer 上の DomU のバックアップを取るなら、サーバ上で xe vm-disk-list コマンドを実行して論理ボリューム名を取得したあと、バックアップ用マシンの crontab にこんな感じで設定します。Xen以外の一般的なLVMのバックアップを取ることもできます。

0 3 * * * blockdiff_backup /var/xenbackup/vm01-`date '+%Y%m'` ssh_lvm_dump -z root@xenserver /dev/VG_XenStorage-... 2>&1

 これで、毎月1日にフルバックアップ、他の日は差分バックアップがとれる。リストアは、こんな感じ。

# for i in vm-01-200911.*.gz ; do \
>   gunzip < $i | blockdiff_merge /dev/VG_XenStorage-...
> done

 あとは、差分バックアップ時にディスクを全スキャンしなくてもよくなれば、もっとバックアップ頻度を上げられていいのにな、と思っています。

11/19追記: XenServerで使う場合の制限と詳細については、下記URLをご参照ください。

October 08, 2009

Cppref: reading cppreference.com docs offline, like man or info or perldoc

Today I created a tiny script called cppref, a wrapper for documents on cppreference.com.  Blurbs are:

  • docs are bundled with the interface, no network access required
  • works like man(1) or info(2) or perldoc(2)

It looks like follows.

$ cppref
You are here: C++ Reference

C++ Reference

                                  C++

General Topics                      * C++ Strings
                                    * C++ I/O
  * FAQ                                 + C++ String Streams
  * Pre-processor commands          * C++ Exceptions
  * Operator Precedence
  * Escape Sequences              C++ Standard Template Library (STL)
  * ASCII Chart
  * Data Types                      * Overview
  * Keywords                        * Iterators
                                    * C++ Algorithms
Standard C Library                  * C++ Vectors
                                    * C++ Double-Ended Queues
  * Overview                        * C++ Lists
  * Standard C I/O                  * C++ Priority Queues
  * Standard C String & Character   * C++ Queues
  * Standard C Math                 * C++ Stacks
  * Standard C Time & Date          * C++ Sets
  * Standard C Memory               * C++ Multisets
  * Other standard C functions      * C++ Maps
                                    * C++ Multimaps
                                    * C++ Bitsets

$ cppref vector
You are here: C++ Reference >> C++ Standard Template Library >> C++ Vectors

C++ Vectors

Vectors contain contiguous elements stored as an array.

Accessing members of a vector can be done in constant time, appending elements
to a vector can be done in amortized constant time, whereas locating a specific
value or inserting elements into the vector takes linear time.

Constructors create vectors and initialize them with some data
Operators    compare, assign, and access elements of a vector
assign       assign elements to a vector
at           returns an element at a specific location
back         returns a reference to last element of a vector
(snip)

Or if the specified term maps to multiple files,

$ cppref push_back        
multiple choices:
  stl::deque::push_back
  stl::list::push_back
  stl::vector::push_back
  string::push_back

By default, cppref uses w3m as its viewer, so you can follow the links to read the documents.

Cppref is available from search.cpan.org/dist/cppref or github.com/kazuho/cppref.  Hove fun!

October 07, 2009

Uploading an autotools-based distribution onto CPAN

Background

It is a pain to create binary packages.  But installing a program from source tarball is a tedious task.  You need to run ./configure & make && make install.  Sometimes you need to resolve the dependencies by hand as well.  That's where source-code-based package distribution systems come in, and the largest system is, IMHO, CPAN.  If you could upload a autotools-based distribution onto CPAN, then the users of the software can install them with the cpan command (or cpanp or cpanf or whatever), with the dependencies automatically resolved.

And for my case, it was considered especially benefitial, since the program I am now working on (it's called incline, a replicator for RDB shards using MySQL or PostgreSQL), uses perl scripts for running tests.  By distributing incline through CPAN, the perl modules required for running the test suite could be installed automatically.  Besides that, the expected users of the software outside my company are mostly perl users.

So I adjusted the files of incline so that it can be uploaded to CPAN (it continues to work as a normal autotools-based distribution as well).  The steps were as follows (for a working example, please see incline - search.cpan.org).

Step 1. write META.yml generator

CPAN packages should contain a file call META.yml which describes the metainformation of the package like dependencies, etc.  Since it would be a good idea to fill-in some fields of the file by reading other files of the distribution, I wrote a script file (META.yml.pl) that generates META.yml.  As you can see, it is pretty straightforward.  One point to note is that it has a --fix-makefile option that inserts dependency information into Makefile, which is mandatory for supporting old versions of the CPAN installer.

META.yml.pl
#! /usr/bin/perl

use strict;
use warnings;

use YAML;

my $meta = {
    name               => 'incline',
    abstract           => 'a replicator for RDB shards',
    version            => do {
        my $s = `echo VERSION | cpp -include src/incline_config.h`;
        $s =~ s/^.*\n\"([0-9_\.]+)\".*?$/$1/s
            or die "failed to obtain version number";
        $1;
    },
    author             => do {
        open my $fh, '<', 'AUTHORS'
            or die "failed to open AUTHORS:$!";
        my @authors = map {
            chomp $_;
            $_;
        } <$fh>;
        close $fh;
        \@authors;
    },
    license            => 'bsd',
    distribution_type  => 'script',
    dynamic_config     => 0,
    configure_requires => {
        perl          => 5.008,
        YAML          => 0,
    },
    requires           => {},
    build_requires     => {
        DBI                => 0,
        'List::MoreUtils'  => 0,
        'Scope::Guard'     => 0,
        'Test::mysqld'     => 0,
        'Test::postgresql' => 0,
    },
    resources          => {
        license => 'http://www.opensource.org/licenses/bsd-license.php',
    },
    no_index           => {
        directory => [ qw/example src t/ ],
        file      => [ qw/README.html/ ],
    },
    'meta-spec'        => {
        version => 1.4,
        url => 'http://module-build.sourceforge.net/META-spec-v1.4.html',
    },
    generated_by       => 'META.yml.pl',
};

if (@ARGV && $ARGV[0] eq '--fix-makefile') {

    my $prereq_expr = do {
        my %req = (
            %{$meta->{requires}},
            %{$meta->{build_requires}},
        );
        join ", ", map { "$_=>q[$req{$_}]" } sort keys %req;
    };
    print <<"EOT";
# MakeMaker Parameters:

#  PREREQ_PM => { $prereq_expr }

# --- MakeMaker post_initialize section:

EOT
    while (my $l = <STDIN>) {
        print $l;
    }

} else {

    print Dump($meta);

}

Step 2. call META.yml.pl from configure

As explained, older versions of the CPAN installer requires the dependencies to be written in Makefile (this is still the case for newer versions if your configuration script dynamically determines the dependencies).  In order to meet the requirement, I added a call to META.yml.pl at the end of my configure.ac.

configure.ac
AC_OUTPUT(Makefile src/Makefile)

cp Makefile Makefile.orig && perl META.yml.pl --fix-makefile < Makefile.orig > Makefile
if test "$?" -ne "0"
then
AC_MSG_ERROR([failed to add metainfo to Makefile])
fi

Step 3. adjust Makefile

There are a couple of things to adjust in the Maefile.  First, make test should be supported.  In my case, I already had one, that executes test scripts written in perl using Test::Harness.  The next thing is to include more than one POD (plain old document).  I already had one as well, since I generate man pages and readmes from POD.  The last thing is to generate and include two files in the distribution, META.yml and MANIFEST.  The Makefile uses a dependency and dist-hook to generate the files.  The files adde should be included in the EXTRA_DIST section of Makefile.am, of course.

Makefile.am
META.yml: META.yml.pl
perl $< > $@

dist-hook:
(cd $(distdir) && find * -type f) > $(distdir)/MANIFEST

test:
perl -MTest::Harness -we 'runtests(@ARGV)' t/*.t

Step 4. write Makefile.PL

Create Makefile.PL that calls ./configure.  Do not write any configuration logic in Makefile.PL, all logic should go into configure.ac so that the distribution could be built by using either Makefile.PL or configure.

Makefile.PL
exec './configure', @ARGV;
die "failed to execute configure:$!";

Conclusion

As described, it is not (so) difficult to add CPAN compatibility to existing autotools-based distributions, or to create a new distribution that works as both.  But to tell the truth it was not as easy as it seems to figure out the right way.  It would have been impossible without help from charbar, migayawa, tokuhirom, and walf443.  I would like to thank them all.

September 11, 2009

Writing Hot-deployable servers (introduction of Server::Starter)

Yesterday at YAPC::Asia 2009, I did a LT introducing verious techniques to write hot-deployable servers, and introduced a perl module called Server::Starter that encapsulates the burden of developing support for hot-deployment within each TCP server program. The presentation slides are on Slideshare.

August 18, 2009

Perl のテスト用に PostgreSQL のインスタンスを自動で構築するモジュール Test::postgresql を書いた

 以前作成した Test::mysqld (参照:Perl のテスト用に MySQL 環境を自動で構築するモジュール Test::mysqld を書いた) の姉妹版、Test::postgresql を書きました。このモジュールを使えば、テストコード内で一時的にテスト専用の PostgreSQL のインスタンスを構築し、テストを実行することができます。構築されたインスタンスは、テスト終了時に自動的に削除されます。

use DBI;
use Test::postgresql;
use Test::More;

my $pgsql = Test::postgresql->new(
    initdb_args
        => $Test::postgresql::Defaults{initdb_args} . ' --encoding=utf8',
) or plan skip_all => $Test::postgresql::errstr;

plan tests => 10;

my $dbh = DBI->connect(
    "DBI:Pg:...;port=" . $pgsql->port
);

... 以下、テストコード ...

お使いになりたい方は、CPAN か coderepos からどうぞ。

August 07, 2009

YAPC::Asia 2009 で「スケールするウェブアプリケーションを20分で作る方法」について話します

 このところ、MySQL と Perl 関連のエントリをいろいろ書いていますが、それは、スケールアウト可能で、かつ、管理が容易なウェブアプリケーションを、簡単に書けるようにしたい、という理由があるからです。

 ただ、ブログエントリだとどうしても細切れになるので、一連のモジュールやプログラムを組み合わせて、どうやってスケールするウェブアプリケーションを作るのかという話を YAPC::Asia 2009 でさせていただくことにしました。

 YAPC::Asia 2009 は9月10日(木)と11日(金)の2日間、東京工業大学大岡山キャンパスで開催されます。今日からチケット販売も始まったので、興味のある方はお越しいただければ、と思います。

August 04, 2009

Perl のテスト用に MySQL 環境を自動で構築するモジュール Test::mysqld を書いた

 ORM やウェブアプリケーション関連のライブラリなどのテストケースを書くにあたっては、 RDBMS へのアクセスが必要になります。しかし、SQLite のようなスタンドアローンのデータベースと比較すると、サーバ型データベースである MySQL に接続してテストを書くのは、既存の MySQL の権限設定やデータベース名を気にする必要があったりと、いろいろ不便です。そこで、MySQL のインスタンスをテンポラリディレクトリに自動生成し、テストが終わったら削除してくれる Perl モジュール Test::mysqld を書きました。こんな感じで使います。

use DBI;
use Test::mysqld;
use Test::More;

my $mysqld = Test::mysqld->new(
    my_cnf => { 'skip-networking' => '' }, # TCP接続を使わない
) or plan skip_all => $Test::mysqld::errstr;

plan tests => 10;

my $dbh = DBI->connect(
    "DBI:mysql:$db_name;user=root;mysql_socket=" . $mysqld->my_cnf->{socket},
);

... 以下、テストコード ...

 テスト専用の mysqld サーバが立ち上がるので、既存のデータを壊したりすることもありません。データベースを作ったり消したりも自由自在。また、既存の mysqld の設定に引きずられることもないので、mysqld の権限設定を気にする必要もありません。テストが終了すると、作成された mysqld インスタンスは自動的に消去されます。

 結論。Test::mysqld を使えば MySQL にアクセスするテストが SQLite なみに簡単に書けるよ、きっと。ということで、お使いになりたい方は、CPAN か coderepos からどうぞ。

2009/8/5追記: Test-mysqld-0.04 で mysql 関連の必要なプログラムが見つからなかった場合の処理を整理したのにあわせ、サンプルをアップデートしました。