DBIx::Skinny::Schema::Loader 0.16 released

YAPC楽しいけど、自分の無力さにめげそうになるよねー、ということで少しでも抵抗しようと思って講堂前で書きました。

make_schema_atにignore_rulesオプションを渡してやることで、schema出力の対象から除外するテーブルを指定出来るようになりました。

my $schema = make_schema_at(
    'MyApp::DB::Schema',
    {
        ignore_rules => [ qr/rs$/, qr/^no/ ],
    },
    [ $dsn, $username, $password ]
),

のように、正規表現リテラルをArrayRefに入れて指定します。

月毎のテーブルを作ってログを記録しているようなDBを扱う時にあるとちょっと便利かも知れません。

DBIx::Skinny::Schema::Loader 0.15 released

今まで、Loaderのprimary keyに関する動作は

  • DB上でpkが指定されていればそれを使う
  • idというカラムがあればpkとみなす
  • いずれにも合致しなければエラー

というロジックだったのですが、いずれにも合致しない、つまりpkが無い場合は未定義のまま動作するように変更しました。

初期のDBIx::Skinnyではprimary keyが存在しないテーブルを扱うことを想定していなかったため、Loader側でそのようなschemaを吐かないようにチェックしていました。

今では、というかかなり前から本体側でそういったケースもフォローしているので、Loader側も今更ながら追従したという流れです。

Test::More 0.95_01以降ではsubtest毎にdone_testingって書かなくてもOK

昨日Twitterにふらっと投げたらreply付いたので、せっかくだからブログに書いておく。

何気なくTest::MoreのPodを読んでいると、

This adds an implicit done_testing() to the end of your subtest. The following two subtests are equivalent:

http://search.cpan.org/~mschwern/Test-Simple-0.96/lib/Test/More.pm
  subtest 'subtest with implicit done_testing()', sub {
      ok 1, 'subtests with an implicit done testing should work';
      ok 1, '... and support more than one test';
      ok 1, '... no matter how many tests are run';
  };

  subtest 'subtest with explicit done_testing()', sub {
      ok 1, 'subtests with an explicit done testing should work';
      ok 1, '... and support more than one test';
      ok 1, '... no matter how many tests are run';
      done_testing();
  };

なんて記述を見つけて、done_testing書かないと怒られるやんと思ってたら、0.95_1のChangelogに記述が。

New Features
* subtests without a 'plan' or 'no_plan' have an implicit 'done_testing()' added to them.

http://cpansearch.perl.org/src/MSCHWERN/Test-Simple-0.96/Changes

ということで、書かなくてもよくなっていたようです。


どうせなら$setupとか$teardownにcoderefを突っ込んでおけば勝手に呼んでくれたりすればもっと便利だと思うのですが、それはTest::Class使えよってことになるかも知れない。

skip-name-resolveでMySQLへの接続がどの程度速くなるのか試してみた

を見て、実際のところどの程度性能に影響があるのか気になったのでベンチを取ってみた。

ベンチマークスクリプトhttp://gist.github.com/594839で、connect -> disconnectを1000回繰り返すのにかかった時間を計測。

■計測結果

環境の詳細は後述するとして、まずは結果から。手動で何回か実行して、結果のボリュームゾーンを感覚的に抽出。

環境 実行時間(秒)
skip-name-resolve 1.73 〜 1.74
hosts 1.75 〜 1.76
DNS 2.26 〜 2.29

/etc/my.cnfでskip-name-resolveを有効にした場合、確かに速くて、DNSで名前解決するよりも0.5秒ぐらい速くなる。1回あたり0.5msecをどう見るかはケースバイケースだが、DB接続だけでこれだけ変わるのであれば採用したいなと思った。

ただ、hostsで名前解決が出来る場合はほとんど誤差程度の違いしか見られなかった。繰り返しテストすると確かにskip-name-resolve使用時の方が速い傾向が出るが、必ず毎回そうなるわけではなく、hostsの方が速いこともあった。

MySQL公式の「6.5.6. MySQLの DNS の使用」によれば、

オペレーティングシステムがスレッドセーフの gethostbyaddr_r()とgethostbyname_r()の呼び出しをサポートしている場合、スレッドではこれを使用してホスト名の解決が実行される。

とあるが、この呼び出しを省略することによるパフォーマンス向上はそれほど大きくはない印象です。

どちらかと言えば、hostsとDNSでこれぐらい差が付くんだなーということの方が印象的なテストになった。DNSはdnscache(djbdns)を使っているが、これがbindだったりunboundだった場合にどうなるかは調べていない。

内部で多数の通信が必要なアプリケーションでは、多少面倒でもhostsで名前解決するような運用にした方が有利なのかな。あと、クライアントでのDBサーバの名前解決はhostsに書いてるんだけど、これをDNS参照すると0.3秒ぐらい遅くなった。

skip-name-resolveを有効にすると、「localhost」も名前として使えなくなるらしいので注意が必要。MySQLlocalhostはソケットで、127.0.0.1TCPで接続しているようなので、そこを厳密にしたい場合は使えないと思われます(以前、mysqld_multiで接続をうまく使い分けてくれなくて、localhost127.0.0.1が同じ物だという前提に基づいたアプリが問題だったことがある)。このへんはあまり興味ないので全く調べてない。

■実施環境

client
OS CentOS5.4
CPU Athlon64 3500+ (1core 2.2GHz)
memory DDR2 1GB
memo Xen上の仮想マシン
DB server
OS CentOS5.5
CPU Xeon X3330 (4core 2.66GHz) (C2Q相当)
memory DDR2 8GB
MySQL 5.1.49 + InnoDB plugin
memo clientとは別の物理サーバ
DNS
  • clientマシンで稼働
  • dnscache(djbdns)を使用
network
  • 100Mの古い安物ハブ
  • GbEなら改善されるかも

■まとめ

  • DNSで名前解決するならskip-name-resolveは検討の価値あり
  • hostsだと誤差程度の差なので、skip-name-resolveその物はそんなに速さに貢献しない
  • hostsとDNSの差が思った以上にあるので、積極的にhostsに書いていきたい
  • 個人的にはhostsをPuppetで配布するアプローチで必要十分な感がある

(追記)ベンチマークスクリプトをDBサーバで実行したら、0.41〜0.48秒ぐらいでした。名前解決はhostsでプライベートIPを指定。ネットワークの差か、CPUの差か。多分CPUの差だと思うけど、機材が足りなくて検証出来ません。

(追記2)DBサーバからプライベートIPをDNSで名前解決させたら0.74〜0.78秒ぐらい。hostsの方が速いけど、最初のテスト環境よりも差は小さい。DNSサーバを高スペックな物にすれば更に差は縮まるような気がしてきました。まぁでも、この差が気になる環境だと内部のトラフィックを減らすためにhosts書くメリットが大きいんじゃないかな。

ASUS P7F-MでデュアルコアCPUを使用する場合はECCメモリが必須

ASUS P7F-Mという、MicroATXで内蔵VGA搭載、INTEL NIC2ポート(と別口1つで合計3ポート)搭載でSocket 1156の「省スペースサーバ自作しろってことですね分かります」的なマザーがあるんですが、これが意外と厄介だったのでメモ。

ECCメモリはxeon3400シリーズ使用時のみ使用可)

xeon L3406使用時はRDIMMは使用できません)

なんて注意書きがありますが、実際に購入する際にはメモリにもっと気を遣う必要があります。

asus.comにあるCPU Support Listを見ると、i3, i5, i7, Xeon L or X 3400番台と広くサポートしていますが、注目は「QVL」のタグにある内容。そもそもQVLってのが何なのか分からないのだけど、開いてみると「Qualified Vendor List」とのこと。業界じゃ常識なんだろうか。

で、ここに上がってるAVL(これも何だか分からないがApproved Vendor Listらしい)をダウンロードしてきて確認。何故かRARアーカイブで提供されているファイルを展開して中のPDFを見ると、対応しているCPUとメモリの対応表が。要約すると、

i3, i5, Xeon L3406とかデュアルコアCPUはnon-ECCメモリ対応してねーよ。あとRDIMMも知らねー

と書いてあります。4コアのi5, i7やXeon X3400番台ならnon-ECCもサポートしているらしいですが、2コアのCPUだとECCメモリを差さないとBIOSすら起動しません。これ、もっとちゃんとアナウンスしておくべきだと思うんだけどな。もしかして常識なんでしょうか。

「DDR3も安くなってきたし、i3使ってお得なサーバ組めるんじゃね?」とか考えてると足下をすくわれたりするのでご注意を。

Path::AttrRouterのControllerをextendsする時はBEGINで(って書いたけどやっぱり '-extends' で)

Path::AttrRouterを使うのに、

package MyController;
use Any::Moose;
extends 'Path::AttrRouter::Controller';

sub index :Path Args(0) {
}

1;

みたいに書くと「Error while loading app.psgi: Invalid CODE attributes:」とか怒られてどうすればいいんだろう。

と思って#perl-casualで聞いてみたら、JPAの牧さんがあっさり「それBEGIN {}でできるよ」と解決してくれました。

package MyController;
use Any::Moose;
BEGIN { extends 'Path::AttrRouter::Controller' }

のように、extendsをBEGINでくくってやれば無問題。use baseはコンパイル時に評価されるけど、extendsは実行時なのでこれが必要らしいです。

catamooseが出た時にBEGINでどうこうって話が出て他のはこれだったのか。2010年にもなって引っかかってすいません。

ちょっと記述としては冗長だなと思うけど、

これがRoleとの関連で不採用になったりと、いろいろと経緯があったようです。

そんな疑問があっさり解決したり、関連トピックがいろいろ聞ける#perl-casualマジお勧め。

こういうショボい質問も皆が優しく教えてくれるので、Perl使ってる人はirc.freenode.netに今すぐアクセスした方がいいですよ。

(追記)Path::AttrRouterについては

package MyController;
use Any::Moose;
use Path::AttrRouter::Controller '-extends';

って書けばBEGINとか使わなくてもいいそうです。typester++

ソースはちゃんと読んで理解しないとダメってことすね。

Moose, Mouse, Class::Accessor::Fastのアクセサでベンチマーク取ってみた

馬鹿にできないアクセサのオーバーヘッド - Craftworks Tech Blog - Branch」にもあるように、Mooseのアクセサによるオーバーヘッドは無視するにはちょっと大きいという印象があります。

じゃあ、Mouseだったらどうなんだろうとか、自分が使うサーバのスペックも随分上がったしとか、そういう経緯で改めてベンチを取ってみました。コードは@Craftworksさんの物にMouseを足しただけ。以下に貼り付けておきます。

で、こいつをXeon X3330@2.66GHzで走らせた結果が以下(cmp_these部分のみ)。

                  Rate moose_ro moose_rw caf_rw mouse_ro mouse_rw mouse_direct moose_direct caf_direct
moose_ro      297030/s       --      -8%   -28%     -66%     -68%         -82%         -83%       -89%
moose_rw      322581/s       9%       --   -22%     -63%     -66%         -81%         -82%       -88%
caf_rw        410959/s      38%      27%     --     -53%     -56%         -75%         -77%       -85%
mouse_ro      882353/s     197%     174%   115%       --      -6%         -47%         -50%       -68%
mouse_rw      937500/s     216%     191%   128%       6%       --         -44%         -47%       -66%
mouse_direct 1666667/s     461%     417%   306%      89%      78%           --          -6%       -39%
moose_direct 1764706/s     494%     447%   329%     100%      88%           6%           --       -35%
caf_direct   2727273/s     818%     745%   564%     209%     191%          64%          55%         --

Mouse速すぎワロタ。Class::Accessor::Fastに対して2倍以上という好成績。そもそも一番遅いMooseでも秒間30万回ぐらい呼べているので、そんなに気にすることは無いかなという印象ですね。

なお、MOUSE_PUREPERL=1で実行するとこんな感じ。

                  Rate mouse_ro moose_ro moose_rw mouse_rw caf_rw moose_direct mouse_direct caf_direct
mouse_ro      297030/s       --      -7%     -13%     -14%   -29%         -82%         -83%       -89%
moose_ro      319149/s       7%       --      -6%      -7%   -23%         -81%         -82%       -88%
moose_rw      340909/s      15%       7%       --      -1%   -18%         -80%         -81%       -87%
mouse_rw      344828/s      16%       8%       1%       --   -17%         -79%         -80%       -87%
caf_rw        416667/s      40%      31%      22%      21%     --         -75%         -76%       -85%
moose_direct 1666667/s     461%     422%     389%     383%   300%           --          -6%       -39%
mouse_direct 1764706/s     494%     453%     418%     412%   324%           6%           --       -35%
caf_direct   2727273/s     818%     755%     700%     691%   555%          64%          55%         --

XS化による効果の程が分かるというものですね。@__gfx__++