2012年6月アーカイブ

昨日でたNet::DNS::Lite-0.09の変更点に関する話。

Furlと組み合わせて使うと幸せになれる Net::DNS::Lite のtimeoutとretry回りは若干変わった実装になっていて、

use Net::DNS::Lite;
my $r = Net::DNS::Lite->new(
    server => [qw/8.8.8.8 8.8.4.4/]
);
$r->resolve("google.com", "a");

このように実行すると、

#サーバ一覧 [server,timeout]
my @retry = (
    ['8.8.8.8',2],
    ['8.8.4.4',2],
    ['8.8.8.8',5],
    ['8.8.4.4',5],  
    ['8.8.8.8',5],
    ['8.8.4.4',5]
);

2秒=>5秒=>5秒とタイムアウト時間が変化しつつ、上の順番でサーバ一覧が作られ、実行される。

これはどこで指定されているかというと、

sub new {
    my ($class, %arg) = @_;

    my $self = bless {
        server          => [],
        timeout         => [2, 5, 5],
        search          => [],
        ndots           => 1,
        reuse           => 300,
        %arg,
    }, $class;

このnewの中で指定されている。なぜ2秒なのか聞いてみたところ、

http://twitter.com/kazuho/status/215635164888436737

とのこと。まぁちょうどいいかなと言う気もする。

ところが、serverを指定せずに呼び出す(inet_atonを使う場合も)と事情が違う。

Net::DNS::Liteはserverが指定されていないと、/etc/resolv.conf を読みサーバ一覧を作る。

resolv.conf が

nameserver 192.168.0.1
nameserver 192.168.0.2

となっていれば当然、serverは [qw/192.168.0.1 192.168.0.2/] になる。

resolv.conf には optionでtimeoutやattempts(再試行回数)も書けて、

nameserver 192.168.0.1
option timeout:1 attempts:2

となるんだけど、Net::DNS::Liteの0.08まではattemptsがサポートされてなかったので、上記の場合、

servers => [qw/192.168.0.1/],
timeout => [1]

となり、サーバ一覧は

@retry = (
    ['192.168.0.1',1]
);

となって、1回しかDNSにアクセスしなくなってしまう(それはそれで良い場合もあるとは思う)

また、標準的なresolverではtimeoutとattemptsのデフォルトは 5と2なので

nameserver 192.168.0.1
option timeout:1

このresolv.confは

# new
servers => [qw/192.168.0.1/],
timeout => [1,1]
# 一覧
@retry = (
    ['192.168.0.1',1],
    ['192.168.0.1',1]
);

となって欲しいところ。

ということで、resolv.confにtimeoutもしくはattemptsがあるときだけ、timeoutを計算しなおすようにkazuhoさんにpullreqを送ってバージョンアップして頂きました。

初期のtimeoutが[2,5,5]なのが標準的な動作と違うのはどうなんだろうなぁと少し考えましたが、2秒というtimeoutが実践的っぽいし、バージョンアップで動作が変わるのもよくないと思い、変更しない方向になりました。

GrowthForecast のページができました。

GrowthForecast - Lightning fast Graphing / Visualization
http://kazeburo.github.com/GrowthForecast/

インストールの方法や基本的な使い方、APIの説明も載せてあります。ぜひ参考にしてください

site.png

これまでとインストールの方法が変わっていて、githubからgit cloneしてくる方式から cpanm コマンド一発でインストールができるようになっています。依存モジュールが多いのでどちらにしろ時間はかかるけど簡単にいれることができるようになっていると思います。

いやー、おもしろかった。主催の @kenjiskywalker さんはじめ皆様ありがとうございます。

Monitoring Casual Talk #1 : ATND
http://atnd.org/events/29621

みんな共通した悩みがあるんだなと改めて思いました。とくにアラートを受け取るための携帯の着信音。自分は「鳩時計」が苦手です。ぱぽーぱぽー。

あとはカクさんDisが多かったですね。個人的にもマウスメインで設定を行う運用ツールは使いたくないです。

自分の発表資料はslideshareに上げました。年始の「hb qp bp study 新年LT&ビアバッシュ2012」で少し喋ったDHWChainの話のアップデート版と、監視ツールを選ぶ際に思う事を放言しています。


アプリケーションエンジニアがvimやemacsについてあれこれ言うように、運用を行うエンジニアも主たる仕事道具である運用ツールについてあれこれ言っていいと思う。今回の勉強会はこれができてとても良かった。

資料の最後から2ページ目に「サービスの維持継続に責任を持つ僕らだからこそ、一刻を争う場面で使う仕事道具に対しても責任を持ちたい」と書いたけど、アプリケーションエンジニアにとってのエディタように僕らも監視ツールという仕事道具に対して愛着や誇り、責任をもってサービスの運用にあたって行けたらいいですね

FreeBSD・OpenBSDと他で ps -e の動作が違ってハマってしまいましたが、Proclet という簡単なsupervisor・プロセス管理モジュールをCPANにあげました。

Proclet - minimalistic Supervisor
https://metacpan.org/module/Proclet

GrowthForecastでは、growthforecast.pl というスクリプトを実行するだけでWebサーバと2つのWorkerが起動します。CloudForecastのように個別に実行が必要になると使うのも面倒ですよね。

growthforecast.pl を実行後、pstreeで見るとこうなってます。growthforecast.plがsupervisor的に動作し、終了したプロセスがあれば自動で起動し直します。

-+= 70330 kazeburo perl growthforecast.pl
 |--- 70332 kazeburo growthforecast.pl (GrowthForecast::Worker 1min) 
 |--- 70333 kazeburo growthforecast.pl (GrowthForecast::Worker) 
 \-+- 70334 kazeburo growthforecast.pl (GrowthForecast::Web) 
   |--- 70335 kazeburo growthforecast.pl (GrowthForecast::Web) 
   |--- 70336 kazeburo growthforecast.pl (GrowthForecast::Web) 
   |--- 70337 kazeburo growthforecast.pl (GrowthForecast::Web) 
   \--- 70338 kazeburo growthforecast.pl (GrowthForecast::Web)

GrowthForecastでこの動作を実現する為に、STFの実装を参考にし(パクリ)つつParallel::PreforkとPrallel::ScoreBoardを利用しています。


Proclet はgrowthforecast.plのようなsupervisorを実現するモジュールで、Parallel::PreforkのHook機構だけを使って実装されています。

use Proclet;

my $proclet = Proclet->new;

$proclet->service(
    code => sub {
        my $worker = MyWorker->new;
        $worker->run;
    },
    worker => 2
);

$proclet->service(
    code => sub {
        my $app = MyWeb->to_psgi;       
        my $loader = Plack::Loader->load(
            'Starlet',
            port => $port,
            host => $host || 0,
            max_workers => 4,
        );
        $loader->run($app);
    },
);

$proclet->run;

service メソッドで動かす物を登録していきます。codeに動かすcoderef、workerに起動するプロセス数を指定します。プロセス数のデフォルトは「1」。最後にrunメソッドを呼ぶと内部でループが回り、プロセス起動・監視します。非常にシンプルですね

GrowthForecastはこっちに切り替える予定です

DeNAさんの「Mobageを支える技術」を献本頂きました。nekokakさんありがとうございます。

今日発売です

この本は4部構成になっています

  • Part 1 ソーシャルゲーム開発技術
  • Part 2 ソーシャルゲーム運用技術
  • Part 3 ソーシャルゲーム効率化技術
  • Part 4 ソーシャルゲーム分析技術

最初はガラケーやスマホなどのどちらかというと、フロントよりの話があり、Part2と3で、インフラ・データベース・アプリケーションのチューニングなどの話題が出て、Part4ではデータマイニングが紹介されます。目次をみるだけでもDeNAを支える技術をできるだけ多く盛り込んだ本だということがわかります。その分、若干書評が書きにくく、万人にお勧めするのが難しい本だなと思いました。

DeNAがさばいている35億PV/dayというリクエスト数と同等レベルのアクセスを相手にしければならないエンジニアというは世の中にそれほぞ多くないはずです。今の私の仕事ではこの1/10にもなりません。DeNAが相手にしなければならない規模を想像するのはなかなか容易ではありません。35億PV/dayの世界について何かしら物語の導入として解説があってもよかったのではないでしょうか。

そこで次のような算数をしてみました。

35億PV/day というのは秒間にすると、4万PV/sec、ピークタイムは平均の倍として8万PV/secになる
アプリケーションサーバ1台あたり最大200req/sec処理できるとして、400台のサーバが必要
35億PV/day の世界では1万回に1回しか起きない事が1秒に8回起きる。100万回に1しか起きないことが1日に3500回起きる

数字が小さくなった分、イメージしやすくなったんじゃないでしょうか。こう改めて考えるとMobageを支えるエンジニアの方々のすごさがわかります。

この本の構成として、こういった前提がなく構成やTIPの話が始まってしまうのが全体的な詰め込んだ感に繋がってしまってるんじゃないかなぁと思いました。


各Part/章では、Part2の松信さんのMySQLの運用、Part3のxaicronの書いたアプリケーションのチューニングの章が興味深く読ませて頂きました。松信さんの6章、7章に関しては「Webエンジニアのための データベース技術[実践]入門 (Software Design plus) 」では若干物足りなかった実際のサービスでの事例があり、いつもながら参考になりました。また、xaicronの11章ではmemcachedの使い方などどこかで真似したいと思わせる内容で楽しめました。コラムの「アプリケーションorミドルウェアどちらで対応すべき?」は必読ですね。


大規模Webサービスに関わっているいる人、関わりたい人にはお勧めの本です。大量のトラフィックを捌く現場を想像しながら、なぜこのようなインフラやアプリケーションの構成になったのか、ツールができたのかを考えながら読むのがいいと思います。規模や環境に応じて開発や運用の方法は TMTOWTDI なのでこの本を消化しつつ日々のエンジニアリングに使えるところは使うのがいいんじゃないでしょうか。

GrowthForecastがMySQL対応したきっかけとしてSQLiteのdead lock問題があったのですが、PRAGMAでチューニングするとパフォーマンスが結構変わることがわかったのでメモ。

やったのは、journal_mode と synchronous の変更。それぞれ

> PRAGMA journal_mode = DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
> PRAGMA synchronous =  0 | OFF | 1 | NORMAL | 2 | FULL

のような感じで変更できる。

それぞれの設定は、

をみるのが良いと思う。journal_mode に指定できるWALについては次のページに書いてある

あと、中途半端感満載だけど以下のページも参考になる


これらの設定を変更しつつベンチマークとってみた結果が以下。ベンチマークはMacbook Air上で取った。

#default
2012-06-08T18:29:58 [INFO] starting benchmark: concurrency: 4, time: 8
2012-06-08T18:30:08 [INFO] done benchmark: score 5560, elapsed 8.082 sec = 687.942 / sec

# journal_mode = WAL
2012-06-08T18:30:08 [INFO] starting benchmark: concurrency: 4, time: 8
2012-06-08T18:30:18 [INFO] done benchmark: score 16624, elapsed 8.032 sec = 2069.632 / sec

# journal_mode = WAL + synchronous = NORMAL
2012-06-08T18:30:18 [INFO] starting benchmark: concurrency: 4, time: 8
2012-06-08T18:30:28 [INFO] done benchmark: score 31283, elapsed 8.000 sec = 3910.348 / sec

# journal_mode = WAL + synchronous = OFF
2012-06-08T18:30:28 [INFO] starting benchmark: concurrency: 4, time: 8
2012-06-08T18:30:38 [INFO] done benchmark: score 33307, elapsed 8.003 sec = 4161.728 / sec

default から「journal_mode = WAL + synchronous = NORMAL」に変更すると、約5.6倍も高速化される。synchronous = NORMAL と OFF の差が少ないのはデータ量の影響でしょうか。データの安全性とのバランスを取ってもsynchronous = NORMALが良さそうです。

ベンチマークスクリプトはgistに張った。

ベンチマークでは fujiwaraさんの Parallel::Benchmark を使った。便利!

この結果を踏まえ、DBIx::Sunny では接続した際にデフォルトで

$dbh->do("PRAGMA journal_mode = WAL");
$dbh->do("PRAGMA synchronous = NORMAL");

が実行されるようにして、バージョン 0.16 にアップデートしました。CloudForecastやGrowthForecastは個別に対応を入れてます。

CPUの使用率のグラフからSoftIRQ(Software interrupts)が抜けてしまっていたので追加して、いくつか修正を加えた。

softirq_cpu2.png

↑午前2時ぐらいからちゃんとでるようになってる。通信が多いサーバではインパクトがある変更かもしれません。

Net-SNMP(UCD-SNMP-MIB)のCPU使用率に関する項目は

  • UCD-SNMP-MIB::ssCpuRawUser.0 = Counter32: 428160510
  • UCD-SNMP-MIB::ssCpuRawNice.0 = Counter32: 381268
  • UCD-SNMP-MIB::ssCpuRawSystem.0 = Counter32: 95049839
  • UCD-SNMP-MIB::ssCpuRawIdle.0 = Counter32: 1753791119
  • UCD-SNMP-MIB::ssCpuRawWait.0 = Counter32: 5234407
  • UCD-SNMP-MIB::ssCpuRawKernel.0 = Counter32: 94121787
  • UCD-SNMP-MIB::ssCpuRawInterrupt.0 = Counter32: 256622
  • UCD-SNMP-MIB::ssCpuRawSoftIRQ.0 = Counter32: 671430

これだけあります。Linux では /proc/stat を参照していて、

cpu  428160511 381268 94121788 1753791119 5234407 256622 671430
     user      nice   system   idle       iowait  intr   soft

と関連付けられます。

各項目について調べていると、 ssCpuRawSystem が system + iowait + interrupts + softirq の合計値という説明をみますが、これは新しいNet-SNMPでは間違った説明です。Net-SNMP 5.4 からは ssCpuRawSystemが system の数値となり、sCpuRawKernelは常に「0」と表示されます。(Linux 2.6系において)

そこでssCpuRawKernelを捨ててグラフを作ればいいと思ったのですが、残念なことに CentOS5系はまだ Net-SNMP 5.3系。仕方がないのでCloudForecastではNet-SNMPのバージョンを取得し、5.4以上かどうかで処理を分けるようにしました。

グラフ上の項目とMIBは、5.3以下では

  • User => ssCpuRawUser
  • Nice => ssCpuRawNice
  • System => ssCpuRawKernel
  • Idle => ssCpuRawIdle
  • Wait => ssCpuRawWait
  • Intr => ssCpuRawInterrupt
  • SoftIRQ => ssCpuRawSoftIRQ

となり、5.4以上では

  • User => ssCpuRawUser
  • Nice => ssCpuRawNice
  • System => ssCpuRawSystem
  • Idle => ssCpuRawIdle
  • Wait => ssCpuRawWait
  • Intr => ssCpuRawInterrupt
  • SoftIRQ => ssCpuRawSoftIRQ

となっています。


この実装をするにあたり、CloudForecastのプラグインにあとからRRDの項目を追加する機構をいれました。追加データ1つにつき新しいRRDファイルが一個できます。これまではRRDファイルを一度作ってしまうと項目を足す事ができず、最初の設計で悩むことが多かったのですが、これで監視項目の追加もずいぶん楽になりました。使い方は CloudForecast::Data::Basic を参考にして頂けたらと思います。

これまで GrowthForecast のグラフ一覧や直近の数値などのデータはSQLiteに保存していましたが、パフォーマンスの問題がありそうということで、MySQL も利用できるようにしました。

移行ツールなどはありませんが、既にGrowthForecastを使っていて、MySQLを使いたい場合は、依存モジュールが増えているので以下のコマンドを実行し、モジュールの追加を行ってください。

$ git pull
$ perl Makefile.PL
$ cpanm --installdeps .

テーブルはgrowthforecast.plの起動に自動で作られるので、MySQLにデータベースの追加とユーザの設定を行って下さい。

mysql> CREATE DATABASE データベース名;
mysql> GRANT  CREATE, DELETE, INSERT, UPDATE, SELECT \
         ON データベース名.* TO 'ユーザ名'\@'クライアント' IDENTIFIED BY 'パスワード';

作成したDBを—with-mysqlオプションで指定して growthforecat.pl を起動します。ユーザ名とパスワードは環境変数として渡します。

$ MYSQL_USER=ユーザ名 MYSQL_PASSWORD=パスワード perl growthforecast.pl \
    --with-mysql dbi:mysql:データベース名;hostname=ホスト

これでSQLiteと同じように動作します。SQLiteで使っていてDead Lockがでる場合は、こちらのMySQLをお試しくださいませ。

試して頂いたoranieさんどうもありがとうございます!

Plack::Middleware::ServerStatus::Lite 0.07 でステータス画面に起動してから処理したアクセス数がだせるようになりました。監視ツールと組み合わせてリアルタイムに処理しているアクセス数を確認できます。

Middlewareを読み込む際に counter_file に記録用のファイルを指定すると利用できます。デフォルトは無効です

use Plack::Builder;
use File::Temp qw/tempdir/;

builder {
    enable "Plack::Middleware::ServerStatus::Lite",
        path => '/server-status',
        allow => [ '127.0.0.1', '192.168.0.0/16' ],
        counter_file => '/tmp/counter_file',
        scoreboard => tempdir(CLEANUP=>1);
    sub { [200,['Content-Type'=>'text/plain'],['OK']] }
};

counter_file に File::Temp::tempfile を使う場合は、EXLOCKの解除が必要となります。

my ($fh,$filename) = File::Temp::tempfile( EXLOCK => 0 );
#または
my ($fh,$filename) = File::Temp::tempfile();
undef $fh;

testしていて見事にハマりました。

ステータス画面は次のようになります。Total Accessesの行が増えてますね。

% curl http://localhost:5000/server-status
Uptime: 1338530908 (9 seconds)
Total Accesses: 4321
BusyWorkers: 1
IdleWorkers: 4
--
pid status remote_addr host method uri protocol ss
86963 _ 127.0.0.1 localhost:5000 GET / HTTP/1.0 3
86964 _ 127.0.0.1 localhost:5000 GET / HTTP/1.0 3
86965 _ 127.0.0.1 localhost:5000 GET / HTTP/1.0 3
86966 _ 127.0.0.1 localhost:5000 GET / HTTP/1.0 3
86967 A 127.0.0.1 localhost:5000 GET /server-status HTTP/1.1 0

ちなみに /server-status へのアクセスもカウントされます。

ステータス画面の「ss」も今回のバージョンで追加されたもので、最後のリクエストを受け付けてからの経過時間です。Aapacheのmod_statusにもあります。


カウンターを入れた事で気になるオーバーヘッドですが、それほど大きくはなさそうです。手元のMacbook Airで上のpsgiを実行し、ApacheBenchをかけたところ、

# サーバ起動
% plackup -E production -s Starman app.psgi 
# ApachBench
% ab -c 3 -n 4320  http://localhost:5000/

カウンターなしで 1700req/sec。カウンター有りで 1650req/sec。実際のアプリケーションにしたらほぼ誤差になるんじゃないでしょうか。


ServerStatus::Lite へのカウンター機能の追加はsmlyさんからの提案でした。ありがとうございます。Cache::FastMmapやIPC::ShareLiteなどいくつか実装を試しつつ最終的に普通のファイルベースとなりました。