l'essentiel est invisible pour les yeux

Monday, May 21, 2007

[Ruby and Erlang] そろそろRBridge::Erlangについて一言いっておくか

RBridge::Erlang


を作った。

弾さんもErlangについて一言言うなどここ最近のErlangの盛り上がりは凄い。

Erlangを1週間ほどさわってみて思ったのは次の二つ。
  • Erlangの軽量なプロセスをRubyから扱いたい。
  • Erlang/OTPが実績もありすばらしい(そう)なのでRubyからその恩恵にあずかる


とりあえず、はじめてのErlanモジュール作成ということで悩んだ結果、RubyからErlangのメソッドを呼び出すRBridge::Erlangを作った。現在同期呼び出しのみサポートしている。ErlangとRuby間の通信におけるプロトコルはJSON-RPCである。


ダウンロード
rbridge-0.0.1.tar.gz

デモの動かし方。
コンパイル

% tar xvzf rbridge-0.0.1.tar.gz
% cd rbridge-0.0.1.tar.gz
% make


Erlang側のサーバを起動する。

% erl
1> c(geometry).
2> geometry:start().


後は、Ruby側から呼び出すだけ。

require 'rbridge'
geo = RBridge::Erlang.new(:geometry, "127.0.0.1")
geo.area({:x => 100, :y => 200}) # => 20000

ポートの変更
デフォルトのポートは3793番を使用しているので、使用している際は次のファイルのPortの設定を変更してください。その際は、Ruby側のRBridge::Erlang.newの第三引数に指定したポートを渡す必要あり。

% cat server_root/conf/httpd.conf
ServerName localhost
ServerRoot server_root
DocumentRoot server_root/htdocs
Port 3793
Modules mod_alias mod_auth mod_jsonrpc mod_actions mod_cgi mod_responsecontrol mod_trace mod_range mod_head mod_include mod_dir mod_get mod_log mod_disk_log
DirectoryIndex index.html
JsonRpcAlias /rpc
ErrorLog logs/error_log
TransferLog logs/access_log
SecurityLog logs/security_log


Erlang側のソース
関数を定義して、rbridge:start関数の引数にリストで渡すだけ。
rbridge:startの第一引数はお約束。?MODULE(現在のモジュール名)を渡してください。
第二引数にservice_procレコードのリストを渡す。idempotent=true or falseはGETリクエストによるJSON-RPCを受け付けるかどうか。POSTは現在実装していないのでtrueにしてください。paramsはサービスの引数を定義する。引数のキーの名前と型("bit" | "num" | "str" | "arr" | "obj" | "any" | nil)のいずれかを指定します。


-module(geometry).
-export([area/2, start/0, stop/0]).

-include("include/rbridge.hrl").

%% Get area
area(X, Y) when is_number(X), is_number(Y) ->
X * Y.

%% service_proc#type ::= "bit" | "num" | "str" | "arr" | "obj" | "any" | nil
start() ->
rbridge:start(?MODULE,
[#service_proc{name = <<"area">>,
idempotent=true,
params=[#service_proc_param{name = <<"x">>, type = <<"num">>},
#service_proc_param{name = <<"y">>, type = <<"num">>}]}]).

% Stop RBrige server
stop() ->
rbridge:stop().



TODO:
「RubyからErlang/OTPの恩恵をどうやって授かるか?」について色々と考えてみる。


追記
id:sfujiwaraさんがPerlから呼び出すコードを書いてくれています。
[Perl][Erlang] RBridge::Erlang を Perl から呼ぶ

Comet&Erlang勉強会@ドリコムのお知らせとRails勉強会@東京

Rails勉強会@東京 第18回に参加した。
参加したセッションは
・ActiveRecord::QueryCacheとMagic Multi Connection
舞波さん・最速配信研究会さん・瀧内さん・Yuguiさん(順不同)といったメンバーが参加されていた。細かいところでの主な論点はRailsのパーフォーマンスの最適化について。大きな部分(Page Cache)での最適化は使いどころが難しい→小さな部分での最適化を積もらせて全体としてパフォーマンスを上げたいが、Railsはそのへんが弱いという話。

・ActiveRecord::QueryCacheは関連についてもキャッシュを管理できるようにしたい。(舞波)
・ActiveRecord::with_scopeはRails2.0で内部的に使用されるようになり外部に公開されなくなる。(日本人は困る人大勢。。)
・バウンスメール処理や大量のメール配信の問題。(ミドルウェアで処理するかDRb)
・ERBが遅い。Cで書かれたERB実装を使用すると早くなる。Erubisとか。
・Routeが複雑&高機能すぎて遅い。ActionCacheは多用するのでRouteが遅いと困る。Routeは基本的な機能だけで十分なのでCで実装したいなぁ。(瀧内)


ShootingStarとErlang
に参加した。
・ShootingStar2.0が出ました。
・クラスタリング機能をサポートしています。
・隠しAPIがいっぱいあるよ。問い合わせが多いと瀧内さんが大変そうなのでソース読んでくださいというのがいいのかな?
・ShootingStarのプロジェクトに新たにDoc Writeの安藤さんが追加されたので急速にドキュメント周りが整備されるかも。
・ErlangはRBridge::Erangを紹介。
・メニーコアが進む現在においてErlang+Rubyでどのような可能性があるかについて話し合う。
・画像処理や3Dなどのギリギリまでパフォーマンスが要求されるところでErlangは実用レベルで耐えれるだろうか。
・Erlangの処理系のソース読んで仕組みを理解しないと安心できない。
・Erlangは本当に早いの?


Comet&Erlang勉強会@ドリコム
日時: 5月27日
時間: 13時~
場所: ドリコム 

数少ないErlang勉強会がドリコムで開かれます。
Cometと絡めてC10K問題やErlang/OTPやHiPEについて実際に手を動かしながらプログラムを書くセッションがやりたいなぁ。

---------------------------------------------------
懇親会

結婚されてたとは知らなかったです。

だって、24時間オンラインだから。
どんな生活かと思いきや9時出社20時帰宅してご飯。22時にまた出社。始発で帰宅。一日二度顔合わせれば十分・・・凄い。

数年内にアメリカに渡るので、24時間アドレナリンでっぱなし・トランスしっぱなしとなるような、未来に残る研究開発・サービスつくりを一緒にやりたいです。同じ職場で働く日を楽しみにしてます。 > Hello, world!

Thursday, May 17, 2007

[Rails] Swiftiplyのアーキテクチャとベンチマーク

Swiftply(日本語で何て読む?)はRubyで書かれたHTTPに特化したプロキシフレームワークである。ApacheのModProxyBalancerやHAProxyなどのプロキシといったフレームワークと大きく異なる点として、プロキシとアプリケーションサーバの関係が上げられる。Swiftplyではブラウザからのリクエストを受け付けるSwiftplyサーバとリクエストを処理する複数のSwifplyクライアントから構成される。Swiftplyクライアントは全て同じアドレス・ポートでリクエストを待ち受ける。

Swiftiplyサーバーの設定ファイルの作成
config/swiftiply_config.ymlにサーバの設定ファイルを設置する。(パスは任意でOK)


cluster_address: 0.0.0.0
cluster_port: 80
#daemonize: true
timeout: 3
map:
- incoming:
- www.yourdomain.com
- www2.yourdomain.com
outgoing: 127.0.0.1:8000
default: true


設定項目
  • cluster_address: ブラウザからのトラヒックをListenするアドレス (String)
  • cluster_port: ブラウザからのトラヒックをListenするポート (Integer)
  • daemonize: デーモンで起動する時はtrue (true or false)
  • timeout: Swiftiplyがブラウザからのリクエストを保持する秒数(sec)。expireしたコネクションの場合は503が返される。 (Integer)
  • map: ドメイン(or IP)別にリクエストの宛先を定義するセクション (Hash)
  • incoming: ホスト名を指定する。配列で複数指定することも可能。(String or Array)
  • outgoing: リクエストの宛先を指定する。Swiftplyサーバからのリクエストは同じアドレス&ポートで待ち受ける複数のSwiftplyクライアントによって処理されるのでここで複数の宛先を指定する必要はない。(String)
  • default: ブラウザからのリクエストがどのincomingにもマッチしなかった際にそのパターンが適用される。(true or false)

Swiftply Mongrel単体での起動
swiftiply-X.X.X/bin/mongrel_railsというMongrelの起動スクリプトが用意されているのでそのスクリプトを使用する。

% SWIFT=1 /usr/bin/mongrel_rails start -e production -p 10000
% EVENT=1 /usr/bin/mongrel_rails start -e production -p 10000

SWIFTまたはEVENTという環境変数を指定することでMongrelをそれぞれのモードで起動できる。SWIFTモードではSwiftplyサーバを立ち上げていないと動作しない。

クラスターとして使用する
初めにサーバを起動する。
初めに示した上記の設定ファイルを使用した場合、80番ポートと8000番ポートがListen状態になる。

% swiftiply -c config/swiftiply_config.yml

8000番ポートに転送されたリクエストのデータを処理するMongrelを起動する。swiftiply_mongrel_raiilsというスクリプトが用意されているのでこれを利用する。(環境は強制的にenvironmentになる)

$ swiftiply_mongrel_rails start -n 10 -p 8000 -d


プロセスIDファイルが#{RAILS_ROOT}/log/dog#{n}.pidに生成されるので再起動する時はmongrel_railsスクリプトを使用する。(swiftiply_mongrel_railsは現在起動のみ)

% mongrel_rails restart -P log/dog1.pid


Apache + Mongrel Cluster環境とSwiftiplyでabを使用しベンチマークを取得した。
リクエスト数を500で固定し同時接続数を10~200まで上げて静的ページと動的ページのRequest per secoundの値を比較した。
CPUはIntel(R) Celeron(R) CPU 2.66GHz, メモリは2GB構成。



静的ページにおいてはかなり高速化している。

TODO
ソースリーディングとアーキテクチャについて理解を深める。
mongrel_clusterのように簡単に扱えるスクリプトを作成し実用で使えるかどうか検討する。

Wednesday, May 16, 2007

[Ruby] Ruby/EventMachineでネットワークプログラミング

Ruby/EventMachine

Ruby/EventMachineはシングルスレッドで高速に動作するネットワークプログラミングのためのライブラリでIPレイヤを扱うコア部分はC++で実装されている。(Pure Ruby版も用意されている。)

イベントドリブンなアーキテクチャで実装されており、ユーザはIP層を全くほとんど意識することなくプロトコルの実装に集中できるように設計されている。ユーザが書くソースコードでは決まったクラスを継承し決まったメソッドをオーバーライトするだけでよい。

現在の実装は、Unix系のシステムを使用している場合はslect(2)システムコールで実装されている。将来のバージョンではepoll(4)に置換したいとのこと。

文字数を返すサーバ


require 'rubygems'
require 'eventmachine'

module CharacterCount < EventMachine::Connection
def post_init
puts "Received a new connection"
@data = ""
@line_count = 0
end

def receive_data(data)
puts "Received data: #{data}"
send_data "#{data.length} (characters)\r\n"
close_connection_after_writing
end
end

EventMachine::run do
host, port = "127.0.0.1", 3793
EventMachine.start_server(host, port, CharacterCount)
puts "Now accepting connections on address #{host}, port #{port}"
EventMachine.add_periodic_timer(10) {$stderr.write '*'}
end



EventMachine::Connection#post_initがクライアントからの接続を受け付けた時に呼び出され、受信したデータはEventMachine::Connection#received_dataの引数に渡される。post_initはEventMachine内のループでリクエストを受け付けるたびにクラスがインスタンス化され呼び出される。ユーザのコード内でこのクラスをインスタンス化することは決してない。

日付を返すメソッド(get_date)を定義したサーバ。

require 'rubygems'
require 'eventmachine'

# This is simple protocol which get date.
class GetDateProtocol < EventMachine::Connection
def receive_data(data)
puts "Received data: #{data}"
if pattern = data.split("get_date: ")[1]
send_data Time.now.strftime(pattern)
elsif data.index("exit") == 0
close_connection_after_writing
else
send_data "ERROR: This method isn't implemented.\r\n"
end
end

def unbind
puts "Connection is closed."
end
end

EventMachine::run do
host, port = "127.0.0.1", 3793
EventMachine.start_server(host, port, GetDateProtocol)
puts "Now accepting connections on address #{host}, port #{port}"
end

unbindは接続が切断された時に呼び出される。その他のメソッドについてはeventmachin.rbを参照。

ネットワーク部分の大部分がC++で書かれており高速に動作しかつ、プロトコルの実装に集中することができる。Ruby/EventMachineのドキュメント中ではC10K問題がライブラリを作るきっかけとなったことについて触れられているが、大本命はやはりErlangかな。

Wednesday, May 09, 2007

[Erlang] 実行時間計測

Eralngで簡単な実行時間計測を行うモジュールを作った。

statistics(runtime), statistics(wall_clock)の一度目の呼び出しはそれぞれCPU時間・実経過時間を0に初期化する。返り値は次の要素から成るタプル(引数にwall_clockを指定)で構成される。


{Total_Wallclock_Time, Wallclock_Time_Since_Last_Call}


prof.erl

-module(prof).
-export([start/0, stop/1, test/0]).

%% prof:start() -> B.
%% Start the profiler.
start() ->
statistics(runtime),
statistics(wall_clock),
{_, T1} = statistics(runtime),
{_, T2} = statistics(wall_clock),
{ok, {T1, T2}}.

%% return -> {Runtime, Wall-clock time}
stop(B) ->
case B of
{ok, {OldT1, OldT2}} ->
{_, T1} = statistics(runtime),
{_, T2} = statistics(wall_clock),
{T1- OldT1, T2 - OldT2};
Other ->
{error, Other}
end.

%% test()
%% Profile test
test() ->
B = start(),
lists:foreach(fun (X) -> math:pow(X, 10) end, lists:seq(1, 1000000)),
{Time1, Time2} = stop(B),
io:format("time=~p (~p) msec~n", [Time1, Time2]).



実行結果

1> c(prof).
{ok, prof}
2> prof:test().
time=300 (1194) msec
ok
3> prof:test().
time=370 (1238) msec
ok


症状
プロセス数を増やしすぎて、CPU時間が短いのに実経過時間が極度に大きくなる場合は、物理メモリが不足しスワップが発生しているサインである。

Tuesday, May 08, 2007

株主への理解

ドリコム内藤社長と対談13(株式分割と資本政策)

とりあえず、「バフェットからの手紙」第三章 普通株を読み、もう一度株価と株式分割について熟考していただきたい。

現状の株主戦略のままでは、中長期的なオーナーの考えを持って株式を購入する投資家を獲得するのは無理である。投機家により株式の売買が繰り返され、企業の内在価値を反映しない株価が続く限り中長期的な株主は損害を被ることになる。

ウォーレンバフェットが示すコーポレートガバナンスを引用すれば、現状の第二形態から第三形態にシフトする改革の時ではないだろうか。

Friday, May 04, 2007

[Erlang] listsモジュールの関数一覧 1



Programming Erlang(PDF)を購入した。

形式コメントからドキュメントを生成するErlang Docのような物が存在するかも知れないが、知らないのでリストに関する操作を提供するモジュール/usr/lib/erlang/lib/stdlib-1.14.2/src/lists.erlからリスト関数を一つずつ見ていきここにまとめることにした。Erlangの規約にのっとり関数名/Arity(関数の引数の数)で示す。

追記
Programming ErlangのAppendix A "Documenting Your Program"を参照。


また、Erlangではモジュールは自作のモジュールも含めてコンパイルされる。標準ライブラリとして提供されているモジュールは/usr/lib/erlang/lib/stdlib-1.14.2/ebin/いかに(環境によって異なる).beam(Bogdan's Erlang Absctract Machine)拡張子で保存されている。

Tips
Erlangインタプリタ上で一度束縛した変数の束縛を解除するにはf().関数を使用する。

listsモジュールの関数一覧

append/2
二つのリストを連結する。

13> lists:append([1,2], [2,3]).
[1,2,2,3]


subtract/2
集合Aから集合Bを引いた差集合を返す。

14> lists:subtract([1,2], [2,3]).
[1]


reverse/1
リストを逆順にする。

15> lists:reverse([1,2,3,4]).
[4,3,2,1]


nth/2
リストの先頭からN番目の要素を取り出す。

6> lists:nth(3,["UIE", "Japan", "Inc"]).
"Inc"


nthtail/2
リストの先頭N要素を取り除いた残りのリストを返す。

20> lists:nthtail(1, ["UIE", "Japan", "Inc"]).
["Japan","Inc"]


prefix/2
リストAがリストBの先頭要素と等しければtrue, 異なればfalseを返す。

35> lists:prefix(["UIE"], ["UIE", "Japan", "Inc"]).
true
36> lists:prefix(["UIE", "Japan"], ["UIE", "Japan", "Inc"]).
true


suffix/2
リストAの末尾要素がリストBの末尾要素と等しければtrue, 異なればfalseを返す。

38> lists:suffix(["Inc"], ["UIE", "Japan", "Inc"]).
true


last/2
配列の最後の要素を返す。

44> lists:last(["UIE", "Japan", "Inc"]).
"Inc"


seq/2 :: seq(MIN, MAX)
整数値MINからMAXまでの順序数を返す。

45> lists:seq(10,20).
[10,11,12,13,14,15,16,17,18,19,20]


seq/3 :: seq(MIN, MAX, Incr)
整数値MINからMAXまでの順序数を返すが増分値を整数Incrの値とする。

48> lists:seq(10,20,3).
[10,13,16,19]


sum/1
リストの合計を返す。

51> lists:sum([10,20,30,40]).
100


duplicate/2 :: duplicate(N,X) XをN個持つリストを返す。

55> lists:duplicate(10, 2).
[2,2,2,2,2,2,2,2,2,2]


min/1
リスト中の最小値を返す。

56> lists:min([1,2,3,4,5]).
1


max/1
リスト中の最大値を返す。

57> lists:min([1,2,3,4,5]).
5


list/2
リスト中の部分リストを返す。(開始要素は先頭要素)

60> lists:sublist([1,2,3,4,5], 3).
[1,2,3]


list/3 :: sublist(List, Start, Length)
リスト中の部分リスト(Start..Start+Length)を抽出する。

58> lists:sublist([1,2,3,4,5], 2, 3).
[2,3,4]


delete/2 :: delete(Item, List) -> List
リスト中からItemを削除する。

62> lists:delete(3, [1,2,3,4,5]).
[1,2,4,5]


zip/2
引数に渡した各リスト(要素数は2つ)の要素からなるタプルのリストを返す。
 
63> lists:zip([1,3,5,7], [2,4,6,8]).
[{1,2},{3,4},{5,6},{7,8}]


unzip/2
zip/2と可逆の操作を行い結果をタプルで返す。

64> lists:unzip([{1,2},{3,4},{5,6},{7,8}]).
{[1,3,5,7],[2,4,6,8]}


zip/3
引数中のリストからタプルの組を生成してリストで返す。

69> lists:zip3([1,4,7], [2,5,8], [3,6,9]).
[{1,2,3},{4,5,6},{7,8,9}]


unzip/3
zip/3と可逆の操作を行い結果をタプルで返す。

70> lists:unzip3([{1,2,3},{4,5,6},{7,8,9}]).
{[1,4,7],[2,5,8],[3,6,9]}


zipwith/3 :: zipwith(F, X, Y)
XとYの要素を順に抽出しF(X1,Y1), F(X2,Y2)...なるリストを返す。

76> Mod = fun(X, Y) -> X * Y end.
#Fun
77> lists:zip(Mod, [1,3,5,7], [2,4,6,8]).
[2,12,30,56]


zipwith3/3 :: zipwith(F, X, Y, Z)
X, Y, Zの要素を順に抽出しF(X1,Y1, Z1), F(X2,Y2, Z2)...なるリストを返す。

82> Volume = fun(X, Y,Z) -> X * Y * Z end.
#Fun
83> lists:zipwith3(Volume, [1,4,7], [2,5,8], [3,6,9]).
[6,120,504]


まだまだあるが、長くなったのでとりあえずココまで。

Thursday, May 03, 2007

GW中に読む4冊

「Life is beautiful: 21世紀の錬金術:Web2.0バブルで一儲けする方法」が人気エントリになることや、このブログでもGoogle Analysticsでの解析結果でコンテンツ上位5位以内に「IPOから学んだ事柄」が必ず入っており、IPOという言葉がITベンチャーで働く者にとって考えさせられる大きな問題となっていることはほぼ間違いない。

真にコーポレートガバナンスを理解しオーナーの利益と従業員含めた企業の利益の両方を追求するための哲学なき経営者が上場すべきでは無いと思う。そこでGW中に読む本として、オハマの賢人と敬愛を込めて呼ばれる、ウォーレン・バフェット氏に関する本を中心として4冊を購入した。金融・会計・経済と言った(私にとっては)専門分野とは異なる分野のため、多くの専門用語に時間を割かれながらもどれも非常に勉強になる本である。

尊敬できる経営者が運営する「企業を買う」(数十年単位の)バリュー投資により富を築いたバフェット氏の本と世界トップの頭脳を集めコンピュータ武装した天才集団LTCMによる裁定取引による荒稼ぎから破綻まで。この二冊を読み比べるだけでも学ぶべきことは非常に多い。









バークシャー・ハザウェイ社の年次報告書にて株主に向けて書かれる投資家必読ともいわれる「会長からの手紙」。それを聞きたいがばかりにバークシャーの株主になる人もいるというから驚きだ。その「会長からの手紙」を一冊にまとめたのが「バフェットからの手紙」であるが会計に関する用語が多く出てくるため、まずは上に示した順序で「最強の投資家バフェット」を読んでから、時間をかけてじっくりと「バフェットからの手紙」を購読するのがおすすめ。

バフェットからの手紙は、哲学書として一生手元においておき繰り返し参照したい一冊である。

Wednesday, April 25, 2007

Web2.0時代の起業

21世紀の錬金術:Web2.0バブルで一儲けする方法

学生4年間で学んだことそのままです。
外部の人間や経験ある人間から見れば周到に仕組まれた道を進んでいることが明白でも、内部で働く人間には気づきにくいこと、そして上場が簡単にできてしまう日本の新興市場が問題でしょうか。もし、Web2.0ブームに乗ってアイデア一つ・WEBサービス一本で起業するならばExit戦略はM&A(買収)されることに見据えるのが普通だろう。

関連
ポールグレアムはハッカーでなくて

Tuesday, April 24, 2007

[Rails] SlowQueryLoggerでRSpecのSpecファイルも実行するように変更

specの場合はmodelテストのほうが良さげ

いつかやろうと放置プレイしていたところを指摘してもらったので実装しました。

SlowQueryLogger


Index: slow_query_logger.rb
===================================================================
--- slow_query_logger.rb (revision 5)
+++ slow_query_logger.rb (revision 6)
@@ -116,10 +116,11 @@

if __FILE__ == $0
# get all controlelrs name
- controller_pattern = Pathname.new(File.join(RAILS_ROOT, 'app', 'controllers', '*.rb'))
- controllers_list = FileList.new(controller_pattern.to_s)
- controllers_list.map! { |path| File.basename(path).sub('.rb', '') }
-
+ controller_pattern = Pathname.new(File.join(RAILS_ROOT, 'app', 'controllers', '*.rb'))
+ model_pattern = Pathname.new(File.join(RAILS_ROOT, 'app', 'models', '*.rb'))
+ controllers_list = FileList.new(controller_pattern.to_s).map! { |path| File.basename(path).sub('.rb', '') }
+ models_list = FileList.new(model_pattern.to_s).map! { |path| File.basename(path).sub('.rb', '') }
+
# Clear the slow query log
ActiveRecord::Base.slow_query_logger.clear

@@ -127,14 +128,19 @@
unittest_functional_test_file = File.join(RAILS_ROOT, 'test', 'functional', "#{controller}_test.rb")
rspec_controller_spec_file = File.join(RAILS_ROOT, 'spec', 'controllers', "#{controller}_spec.rb")

- # check for unit test or spec file of rspec
+ # checks for unit test or spec file of rspec
if File.exists?(unittest_functional_test_file)
require unittest_functional_test_file
elsif File.exists?(rspec_controller_spec_file)
require rspec_controller_spec_file
-# rails_spec = Pathname.new(File.join(RAILS_ROOT, 'script', 'rails_spec')).cleanpath.to_s
-# cmd = rails_spec + " " + Pathname.new(rspec_controller_spec_file).cleanpath.to_s
-# system cmd
end
+
+ # checks for file of RSpec model's spec
end
+
+ # Checks for file of RSpec model's spec
+ models_list.each do |model|
+ rspec_model_spec_file = File.join(RAILS_ROOT, 'spec', 'models', "#{model}_spec.rb")
+ require rspec_model_spec_file if File.exists?(rspec_model_spec_file)
+ end
end



modelのSpecファイルが存在する場合には、コントローラのSpecと合わせてそちらも実行するように変更しました。RSpecでBDDを行うと、主たるロジックは全てモデルに実装してコントローラからはモデル中に定義したメソッドを呼び出すだけになる。

先日実装したActiveRecord::QueryCacheでキャッシュ戦略を管理する場合にも、モデル中のメソッド内で呼び出しているAR#find一つ一つに対してキャッシュ指定することになる。

これは細かい指定ができていいかもしれないが、モデルのメソッド(でいくつかの複雑で重いSQLを実行し加工した)結果自体をキャッシュしたいことがある。ARと直接関連するわけではないので、キャッシュの削除戦略が複雑になる。現在考え中。

こんなの?

class IndexController < ApplicationController::Base
def index
@me = session[:user]
@my_loves_friends = cache(:key => "index_controller_loves_friends", :expire => 10.minutes) do
@me.find_loves_users(:limit => 10)
end
end
end

Monday, April 23, 2007

[Rails] ActiveRecord::QueryCacheにキャッシュの削除に関する指定を実装した。

はてぶやdel.icio.usで意見をもらったので、ActiveRecord::QueryCacheにキャッシュの削除に関する設定をできるように実装した。

使用方法
:expire_methodsでキャッシュを削除するメソッドを指定する。指定できるメソッドは、ActiveRecord#after_○○のメソッド名。


class Person
query_cache :expiry => 3.minutes, :expire_methods => [:create, :update, :destroy]
end


実装

def query_cache(options)
options.assert_valid_keys([:expiry, :expire_methods])
write_inheritable_attribute('query_cache_expiry', options[:expiry] || 0)
[options[:expire_methods]].flatten.each do |method|
__send__("after_#{method}") { __send__(:delete_all_query_cache) }
end unless options[:expire_methods].nil?
end


memcachedを本格的に運用するためには超えなければいけない壁があるんだけど、それは面白そうなので社内勉強会ネタとして開催してみよう。

Thanks for コメントくれた人

Sunday, April 22, 2007

[Rails] ActiveRecord::QueryCacheを実装した。CacheStoreにはmemcachedを使用。

ActiveRecord::QueryCache

ActiveRecordの富豪っぷりに困っている。キャッシュの仕組みが用意されているみたいだが過去の遺物となり使用されていない様子。(See Class::ActiveRecord::QueryCache) だから作った。

ActiveRecordの富豪っぷり

  • ActiveRecord#findではフレーム関数を大量に呼び出し深いスタックを生成する。
  • 毎度引数+同じ値で呼び出されるのに毎度SQLの生成を行う。

そこでキャッシュ戦略でも比較的基本的な、クエリ単位でのキャッシュをおこなうクエリキャッシュとしてActiveRecord::QueryCacheを実装した。引数をキーにしてキャッシュを保存するため、SQLの生成も行わず高速に結果をフェッチすることができる。今後は、エンタープライズでのO/R Mapplerのキャッシュ戦略などを調べてもう少し拡張していく予定。

ActiveRecord::QueryCache

仕様
  • SQL単位ではなくActiveRecord#findに渡された引数をキーとしてキャッシュを保存する。
  • キャッシュの保存先にはmemcachedのみ対応。(2007/4/22現在)
  • メソッド単位でキャッシュのexpiryを設定することはできない。クラス単位で指定する。(改良するかも)


インストール
% sudo apt-get memcached
% sudo gem install memcached-client
% svn co http://shindaita.stiq.net/svn/cache_on_rails/query_cache.rb /path/to/raisapp/lib


Railsで使用する場合
# config/enviroment.rb
require 'query_cache'
CACHE = MemCache.new 'localhost:11211', :namespace => 'rakuto.blogspot.com', , :multithread => true


サンプル
CACHE = MemCache.new 'localhost:11211', :namespace => 'rakuto.blogspot.com'

class Person < ActiveRecord::Base
query_cache :expiry => 10.minutes
end

p Person.find(:all, :conditions => "name like '%'", :cacheable => true)


キャッシュが有効だと引数からSQLを生成することも無く、DBへ参照問い合わせを行うことも無いため高速に結果をフェッチすることが可能である。

キャッシュを削除するメソッドは現在一つだけ用意している。ActiveRecord#delete_all_query_cacheメソッドを使用すると、そのクラスで使用しているキャッシュが全て削除される。(仕組み的にはキーを配列で記録しておいて、その値をフェッチしてキーに該当するキャッシュを全て削除する)

Person.delete_all_query_cache


ActiveRecordで作成, 更新 or 削除があったときにキャッシュを削除する指定を実装。
[Rails] ActiveRecord::QueryCacheにキャッシュの削除に関する指定を実装した。

エラー処理 (2007/4/23日追加)
memcached関連でエラーが発生した場合でもロギングして処理を続行する(通常通りDBへアクセスを行う)サイレンスモードを実装。デフォルトではtrueに設定。

class Person
query_cache :expiry => 3.minutes, :expire_methods => [:create, :update, :destroy], :silence => false
end


TODO
  • キャッシュ戦略についてもう少しバリエーションを増やしたい。
  • memcached以外のCacheStoreへの対応。
  • キャッシュの削除戦略が普通はどうするものなのかよくわからない。詳しい人教えてください。


Bug Fix

AR#findに第一引数(:all, :first)のみ渡した場合のbug fix. (Revision 8)


Saturday, April 21, 2007

[Rails] Changeset 6197でランダムなsession_keyを使用するようになった変更へのMobile on Railsの対応

こちらのChangeset 6197でRailsアプリ名を含むランダムなsession_keyを使用するようになったので、Mobile on Railsもそれに対応した。

Mobile on Rails

Index: workspace/mobile_on_rails/lib/mobile/controller.rb
===================================================================
--- workspace/mobile_on_rails/lib/mobile/controller.rb (revision 65)
+++ workspace/mobile_on_rails/lib/mobile/controller.rb (revision 66)
@@ -31,7 +31,7 @@

def rewrite_options_with_session_id(options)
if request && request.mobile?
- session_key = ::ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:session_key] || '_session_id'
+ session_key = self.class.read_inheritable_attribute("session_options")[0][:session_key] || '_session_id'
if request &amp;& request.session.session_id
options = options.dup
options.update session_key => request.session.session_id



Changeset 6184によるとRailsのデフォルトのSessionStoreとして新たにCookieベースのSessionStoreが実装された様子。Rails中でセッションを使用する場合、flashに代入するエラーメッセージやログイン後のユーザオブジェクトなどがほとんどのため、4kbyteの制限内に収めることができる。その場合は、PStoreよりもCookieStoreの方が実行速度が速いとの事。

Tuesday, April 17, 2007

SlowQueryLoggerのブックマークが

限りなく少ないのに笑った。

SlowQueryLogger
仕方が無いので自己リンク。

2はてぶ
1クリップ
8デリ

テストを実行してEXPLAIN情報からボトルネックとなるクエリをソース中で使用されている箇所とともに、書き出すアイデアは面白いと思ったのになぁ。。
VaryなCacheへの対応・複数DB対応・ボトルネック測定など組み込んで洗練させて、
Scaling Rails Suiteとして出せばもっとうける?

P.S
yuin元気かな?

Sunday, April 15, 2007

IPOから学んだ事柄

経営者像に正解は無いし実に色々と存在する。業種により成果をあげる経営者像は異なるだろうし、時代によっても変化するだろう。忠誠を激しく要求するようなスティーブジョブスのような経営者もいれば、ある意味無駄なルールは無視できるエッジのきいた人間を望むようなCEOもいる。

おそらくこれは経営者のバックグラウンドに依存するところが大きい。
UIEJの社長は営業出身でシリコンバレーで働きグリーンカードも持っている。そして、人脈や恩義・忠誠を非常に大事にしているように感じる。UIEのCEOは今でもプログラマであり型破りな意見を言う。相手が偉い人であったりクライアントであろうと自分の考えは突き通す人に見える。



ドリコムを辞めて1年になるがその間の変化はとても大きいようだ。
外野からの見物になるため、メディアを通じて誇張された認識なども含むかもしれない。しかし、「会社はファンのための物」と語るCEOは、外部ではなく社内にファンを作れているのだろうか?

上場企業で自由な社風という甘い言葉に誘惑されて入社した世間知らずの新卒の"ファン"などではなく、「CEOが示すビジョンを実現するためなら、倒れようとプログラムを書きつづける」というような明日の売り上げとなる技術を開発する優秀な技術者のファンを獲得できているのだろうか?

私がドリコムが一番失敗したと思うことはIPOではなく人事に対する散財だ。
経験が豊富な人間や優秀な技術者が転職したいと思える会社つくりではなく、なぜ新卒の採用なのか?魅力のある会社を作りプロモーションをかければ優秀な人間は自然と集まってくる。IPOで得た資金を散財しただけに見合う社員を集めれたのだろうか?その資金を優秀な技術者が集まる会社の風土作りへ当てていたらどういう社員が転職してきただろうか?

過去に成功したビジネスを捨てきることができず、売り上げが上がらない→営業力が足りないとの意思決定→PUSHの営業体制のため人材を整える→急速な人材確保のためにIPOで得た資金が大量に使われるというスパイラルにはまり、本来のコアコンピタンスであった技術や技術者が持つマインドに対するトップマネジメントの理解などどこかに忘れ去られているように感じる。

今の売り上げを確保するための社員だけでなく、明日の売り上げを作り上げる技術者が必要である。パイの合計が決まっている市場でゼロサムゲームをしていては企業の未来は無い。パイが無くなれば新たなパイがある市場に飛びつくことになる。

ドリコムに優秀な技術者がいるが、CEOの示すビジョンで彼らのマインドを魅了し引き止める事ができなくなった際には、優秀な技術者の大規模な流出が起こりえるかもしれない。そうなった時には、目先の売り上げに飛びつき、株式交換により外部の小さなベンチャーの買収を繰り返し、自社の営業力を使って売り上げを計上し上場させ有価証券売却益を本社の売り上げに付け替えるという図式しか考えられない。

私はそのような企業に何の魅力も感じない。

どうしても気になるもう一点が企業の情報統制の甘さである。
毎日が文化祭みたいな会社と書き、社内の写真やミーティングの内容などを赤裸々につづっている新卒社員のブログを時々拝見するが、まともな情報管理体制を引けているのか疑問である。

自由な社風で自分達が主役などという甘い言葉は、経験もあり実績もある優秀な社員が集まった上で初めて形になるものだろう。自由を履き違えてはいけない。

大和証券では新卒社員が5分遅刻した際に、上長がブチきれ机を蹴飛ばしその社員の胸ぐらをつかみかかり罵倒し、その後見せしめとしてその日一日中新卒社員で「遅刻に対して」というお題でMTGが開かれた話を友達から聞いた。新卒社員に対してこの上長が取った行動は正しかったと思う。


PS
私がIPOから学んだ事は、IPOをするために何をするかではなくIPOで得ることのできる資金で何をするかを見据えIPOすることだ。言われてみれば当たり前のことである。

写真は今日自宅に届いたテーブルの写真(写真は別のショッピングサイトのもの)。赤色がポイントとなっていてとてもカッコイイ。

追記
社内のマネジメント体制が確立していない企業が、なぜ企業にも大きな教育コストを割くことになる新卒の採用にこだわるのか?と考えた時の答えは、一つしか思いつかなかった。「新卒を採用しているという事」自体が急成長中の企業であるということを示すためのプロモーションだったのだろう。明らかにプロモーションすべき方向を誤ったように見える。企業のプロモーションも手がけた人事であっただけに、人事担当者の責任は重い。

Thursday, April 12, 2007

[Rails] Unit::TestかRSpecのSpecファイルを実行しボトルネックとなるクエリを書き出すSlowQueryLoggerを作った

久々に面白いモノを作った。

SlowQueryLogger

RailsではActiveRecordを利用してガンガン富豪プログラムを書きがちなため、適切にインデクスを張っていないとすぐに重たくなるので、Rails中で発行された遅いクエリをロギングするプログラムを作った。

ActiveRecordの参照系クエリを発行するメソッドをEXPLAINにより実行解析情報を取得しロギングしてから通常の参照クエリを発行するように拡張し、Rails付属のUnit::TestのfunctionalテストまたはRSpecのコントローラーのSpecファイルを実行する。最終的には、filesortか一時テーブルを使用しているクエリがログファイルに書き出される。

つまり、functional testを定義したファイルかRSpecのcontrollerのスペックファイルがあれば特に何も用意する必要はない。


現在MySQL限定だが、次のような情報もあわせてロギングするためSlow Query Logを見るよりもずっと解析が容易になる。

  • 参照問い合わせが呼び出されたソースファイルと行番号
  • 実行されたSQL
  • EXPLAIN情報


インストールとデプロイ
#{RAILS_ROOT}/script/いかに配置後実行するだけ。

% svn co http://shindaita.stiq.net/svn/slow_query_logger/
% mv slow_query_logger/slow_query_logger.rb /railsapp/script/
% chmod +x /railsapp/script/sloq_query_logger.rb

実行

% ./script/slow_querry_logger.rb

仕様
  • functional testとコントローラのRSpecファイルが両方存在する場合はfunctional testを実行する。
  • 実行解析情報のExtraフィールドにfilesort or temporaryが存在する時にロギングする。
  • 現在MySQLのみ。
  • 実行の度にログファイルは初期化される。


log/mysql_slow_query.logの形式
カラーで出力するとかはやらない。

/var/www/rails_app/config/../app/models/user.rb:250:in `find_hoge'
SQL: EXPLAIN SELECT entries.`id` AS t0_r0, entries.`user_id` AS t0_r1, entries.`title` AS t0_r2, entries.`body` AS t0_r3, entries.`created_on` AS t0_r4, entries.`updated_on` AS t0_r5, entries.`group_id` AS t0_r6, entries.`unread_flag_for_self` AS t0_r7, entries.`comments_num` AS t0_r8, entries.`deleted_at` AS t0_r9, entries.`deleted_flag` AS t0_r10, entries.`last_updated_on` AS t0_r11, entries.`last_comment_id` AS t0_r12, entries.`photo_key` AS t0_r13, groups.`id` AS t1_r0, groups.`name` AS t1_r1, groups.`owner_id` AS t1_r2, groups.`public_level` AS t1_r3, groups.`category_id` AS t1_r4, groups.`photo_id` AS t1_r5, groups.`introduction` AS t1_r6, groups.`created_on` AS t1_r7, groups.`updated_on` AS t1_r8, groups.`users_num` AS t1_r9, groups.`official_flag` AS t1_r10, groups.`entry_num` AS t1_r11, groups.`deleted_flag` AS t1_r12 FROM entries LEFT OUTER JOIN groups ON groups.id = entries.group_id WHERE (( entries.deleted_flag = false ) AND ( group_id IN (NULL) )) ORDER BY entries.updated_on DESC LIMIT 5
select_type : SIMPLE
key_len : 5
type : ref
id : 1
Extra : Using where; Using filesort
possible_keys : entries_group_id_index,index_entries_on_group_id_and_created_on
table : entries
rows : 23
ref : const
key : entries_group_id_index

/var/www/rails_app/config/../app/models/user.rb:260:in `find_hoge_hoge'
SQL: EXPLAIN SELECT events.`id` AS t0_r0, events.`group_id` AS t0_r1, events.`title` AS t0_r2, events.`date` AS t0_r3, events.`supplement_date` AS t0_r4, events.`location_id` AS t0_r5, events.`supplement_location` AS t0_r6, events.`user_id` AS t0_r7, events.`description` AS t0_r8, events.`created_on` AS t0_r9, events.`updated_on` AS t0_r10, events.`deleted_at` AS t0_r11, events.`deleted_flag` AS t0_r12, events.`comments_num` AS t0_r13, groups.`id` AS t1_r0, groups.`name` AS t1_r1, groups.`owner_id` AS t1_r2, groups.`public_level` AS t1_r3, groups.`category_id` AS t1_r4, groups.`photo_id` AS t1_r5, groups.`introduction` AS t1_r6, groups.`created_on` AS t1_r7, groups.`updated_on` AS t1_r8, groups.`users_num` AS t1_r9, groups.`official_flag` AS t1_r10, groups.`entry_num` AS t1_r11, groups.`deleted_flag` AS t1_r12 FROM events LEFT OUTER JOIN groups ON groups.id = events.group_id WHERE (( events.deleted_flag = false ) AND ( group_id IN (NULL) )) ORDER BY events.updated_on DESC LIMIT 5
select_type : SIMPLE
key_len : 5
type : ref
id : 1
Extra : Using where; Using filesort
possible_keys : index_events_on_group_id,index_events_on_deleted_flag_and_updated_on
table : events
rows : 1
ref : const
key : index_events_on_group_id


ソース中の51行目の以下の部分を変更すればロギングするクエリを変更できます。(Extraカラムに対する正規表現パターン指定)

LOGGING_PATERN_FOR_EXTRA_FIELD = /(filesort|temporary)/


SlowQuerryLogger使って適切にインデクス張って、ログファイルが出力されなくなるようにしましょう。

参考
5.2.1. EXPLAIN 構文(SELECT に関する情報の取得)
7.2.1. Optimizing Queries with EXPLAIN
第5章 MySQL の最適化
Chapter 7. Optimization

Wednesday, April 11, 2007

世田谷にある地中海料理屋DOMANI

通勤途中にあり引っ越してきた時から気になっていた海路ドマーニというお店に行って来ました。帰りに前を通る度にガーリックとオリーブオイルのとてもいい匂いがします。

シャルドネ (ボトル) 3990円


牛肉のカルパッチョ 970円


アサリのガーリックオイル 1890円


フキノトウとサザエのオイルパスタ 1600円
サザエの苦味をベースにしたオイルパスタ。


うにのクリームソースパスタ 1680円


バニラアイスにエスプレッソ 800円


どれもおいしかったけどせもりなのパスタにあったような感動が無かった。
また世田谷価格なのか価格設定が20%高い気がした。後、少し味が濃かった。

ただ一人でボトル一本あけてまだ頼もうとすると同じワインをグラス一杯分サービスしてくれた。こういう時いつもお店の人に覚えてもらえるので得だなと思う。

地中海料理でおいしかったのは京都のノルマンディがとてもおいしかった。もう随分と昔の気がするがBOCCA del VINOと共にまた行きたい。

渋谷のおすし屋

渋谷にあるすし善というお店に連れて行ってもらった。

常連さんを大切にするためマスメディア等での紹介は行っていないみたい。このようなお店はネットなどでは見つけられないため、常連として通っている人に連れて行って貰うしか知る術がありません。こんな名店に連れて行ってくれた方にとても感謝します。

どの品もとてもおいしかった。
何よりもうれしかったのが日本酒が豊富でとてもおいしい事とお酒がすすむおつまみをたくさん出してくれること。日本酒は酔鯨(高知)以外初めて飲むものばかりでした。酔鯨も大好きなので注文しました。

黒龍
石鎚
久平次
鯨酔(高知)

鯛(おろしポン酢)
ウニ(ご飯と共に)
タコ(スダチで)
イカ(生姜醤油 or わさび醤油)
とり貝(刺身とにぎり)
だし巻き(絶品) 
赤貝(刺身)
こはだ

まだまだ食べましたが忘れてしまいました。本当にどれも絶品でした。
出し巻きといえば京都の八条口にある佳辰というお店のうまきも絶品ですが、それを超えるぐらいおいしかった。

大将もとてもいい人です。
ここで一つラッキーえびすビールと呼ばれるえびっさんが鯛を両手に抱えているビールが存在することを知りました。大将が7年やっていてはじめて見たとのことなのでお目にかかれる機会はほとんど無いかもしれません。

また是非行きたいお店です。

Sunday, April 08, 2007

[ruby] 特異メソッドに別名をつけて退避する

特異メソッドをalias_methodを使用して別名をつけようとするとエラーになる。
だが、同じブロック内でrespond_to?(:find)を実行するとtrueが返される。


undefined method `find' for class `ActiveRecord::Base' (NameError)



module EffectiveRails
module ActiveRecord
def self.included(klass)
klass.send ClassMethods
klass.class_eval do
p respond_to?(:find) # => true
alias_method_chain :find, :explain
end
end

module ClassMethods
def find_with_explain(*args)
# hogehoge
find_without_explain(*args)
end
end
end
end

ActiveRecord::Base.class_eval do
include EffectiveRails::ActiveRecord
end



Rubyの少々ややこしいクラスとオブジェクトの仕組みによるものだが、特異メソッドに別名をつけて退避したい場合は、alias_method (_chain)メソッドではなくてaliasをinstance_evalのブロック内で使用するとよい。


module EffectiveRails
module ActiveRecord
def self.included(klass)
klass.send ClassMethods
klass.instance_eval do
alias :find_without_explain :find
alias :find :find_with_explain
end
end

module ClassMethods
def find_with_explain(*args)
# hogehoge
find_without_explain(*args)
end
end
end
end

ActiveRecord::Base.class_eval do
include EffectiveRails::ActiveRecord
end



同じような現象でハマッた方も多いのではないだろうか?

Monday, April 02, 2007

AppleとEMIの提携発表Webキャスト

2 April press conference

日本時間で言えば本日PM9時頃~かな。
ビートルズの配信契約の話ではなく、EMIがDRM撤廃を発表するとの事。
スペシャルライブも行われます。

追記
先ほど終了。
EMIアーティストのミュージックビデオもDRMフリーで提供されるほか、
2007年度はデジタルコンテンツの著作権が大きく変わりそうな年。年内にiTunes Storeの50%がDRMフリーかつ高音質で提供されると予測するスティーブジョブス。


ジョブズとEMI、DRMフリーダウンロードサービスを発表