[perl] Perl徹底攻略という本が出ました

本日、7月23日発売です。

Perl徹底攻略 (WEB+DB PRESS plus)

Perl徹底攻略 (WEB+DB PRESS plus)

昨年10月に発売された Web+DB Press vol.72 に寄稿した「Webアプリケーションのパフォーマンス改善」が収録されています。

内容はほぼ初出時のままです。再録にあたってコード部分の変更はなしでいけると思い込んでいてそのまま校了してしまった後に、Devel::KYTProf に執筆当時のバージョンと非互換な変更が入っているのを見つけてしまい、あわてて互換性を確保するpull reqを送って 発売までにリリースされて助かった、という裏話があったりなかったり。

PerlWebサービスを作るために必要な基礎から運用、娯楽まで幅広くカバーしている本ですので、是非お手にとっていただければと思います。

nginxでメソッドごとにリクエスト数制限を掛けたい

アプリケーションでどうしても捌けない量のリクエストが一時的に押し寄せてしまう場合、アプリケーションサーバが死ぬのを避けるために GET は制限を掛けたいが、POST はリトライが面倒なのでなるべく通してあげたい、というような要求を nginx で処理できるかどうか。

実装として一番望ましいのは

  • GET は 100 req/sec で制限 (超えたら503)
  • POST は無制限

のようにメソッドごとに別々の制限を掛けることだったのですが、とりあえず HttpLimitReqModule を使うことで、メソッドごとに同一の上限を設定することはできました。

http {
    limit_req_zone $request_method zone=method:1m rate=100r/s;
    server {
        listen 80;
        location / {
            limit_req zone=method;
            proxy_pass http://127.0.0.1:5000/;
        }
    }
}

これで $request_method をキーにして、100req/sec を超えた分を 503 にします。

この設定では GET も POST も同じ上限になりますが、たとえば仮に

  • リクエストの比率が GET : POST = 9 : 1
  • アプリケーションが処理できる上限が 150req/sec
  • そこに 300req/sec が到達する

という場合を考えると GET=270, POST=30 req/sec になるので、

  • GET は 100 req/sec を超えた分 (170 req/sec) が 503
  • POSTは 30 req/sec なのですべてアプリケーションの処理に回る
  • アプリケーションに到達するリクエストは合計 100 + 30 = 130 req/sec

となって、POSTを全部通しつつ限界を超えないような制限ができます。

chef-soloがcookbookから実行するscriptの無限ループで大量にメモリを食って死んだ件

とあるホストで初期設定をしようと思って chef-solo を実行していたところ、メモリを全部食い尽くして chef-solo (11.4.4) が死亡するという事案が発生。

追ってみたところ、どうやら原因はこんなかんじ。

  • cookbook から shell script を実行していて、その中で perl Makefile.PL && make && make install していた
  • CPAN.pm が初期設定を終えていない場合、対話モードに入る
  • 対話モードで標準入力が閉じられていると途中まではデフォルトの入力で進むが、地域を選択するところはデフォルトがないためここでメッセージを表示しながら無限ループする
  • 無限ループで大量に出力されたメッセージを、(詳細は確認していないですが) chef がメモリに乗せ続けて太る

ということで、cookbook 内で実行する script には注意しましょう。というか chef 自体そのあたりの防御機構を持っていた方がいい気もしますね。

# echo -n "" | perl Makefile.PL
*** Module::AutoInstall version 1.03
*** Checking for Perl dependencies...
We have to reconfigure CPAN.pm due to following uninitialized parameters:

cpan_home, keep_source_where, build_dir, build_cache, scan_cache, index_expire, gzip, tar, unzip, make, pager, makepl_arg, make_arg, make_install_arg, urllist, inhibit_startup_message, ftp_proxy, http_proxy, no_proxy, prerequisites_policy, cache_metadata

/usr/lib/perl5/5.8.8/CPAN/Config.pm initialized.

(略)

I'd use that as a database of CPAN sites. If that is OK for you,
please answer 'y', but if you want me to get a new database now,
please answer 'n' to the following question.

Shall I use the local database in /root/.cpan/sources/MIRRORED.BY? [y] y

(ここまではデフォルトがあるので進む)

Now we need to know where your favorite CPAN sites are located. Push
a few sites onto the array (just in case the first on the array won't
work). If you are mirroring CPAN to your local workstation, specify a
file: URL.

First, pick a nearby continent and country (you can pick several of
each, separated by spaces, or none if you just want to keep your
existing selections). Then, you will be presented with a list of URLs
of CPAN mirrors in the countries you selected, along with previously
selected URLs. Select some of those URLs, or just keep the old list.
Finally, you will be prompted for any extra URLs -- file:, ftp:, or
http: -- that host a CPAN mirror.

(1) Africa
(2) Asia
(3) Europe
(4) North America
(5) Oceania
(6) South America
Select your continent (or several nearby continents) []
Sorry! since you don't have any existing picks, you must make a
geographic selection.

(1) Africa
(2) Asia
(3) Europe

(以下無限ループ)

Provisioning Frameworks Casual Talks vol.1 で「新卒研修でserverspecとChefを使った話」を発表しました

主催の @studio3104 さん、登壇された方々、参加者の皆さんありがとうございました。
Provisioning Frameworks Casual Talks vol.1 にて、カヤックの今年の新卒研修でserverspecとChefを使った話、をしてきました。

スライドはこちら

このblogには書いていなかったので、2013年のカヤック技術部新卒研修について、参考資料のリンクも張っておきます。

Chefの実行結果に対するテストは書かないとなあ、とずっと思っていながら結局あまり有効な手を付けられていない状態だったのですが、ちょうどいいタイミングで serverspec が出てきたので試しがてら研修で取り入れてみたという状態です。

プロダクション環境のテストはこれから随時書いていきますが、なにしろ対象が大量に(台数的な意味ではなく cookbook のバリエーション的な意味で……)あるのでまず cookbook に変更を加えるところから順番ですかね。

先着順参加方式について

MySQL casual vol4 の時も今回も、人数がちょうどよく埋まっているのであまり不満は聞かれないようですが、定員に達した場合の受付締切のアナウンスは(どこでアナウンスするのかも含めて)しっかり告知する必要があるな、と思いました。

当日登録のatndを併用するのは、先に確保してしまって忘れてしまうような人をださない手としてよさそうですね。

MySQLでデータ領域をシステムと別diskにするならtmpdirも設定した方がいい

某所に300ホスト以上を2年ほど監視していたZabbixのMySQLがありまして、データが100GBぐらいになってメモリ8GBのホストではdisk IOが辛くなってきたので、移行することにしました。普段はそんなにでもないのですが、housekeeperが動作して古いデータを消しに行くとバッファプールに乗っていない部分に読みに行って重いのです。

この際折角なので Intel S3700 (サーバ用のSSD) をおごり、

  • Zabbix-1.8 から 2.0 にアップグレード
  • MySQL-5.0.77 から MySQL-5.6.11 に変更
  • システムは HDD で /dev/sda1
  • データは SSD で /dev/sdb1 を /data にマウント

という構成で移行の検証を行っていたところ…

MySQLのバージョンが大きく上がるので mysqldump を取得して restore 後、patch.sql (1.8→2.0 の migration SQL) を実行中に、妙に HDD への書き込みが多くてボトルネックになっている雰囲気。ALTERの一時ファイルは確かデータファイルと同じ所にできた記憶なんですが…

lsof で mysqld が開いているファイルを見ても、/data 以外には /tmp しかない。
ということで、Percona-Toolkit の pt-ioprofile を教えていただいたので試してみました。

日々の覚書: pt-ioprofileでMySQLのテンポラリテーブルのサイズを測る

結果。ばっちり /tmp を使っていました。

$ sudo pt-ioprofile --cell sizes --run-time 60
2013年  4月 24日 水曜日 11:06:23 JST
Tracing process ID 22049
     total      pread     pwrite filename
1309671424          0 1309671424 /tmp/ibQo90KP
1308622848 1308622848          0 /tmp/ibGwKRu3

/data/tmp を作成し、my.cnf で tmpdir を指定することで、MySQLにそちらを使用させるよう変更し ALTER 再実行したところ、無事 SSDのほうを使ってくれるようになりました。

# mkdir /data/tmp
# chmod 1777 /data/tmp
[mysqld]
tmpdir = /data/tmp

/tmp が十分に高速な場合はデータと別にしておくことでパフォーマンス向上が見込める可能性はあると思いますが、今回は /tmp が低速だったので SSD 上に置いてしまうほうがいいですね。

Twitterで教えていただいた @yoku0825 さん、 @kazuho さんありがとうございました。

[perl] Perlで同じコードを違うバージョンのモジュールでベンチマークする

複数バージョンのモジュールで同じコードを実行してBenchmark.pmで計測したい、というケースにこう書けばいいかな、という例。

普通にBenchmark.pmを使ってしまうと同一モジュールを違うバージョンで複数読み込むことができないため、

  • バージョンごとに子プロセスを fork
  • 子プロセスで use lib して @INC を追加してから use
  • 計測した結果をファイルに保存して親プロセスで集約、表示

という流れで書きます。

use strict;
use Benchmark qw/ :all /;
use File::Temp qw/ tempfile /;
use Storable qw/ nstore retrieve /;

# 計測したいコード
my $code = sub {
    my $c = Cache::Redis->new;
    for ( 1 .. 10 ) {
        $c->set( $_ => $_ );
        $c->get($_);
    }
};

my $result = {};
for my $name (qw/ extlib-0.01 extlib-0.02 /) {
    my ($fh, $fn) = tempfile();
    my $pid = fork;
    if ($pid) {
        close $fh;
        wait;   # 子プロセスの終了を待つ
    }
    else {
        eval qq{use lib '$name/lib/perl5'}; # 読み込み先追加
        eval qq{use Cache::Redis};          # use する
        my $r = timethis(1000, $code);      # 計測して
        nstore $r, $fn;                     # ファイルに保存
        exit;                               # 子プロセスは終了
    }
    $result->{$name} = retrieve $fn;        # 親で読み込んで集約
};

cmpthese $result; # 結果表示

あとは cpanm で、git のタグごとに別の場所に install して計測できます。

$ cpanm -n -l extlib-0.01 git://github.com/Songmu/p5-Cache-Redis.git@0.01
$ cpanm -n -l extlib-0.02 git://github.com/Songmu/p5-Cache-Redis.git@0.02
$ perl bench.pl

timethis 1000:  3 wallclock secs ( 1.49 usr +  0.53 sys =  2.02 CPU) @ 495.05/s (n=1000)
timethis 1000:  3 wallclock secs ( 1.61 usr +  0.56 sys =  2.17 CPU) @ 460.83/s (n=1000)
             Rate extlib-0.02 extlib-0.01
extlib-0.02 461/s          --         -7%
extlib-0.01 495/s          7%          --

このような働きをモジュールにして切り出したら嬉しいですかね…?

[2013-12-12 追記]

モジュール化してほしいとリクエストされたので念のため検索してみたら、Benchmark::Forking というモジュールを発見。これを使うと、上記のコードは以下のように書けます。すっきり。

use strict;
use Benchmark::Forking qw/ :all /;
 
my $code = sub {
    my $c = Cache::Redis->new;
    for ( 1 .. 10 ) {
        $c->set( $_ => $_ );
        $c->get($_);
    }
};
 
cmpthese(1000, {
    'extlib-0.02' => sub {
        unless ($INC{"Cache/Redis.pm"}) {
            eval qq{use lib 'extlib-0.02/lib/perl5'};
            eval qq{use Cache::Redis};
        }
        $code->();
    },
    'extlib-0.01' => sub {
        unless ($INC{"Cache/Redis.pm"}) {
            eval qq{use lib 'extlib-0.01/lib/perl5'};
            eval qq{use Cache::Redis};
        }
        $code->();
    },
});

monitoring casual talks #3 に参加してきました

2013年3月8日に開催された、monitoring casual talks #3に参加してきました。

会場をご提供いただきました paperboy&co. 様と皆様には大変お世話になりました。ありがとうございます。

自分の発表資料はこちらです。『いつもと違う』を検知したい

普通異常値検知というとなにやら難しい数学やアルゴリズムのお話になりそうなのですが、Zabbixが描画したグラフの画像自体の差分を Perl の画像処理モジュール Imager を使って求めて、差分の多寡をピクセル数で数値化したらいいんじゃない?というネタです。
(まだ実戦投入はしていないのですが、いけそうなので近々入れたいです)

会全体を通しての感想としては…

  • サーバ管理ツールは各社内製でいろいろやっていて戦国時代ぽい
    • 要件を一般化するよりは自社にあったものを作ってしまう方が早そうです
  • Zabi友 vs nagios (+ munin?) の明日はどっちだ
  • 時間あるから適当に好きなだけ喋ろう、はやっぱり無理
    • 終盤時間足りなくなるのが3回連続。みんな喋りたがりすぎ

次回はまさかの京都開催という噂もありますが、楽しみに待っています。

あとはモニカジを待たずとも、そのあたりの話で聞きたい & 喋りたいことがあったら適当に twitter で人を捕まえて渋谷あたりで飲みに行けばいいんじゃね、と思いました。