2011年9月15日木曜日

Mac OS X 10.7 (Lion) で hosts ファイルの設定が反映されない

先日から本格的にMacBook Airを仕事用のPCとして使い始めました♪

今まで使っていた3年前のVAIOよりもサクサク動くし、
快適!と喜んでいたのもつかの間、、、

残念なことに /etc/hosts ファイルを書き換えても、
設定内容が反映されないことがわかりました。
そのため、例えば、
/etc/hosts を書き換えて、ブラウザから一時的にテスト環境に接続する
といった使い方ができないことがわかりました。

詳しい理由とワークアラウンドは、
以下のURLに書かれています。

Mac OS X Lion, /etc/hosts Bugs, and DNS Resolution | Justin Carmony

書かれている内容を要約すると、、、

hostsの設定が反映されない理由:
・名前解決に、hostsファイルよりもDNSサーバが優先される
・名前解決の順番は変更できない

この問題の回避方法:
・自分のPCにDNSサーバをインストールする。

うーん、微妙。。。
どうして Mac OS X 10.7(Lion)からこういう仕様(BUG?)になったんでしょうか?

2011年8月30日火曜日

EC2のIPアドレス

EC2のインスタンスを起動すると
Private IPおよびGlobal IPが割り当てられます。
また、それぞれに対応するPrivateDNS名およびPublicDNS名が付きます。
これらの値は、インスタンス起動時に割り当てられるのですが、

Global IPについては、インスタンスをTerminateしない限り変更さない。
Private IPについては、インスタンス起動中も定期的に変更される。


と、最近までは思っていました。
でも、最近一部のインスタンスでPrivateDNSの名前が、

ip-10-XXX-YYY-ZZZ.ec2.internal

みたいなPrivate IPを含む名前になっていることに気づきました。

以前までは、PrivateDNSはMACアドレスを含む

domU-11-22-33-AA-BB-CC.compute-1.internal

みたいな名前でした。

自分の解釈では、Private IPは定期的に変更されるから、
MACアドレスのように不変な値がPrivateDNSの一部になっているのかと理解していました。
少し記憶が曖昧なのですが、実際、以前はPrivate IPが、
DHCPで定期的に変更されていたような気がします。

逆に、Global IPについては、インスタンスをTerminateしない限り変更されないので、
PublicDNS名は、Global IPを含む形式になっていると理解していました。

ってことは、Private IPが変化しなくなったのかな?
と思って調べていたら、
以下のページにたどり着きました。

動作中にインスタンスのIPアドレスの変更がありえるか?

上記ページによると、インスタンス起動中は、
Private/Global IPは不変とのこと。
またRebootしても不変とのこと。

知らなかった。。。
一体いつからこうなったんでしょうか?
最初からだったのでしょうか?
私の気のせい?

2011年8月23日火曜日

メールを転送した時にSPAM扱いされないようにする方法

自分で運用しているメールサーバに届いたメールを、
サーバ側から特定のメールアドレスに転送した時にSPAM扱いされないようにする方法を紹介します。

■その前に、このエントリを読み進めるための前提知識


■そもそも私が直面した現象
自分が運用しているメールサーバに届いたメールを、サーバ側のエイリアスの設定で
Gmailまたは、Google Appsのgroupに転送すると、エラーメールが返ってくるようになりました。
エラーメールには、以下のURLを参照するようにと書いてありました。

http://www.google.com/support/a/bin/answer.py?hl=en&answer=168383

また、添付されていくるメールのヘッダ情報によると、SPFがFAILしていました。


■解決方法
2つの方法を確認したところSPFがPASSになることを確認しました。
(もちろん、SPFの設定が正しく行われていることが前提。)
どちらの方法でもSPFはPASSになりますが、
可能であれば、両方実施した方が確実にSPAM扱いされないように思います。

解決方法その1:
転送する時に、Senderヘッダを付加。
Senderヘッダの値には、転送メールを実際に送信するメールサーバのドメインのメールアドレスを指定します。
※意味的には、Resent-Sender ヘッダを指定するほうがいいかもしれない。(ただし、未検証)

解決方法その2:
エンベロープFromを指定して転送する
転送メールを送信するメールサーバのドメインのアドレスをエンベロープFromに指定。


■参考までに解決方法その2を実験した時の結果を紹介します
ここでは、以下の前提で話を進めます。

自分のメールサーバのドメイン:hoge.com
自分のメールサーバのIP:50.50.50.50

また、DNSのTXTレコードにはきちんとSPFの設定ができているものをします。
ここの例だと、

hoge.com. TXT "v=spf1 +mx +ip4:50.50.50.50 ~all"

といった感じでhoge.comドメインのメールを送信するメールサーバのIPが
SPFレコードに含まれているものとします。

ここで、

From: foo@docomo.ne.jp
To: aaa@hoge.com


というメールが携帯(DoCoMo)からhoge.comサーバに送られてきて、
それをbar@gmail.comというGmailアドレスに転送すると仮定します。

つまり、
/etc/aliases
に以下のエントリがあるものとします。

aaa: bar@gmail.com

その場合、docomo.ne.jp ドメインからのメールが、hoge.comというメールサーバからgmail.comに送信されることになります。
メールを受信するサーバは、送信ドメイン認証を行うわけですが、
当然、Fromヘッダのドメインがdocomo.ne.jpのメールがhoge.comから送信されるので、
gmail.comのメールサーバ側では偽装していると判断され、
SPFがFAILしたり、SPAMフォルダに振り分けられたりします。
また、最悪の場合、受信を拒否されます。

そこで、aliaseの設定を以下のようにして、
エンベロープFromをメールを実際に送信するサーバのドメインに変更します。

aaa: "|/usr/sbin/sendmail -oi -f aaa@hoge.com bar@gmail.com"

すると、めでたくSPFがPASSになります。


■未解決の問題
問題は、このことを知らずに転送メールを送り続けた結果、
自分のメールサーバがSPAMを送りつけるメールサーバとしてブラックリスト入りした場合です。

特にGoogleの場合、以下の内容のエラーメールが返ってきた場合です。

Delivery to the following recipient failed permanently:

これを回避する方法は今のところ見つけられませんでした。
Googleのアカウントマネージャに相談するしかないのでしょうか?


■参考までに送信ドメイン認証を行っているメールサーバ
ezweb.ne.jp(AU):
http://www.au.kddi.com/service/email/support/chui/spf_record.html

docomo.ne.jp(NTT DoCoMo):
http://www.nttdocomo.co.jp/service/communication/imode_mail/notice/sender_id/

yahoo.co.jp(Yahoo!)
http://pr.yahoo.co.jp/release/2006/1212a.html

gmail.com(Google):
http://www.fukatani.org/~hi-lo/blog/archives/2010/11/gmailspfdkim.html
http://www.google.co.jp/support/forum/p/apps/thread?tid=295af15255405e56&hl=ja


■参考にしたURL
http://kuro.crow2.net/problem/post_p3.htm
http://www.openspf.org/FAQ/Forwarding
http://www.itmedia.co.jp/enterprise/articles/0603/24/news007.html
http://www.atmarkit.co.jp/fsecurity/special/82senderid/sender101.html
http://www.google.com/support/a/bin/answer.py?hl=en&answer=168383
http://www.matsuaz.com/matsumotojs/2011/01/24/1295879296259.html
http://www.atmarkit.co.jp/fnetwork/rensai/netpro03/mail-header.html
http://gabacho.reto.jp/whims/whim0105.html

2011年7月19日火曜日

MySQLのinnodb_log_file_sizeの変更方法

先日、MySQLのInnoDB関連のチューニングを行いました。

具体的には、
http://dsas.blog.klab.org/archives/50860867.html
などのURLを参考にして、

innodb_buffer_pool_size
innodb_log_file_size
thread_cache_size

を変更したのですが、うまく起動できませんでした。
(起動したように見えるが、実際はエラーが出てうまく動かない。)

原因は、innodb_log_file_size を変更した時に、古いlogファイルが残っていると、
サイズが違うため、以下のエラーが発生し、

InnoDB: Error: log file ./ib_logfile0 is of different size 0 5242880 bytes

その後に、

Incorrect information in file XXX

といったメッセージが大量に出ていました。

これを防ぐためには、次の手順で innodb_log_file_size を変更します。

1.SET GLOBAL innodb_fast_shutdown=0; を実行
2.MySQLをshutdown。
3.my.cnfのinnodb_log_file_size を変更。
4.ログファイルを削除(デフォルトだと ib_logfile(0|1) の 2 ファイル)
5.MySQLのをstartup

参考にしたURL:
http://d.hatena.ne.jp/koziy/20070911/1189472682
http://dsas.blog.klab.org/archives/50860867.html
http://www.ibm.com/developerworks/jp/linux/library/l-tune-lamp-3.html

2011年2月25日金曜日

コンピュータの名著・古典100冊

前回のエントリでは、
「最近、コンピュータやソフトウェアについて体系的に勉強したいなと思っています。」
と書きました。
しかし、いざ自分で勉強しようと思っても、どの本を自習用教材にすれば良いのかで迷いました。
また、初心者でもがんばれば読破できて(適度な難易度で)、単なるハウツー本やバズワード系流行り本ではない本(内容に普遍性がある本)を探すのは難しいなと思っていました。

ところが先日、たまたま近所の図書館で良い本に巡り会いました。

コンピュータの名著・古典100冊 という本なのですが、その名の通り、コンピュータサイエンスにおける名著を紹介するブックガイドです。

この本の素晴らしいところは、コンピュータサイエンスの技術的な側面だけではなく、歴史や人物や思想といったジャンルも含めた11個のカテゴリーに分けて、それぞれの名著を紹介しているところです。
参考までに11個のカテゴリを載せておきます。

1.歴史
2.人物・企業
3.ドキュメンタリー
4.思想
5.数学/アルゴリズム
6.コンピュータサイエンス
7.アーキテクチャ/OS/データベース
8.コンパイラ/言語
9.プログラミング
10.ソフトウェア開発
11.インターネット

加えて、選定基準の1つが「10年ぐらい経っても古くならない本」ということで、流行に左右されない本を紹介しているところも良いです。

というわけで、IT業界で働く全ての方にこの本はおすすめです。
まずは、自分の関わっているジャンルの名著から読んでいき、必要に応じて他のジャンルにも進んでいくのはどうでしょうか?
各章末にはゲストライターによるコラムも収録されていて、その部分だけでも楽しく読むことができると思います。



ちなみにこの本は、2006年に改訂されています。
2003年に出た初版本とは、基本的に推薦されている本は同じなのですが、コラムを寄稿されている方のメンバーが変更になっています。
個人的には、西和彦さん、まつもとゆきひろさん、斉藤秀夫さんが寄稿されている初版本
が好きだったりします。

2011年2月5日土曜日

わが友 石頭計算機(My good friend the stone brain computer)

最近、コンピュータやソフトウェアについて体系的に勉強したいなと思っています。
私は大学の専攻が情報処理系ではなく、基本的には仕事を通じて+独学で勉強してきたので、部分的に知識が抜けているし、それをダマシダマシ切り抜けて仕事をしてきたとわかっていたので、いつかは基礎からきちんと勉強したいなと思っていました。
でも、なんとなく実行に移せずにいました。
正直、何の勉強から始めたらいいのかがわからなかったということもありました。

ところが先日、あるきっかけで、安野光雅著の「わが友 石頭計算機」という絵本?の存在を知りました。
色々なサイトでレビューを見てみると、コンピュータ(計算機)についてとても分かりやすく説明されていて、どうやら凄い本らしいということがわかりました。
絵本ということもあって、これだったら初心者でもいけそうと思い、早速購入して読んでみました。
ちなみにこの本は、絶版になっていたので、ちょっと奮発してコレクター商品を中古で購入しました。
(改訂版が「石頭コンピューター」というタイトルで出ているようですが、少し内容が異なるそうです。)

内容は
・はあどうえあ
・そふとうえあ
・ろぼっと
という3部構成になっています。

まず、1691年に、Dr.ストーンブレーンによって書かれた本の翻訳という設定がいきなり面白いです。
それから、計算機がどのようにして2進数で足し算を行っているのか?について、ししおどしを用いて図解しているのですが、非常に分かりやすかったです。
そもそも、計算機が引き算を足し算で行っているという事実(補数の概念)も目からウロコでした。
ソフトウェアのバグを魔女裁判に例えて説明するあたりも素晴らしいです。
全体を通して、理解することが難しいことを、挿絵を用いながら上手に説明されていてビックリします。

とにかく、決して色褪せることのない名著です。
老若男女、文系、理系問わず、ぜひ読んで欲しい絵本です。

安野光雅さんは個人的に大好きな絵本作家の方で、他にも紹介したい絵本が沢山ありますが、また別の機会にポストしたいと思います。



2011年2月2日水曜日

WinSCPと改行コード

最近改行コードネタが多いのですが、WinSCPと改行コードの問題について書きます。

私は普段、WinSCPというソフトを使って自分のPC(Windows)で作成したファイルをLinuxサーバに転送しています。

先日、あるファイルがWindowsの改行コードのままサーバに転送されていることがわかりました。
(参考までに、WindowsとLinuxでは改行コード(EOL)の種類が異なります。WindowsはCRLF、UNIX系OSはLFです。この違いのために、問題が発生することがあります。)

原因を調べるために色々実験したところ、以下のことがわかりました。

1.ファイルをサーバに転送する時のWinSCPの動作

・ASCIIモードの場合
改行コードはUNIX形式に変換され転送される。
WinSCPのデフォルトの設定では、ファイルの拡張子を見て、自動的にASCIIモードに切り替わる。

・バイナリモードの場合
改行コードの変換は行われず、Windows形式のままサーバに転送される。
WinSCPのデフォルトの設定では、ファイルの拡張子を見て、自動的にバイナリモードに切り替わる。


2.サーバー上のファイルをWinSCPから直接編集して保存した場合の動作

デフォルトのエディタで編集した場合は、バイナリモードで転送される。
つまり、改行コードの変換は行われず、Windows形式のままサーバに転送される。
外部エディタを設定している場合、強制的にASCIIモードで転送するよう設定することが可能。


以上の結果から、改行コードをきちんと変換するためのWinSCPの設定は、以下のようになります。

1.[オプション]->[環境設定]->[転送]で「以下のファイルはテキストモード」のところに、必要な拡張子を追加します。

ここに設定されている拡張子のファイルは、テキストファイルとみなされて、自動的にASCIIモードで転送されます。
確実にテキストファイルであることが分かっている拡張子はきちんと追加しておき、確実にASCIIモードで転送されるようにしておきましょう。
参考までに、自分の場合以下の拡張子を追加しました。
*.yml; *.yaml; *.conf; *.pm; *.tt; *.sql; *.t; *.py; *.js; *.json;


2.[オプション]->[環境設定]->[エディタ]画面で必ず外部エディタを登録しておく。
また、「外部エディタでのファイル編集時には強制的にテキスト転送モードにする」にはチェックを入れておく。
なお、追加したエディタは最優先にしておきましょう。

こうすることで、サーバー上のファイルをWinSCPから直接編集して保存する際は、必ずASCIIモードで転送されるようになります。


3.[オプション]->[環境設定]を開き、左の[環境]->[パネル]メニューを選択し、「ダブルクリック時に実行する操作」は「編集」を選択します。


参考にしたURL:
http://www.affrc.go.jp/ja/info/scs/guide/ssh/ascii_mode.html

2011年2月1日火曜日

Subversion導入時の注意点(改行コード、実行権限) その2

前回のエントリでは、Subversionで発生した改行コードおよび実行権限の問題について、その対処方法を書きました。
今回のエントリでは、そもそもそういった問題が発生しないためには何をすればよかったのか?について書きたいと思います。

前回のおさらいになりますが、最終的な結論として、

・実行ビットが必要なファイルには、svn:executable 属性を設定
・テキストファイルには、svn:eol-style 属性に native という値を設定

を行えば、問題は回避できるということでした。

ただ、これらの設定を毎回手動で行うのは面倒ですし、うっかり忘れたりすることも考えられます。
でもご安心ください。
リポジトリにファイルを追加するときに、これらの属性設定を自動的に行う機能がSubversionクライアントには備わっていました。

例として、次のような条件で自動的に属性を設定したいと仮定します。

・拡張子がplというファイルには実行権限を設定
・拡張子がplおよびpmというファイルはテキストファイルなので、OSに依存しない改行コードに正規化

この場合、次の内容をSubversionクライアントの設定ファイルに書いておくと、次回のaddからはファイルの拡張子を見て、自動的に属性を設定してくれます。

enable-auto-props = yes

[auto-props]
*.pl = svn:eol-style=native;svn:executable
*.pm = svn:eol-style=native


この例では *.pl と *.pm だけでしたが、実際はもっと色々な拡張子に対応したものになるかと思います。
こちらのサイトには、様々な拡張子を網羅した、とても良い設定例が載っていますので、ここの設定をベースにして、自分の環境用にアレンジするのが良いと思います。

なお、設定ファイルの場所は、Subversionクライアントによって異なります。
参考までに、私が使ったことのあるものだと以下の場所になります。

・Linuxのsvnコマンドの場合
~/.subversion/config というファイルが設定ファイルになります。

・WindowsのTortoiseSVNの場合
右クリック→TortoiseSVN→Settingsを開き、Generalの「Subversion configuration file」の「Edit」ボタンをクリックすると設定ファイルが開きます。


というわけで結論:
これからSubversionを導入して、ファイルのバージョン管理を行うプロジェクトは、
一番最初に全てのメンバーに対して、プロジェクトで採用する設定ファイルを共有しておく。
その後は特に何も心配しなくてもOK。



不幸にも、私の場合のように、すでにSubversionを導入済みで、後から問題に気づいてからこの設定を行った場合は話が面倒です。
なぜなら、残念なことに、すでにリポジトリ上に存在するファイルに対しては、この設定は意味を持ちません。
あくまでも今後add(リポジトリへのファイルの追加)する時に意味を持ちます。
つまり、すでにリポジトリに存在するファイルについては、スクリプトなどを使って、前回のエントリで紹介したような属性設定の操作(svn psコマンド)を必要な全てのファイルに対して行う必要があります。

さらに不幸なことに、1つのファイルの中に異なる形式の改行コードが混在するファイルがリポジトリにうっかりCommitされている場合は、svn:eol-style=native 属性を後から設定したくても以下のようなエラーが発生して設定できません。

$ svn ps svn:eol-style 'native' foo.txt
svn: File 'foo.txt' has inconsistent newlines


というわけで、不幸にも私のような状況になってしまった方は、
・改行コードの統一
・属性の設定
という作業を必要な全てのファイルに対して、スクリプトなりを使って頑張ってやるしかないということになります。
幸い、先程紹介したサイトに、スクリプトのサンプルが載っていました。
それを参考に、このエントリで書いた設定例の設定をリポジトリ上のファイルにも適用したい場合には、どういったスクリプトを実行すればよいのかを書きます。

# 改行コードの統一
$ find . -type f -name \*.pl -print0 | xargs -0 perl -i -pe "s/\r\n/\n/"
$ find . -type f -name \*.pm -print0 | xargs -0 perl -i -pe "s/\r\n/\n/"

# 属性の設定
$ find . -type f -name \*.pl -print0 | xargs -0 svn propset svn:eol-style native
$ find . -type f -name \*.pl -print0 | xargs -0 svn propset svn:executable ''
$ find . -type f -name \*.pm -print0 | xargs -0 svn propset svn:eol-style native


これ以上、この問題の餌食になる人が出ないことを祈ります。。。
皆様も本当に気をつけてください。

参考にしたURL:
http://openlab.dino.co.jp/2008/10/15/212603361.html

2011年1月30日日曜日

Subversion導入時の注意点(改行コード、実行権限)その1

Subversion導入後にファイルの改行コードおよび実行権限で色々問題が発生したので、その時の対応をまとめたいと思います。

開発者はLinux、デザイナーさんはMac OS、HTMLのコーダーはWindowsみたいな状況って普通にあることだと思うんですが、このようなOSが混在する状況で、ちょっと困った問題が発生しました。

皆がそれぞれの環境で作成したファイルを、Subversionにコミットしていたんですが、ある日、Linux上でCheckoutしたPerlスクリプトファイルを実行しようとしたところ、次のようなエラーが発生しました。

$ ./test.pl
-bash: ./test.pl: Permission denied


あれれ、実行できない。
lsで確認すると。

$ ls -la test.pl
-rw-rw-r-- 1 yt yt 172 Jan 28 18:08 test.pl


おや?実行権限が無い。。。。
まぁいっか、chmodで実行権限を付与して実行してみよう。

$ chmod 755 test.pl
$ ./test.pl
-bash: ./test.pl: /usr/bin/perl^M: bad interpreter: No such file or directory


今度は意味不明なエラーが発生。
そこで、このファイルをvimエディタで開いてみると、ステータスバーに以下の表示。

"test.pl" [noeol][dos] 9L, 172C

どうやらファイルの改行コードがWindows(DOS)形式になっていたことが原因で、エラーが発生していたようです。

そこで、こちらを参考にして、vimのコマンド set ff=unix を使って、改行コードをUNIX形式に変換しました。
すると、エラーが発生しなくなりました。

なぜ、こんなことになったのか?
ということで調べたところ、このファイルは元々Windows上で作成し、WindowsのSubversionクライアントのTortoiseSVN経由でリポジトリにaddされたことがわかりました。

そもそもWindowsというOSにはUNIX系OSでいうところの実行ビットという概念が存在しないですし、改行コードも異なるので、なるほどこんな問題が発生しても不思議ではありません。

この問題をどうにか解決できないのかを調べていたところ、Subversionの日本語マニュアルにたどり着きました。
それによれば、Subversionはファイルやディレクトリに対して属性というメタデータを設定することができて、そのメタデータ自体もバージョン管理されるということでした。
さらに属性には、Subversion自身が使用している特殊な属性があり、それを使うとこの問題を解決できることがわかりました。
具体的には、svn:executable と svn:eol-style という属性名なんですが、それぞれの意味は、

svn:executable
ファイルに対してこの属性名が設定されていると、OSが対応していれば実行ビットが有効になる。
属性値は無い。

svn:eol-style=native
Checkoutする際に、ファイルに含まれる改行コードをCheckout先のOSに応じて変換する。
つまり、Windowsの場合は CRLF に変換され、UNIX系OSの場合は LF に変換されます。
逆にaddやcommitなどのコマンドでリポジトリにファイルを格納するときには、 オペレーティングシステムにはよらず、正規化された改行コード(LF)に変換されてリポジトリに格納されます。


だいたい上記のような意味だと私は解釈しました。
早速テストしてみました。実行結果はこんな感じです。

$ ls -la test.pl
-rw-rw-r-- 1 yt yt 23 Jan 28 18:59 test.pl
$ svn ps svn:executable '' test.pl ←svn:executableの設定
property 'svn:executable' set on 'test.pl'
$ ls -la test.pl
-rwxrwxr-x 1 yt yt 23 Jan 28 18:59 test.pl ←実行権限が付与された!
$ ./test.pl
-bash: ./test.pl: /bin/bash^M: bad interpreter: No such file or directory
$ svn ps svn:eol-style 'native' test.pl ←svn:eol-style=nativeの設定
property 'svn:eol-style' set on 'test.pl'
$ ./test.pl ←svn:eol-style=nativeを設定しただけではEOLの変換は行われないのでエラーになる
-bash: ./test.pl: /bin/bash^M: bad interpreter: No such file or directory
$ svn commit ←commitの段階でファイルのEOLがLFで正規化されてリポジトリに保存される
Sending test.pl
Transmitting file data .
Committed revision 31.
$ ./test.pl ←エラーが無くなり、実行可能になった!!
hello


という訳で、OSが混在する環境でSubversionを使用している場合は、
・実行ビットが必要なファイルには、svn:executable 属性を設定。
・テキストファイルには、svn:eol-style 属性に native という値を設定
しておけばよいという結論になりました。

次回のエントリでは、そもそもそういった問題が発生しないためには何をすればよかったのか?について書きたいと思います。

参考にしたURL:
http://advweb.seesaa.net/article/3074705.html
http://demo.zeera.jp/files/cubo/cubo-demo/document/01.Cubo-Manual/html/subversion/svn.advanced.props.html
http://cesare.seesaa.net/article/44146340.html

2011年1月18日火曜日

データの直列化(Data::Dumper vs JSON::XS vs Storable)

Perlのデータ構造を直列化(Serialize)したくて、いくつかのモジュールを試してみたので、その結果をこのエントリに記録しておきます。

そもそも今回やりたかったことは、
  1. Perlのハッシュを直列化する
  2. ただし、ハッシュの値はスカラーまたは配列のどちらか
  3. ある事情により、ハッシュキーでソートしてから直列化したい 
だったのですが、データ構造が複雑ではなかったので、最初は自分で直列化していました。でも、ソースがいまいちスッキリしなくて気持ち悪かったので、CPANモジュールに置き換えることにしたんです。

とは言っても、どのモジュールを使えばベストなのかで迷ったので、ベンチマークを取って、一番速いモジュールを採用することにしました。

use strict;
use warnings;
use Data::Dumper;
use JSON::XS;
use Storable 'nfreeze';
$Storable::canonical = 1;
use Benchmark qw(timethese  cmpthese);

my $hash = {
    'z' => 'z',
    'a' => 'a',
    'c' => [ '2','1'],
};

sub my_serialize {
    my $params = $hash;
    my $str = "{";
    foreach my $name ( sort(keys %{$params}) ) {
        if ( ref($params->{$name}) eq 'ARRAY' ) {
            $str .= "'$name'=>[";
            foreach my $value ( @{$params->{$name}} ) {
                $str .= "'$value',";
            }  
            $str .= '],';
        } else {
            $str .= "'$name'=>'$params->{$name}',";
        }  
    }  
    return $str . "}";
}

sub data_dumper {
    my $d = Data::Dumper->new([$hash]);
    return $d->Terse(1)->Indent(0)->Sortkeys(1)->Dump;
}

sub json_xs {
        return JSON::XS->new->canonical->encode($hash);
}

sub storable {
    return nfreeze($hash);
}

my $count = 1000000;
my $comp = timethese(
    $count,
    {
        my_serialize => \&my_serialize,
        data_dumper => \&data_dumper,
        jspn_xs => \&json_xs,
        storable => \&storable,
    }
);

cmpthese $comp;

上記のようなベンチマーク用のコードを用意して、実行してみると、、、


Benchmark: timing 1000000 iterations of data_dumper, jspn_xs, my_serialize, storable...
data_dumper: 43 wallclock secs (41.97 usr +  0.08 sys = 42.05 CPU) @ 23781.21/s (n=1000000)
   jspn_xs:  3 wallclock secs ( 2.04 usr +  0.00 sys =  2.04 CPU) @ 490196.08/s (n=1000000)
my_serialize:  6 wallclock secs ( 5.69 usr +  0.00 sys =  5.69 CPU) @ 175746.92/s (n=1000000)
  storable: 42 wallclock secs (29.85 usr + 12.66 sys = 42.51 CPU) @ 23523.88/s (n=1000000)
                 Rate     storable  data_dumper my_serialize      jspn_xs
storable      23524/s           --          -1%         -87%         -95%
data_dumper   23781/s           1%           --         -86%         -95%
my_serialize 175747/s         647%         639%           --         -64%
jspn_xs      490196/s        1984%        1961%         179%           --

上記のような結果になりました。つまり、今回の条件下では、JSON::XSがData::DumperやStorableの20倍高速に動作することがわかりました。

という訳で、JSON::XSを使うことに決定。

2011年1月13日木曜日

DBIx::Class(DBIC)で既存のメソッドを上書きしたい。けどできない。

自分の開発しているWebアプリケーションは、DBIx::Class(DBIC)を使っているのですが、少しハマったので、調査したことを書きます。

そもそもやりたかったことは、、、
テーブルに対して、DBIC経由でUPDATEなどのDML文が発行された時に、
自動的にmemcachedに保存されているキャッシュを削除させたかったのです。
そうすることで、キャッシュの削除を透過的に行うことができて、嬉しいかなと思いました。

そこで、該当テーブルのupdateメソッドを上書きすることにしました。

package DBIC::DB1::Result::Table;

use strict;
use warnings;
use base 'DBIx::Class';

(中略)

sub update {
    my $self = shift;
    $self->next::method( @_ );
    $self->delete_cache();
}

1;


こんな感じでupdateメソッドを上書きしておけば、UPDATEのタイミングでdelete_cache()も実行されるハズと思っていたのですが、上手くいかない場合がありました。

#!/usr/bin/perl

$ENV{'DBIC_TRACE'} = 1;

use strict;
use warnings;
use lib '/MyAPP/lib/
use YAML::Syck;
use DBIC::DB1;
use utf8;

$YAML::Syck::ImplicitUnicode = 1;
my $configfile = '/MyAPP/MyAPP.yml';
my $c = YAML::Syck::LoadFile($configfile);

my $row = DBIC::DB1->connect(
    @{$c->{'Model::DB1'}->{connect_info}}
)->resultset('Table')->find(1);
$row->update({ col1 => '' });        #上書きしたメソッドが実行される

my $rows = DBIC::DB1->connect(
    @{$c->{'Model::DB1'}->{connect_info}}
)->resultset('Table')->search({ item_id => { 'in' => ['1','2'] } });
$rows->update({ col1 => '' });       #上書きしたメソッドが実行されない


上記のテストスクリプトを実行すると、findメソッドなどで取得した単一レコードに対してupdateを実行すると、上書きしたupdateメソッドが実行されるのですが、searchメソッドなどで取得した複数行のレコードに対してupdateメソッドを発行した場合、上書きしたupdateメソッド(DBIC::DB1::Result::Table内のupdateメソッド)が呼ばれていないことがわかりました。

そこで、findおよびsearchメソッドが返すオブジェクトを調べてみることにしました。
その結果、findが返すのは、DBIC::DB1::Result::Tableオブジェクト、一方、searchが返すのは、DBIx::Class::ResultSetでした。

そこで、DBIx::Class::ResultSetをGoogle検索したところ、DBIx::Class::Manual::Cookbook - 様々なレシピというページが見つかったので、ここを参考にして次のようなパッケージを新しく作成しました。

package DBIC::DB1::ResultSet::Table;

use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub update {
    my $self = shift;
    $self->next::method( @_ );
    $self->delete_cache();
}

1;


そして先ほどのテストスクリプトを実行すると。。。出来た!うまくいきました。
つまり、単一レコード(Result)に対するupdateメソッドと複数レコード(ResultSet)に対するupdateメソッドは、それぞれ別に定義してあげないといけなかったのでした。

なお、先ほどのDBIx::Class::Manual::Cookbook - 様々なレシピというページには、
パッケージ DBIC::DB1::Result::Table に対して、以下のコードを追加するよう書かれていますが、これはなくてもOKでした。

__PACKAGE__->resultset_class('DBIC::DB1::ResultSet::Table');

最初は理由がわからなかったのですが、色々調べてみると自分の環境では、パッケージ DBIC::DB1において、

__PACKAGE__->load_namespaces;

しているからでした。
これをしておくと、DB1/Result、DB1/ResultSet 以下に配置したパッケージが自動的に認識されるみたいです。こりゃ便利。

以下参考にしたURLです。
http://www.mail-archive.com/dbix-class@lists.scsys.co.uk/msg04797.html
http://search.cpan.org/~arcanez/DBIx-Class-0.08126/lib/DBIx/Class/Schema.pm#load_namespaces
http://gihyo.jp/dev/serial/01/perl-hackers-hub/000303