<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>BPS株式会社 開発ブログ Beyond Perspective Solutions LTD. &#187; フレームワーク</title>
	<atom:link href="http://www.bpsinc.jp/blog/archives/category/framework/feed" rel="self" type="application/rss+xml" />
	<link>http://www.bpsinc.jp/blog</link>
	<description>BPS株式会社（Beyond Perspective Solutions）のプログラマによる技術・開発などに関してのブログです</description>
	<lastBuildDate>Wed, 20 Jul 2011 08:14:42 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.5</generator>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>CakePHP Containableビヘイビア</title>
		<link>http://www.bpsinc.jp/blog/archives/2299</link>
		<comments>http://www.bpsinc.jp/blog/archives/2299#comments</comments>
		<pubDate>Thu, 02 Sep 2010 08:49:08 +0000</pubDate>
		<dc:creator>shibachan</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[芝原]]></category>
		<category><![CDATA[Behavior]]></category>
		<category><![CDATA[Containable]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=2299</guid>
		<description><![CDATA[Containableビヘイビアをご存知でしょうか？
もし、使用していならばこれを機にぜひ導入してみてください。
公式ページによると
ContainableBehavior は CakePHP のコアの新機能です。このビ [...]]]></description>
			<content:encoded><![CDATA[<p>Containableビヘイビアをご存知でしょうか？<br />
もし、使用していならばこれを機にぜひ導入してみてください。</p>
<p><a href="http://book.cakephp.org/ja/view/474/Containable">公式ページ</a>によると</p>
<blockquote><p>ContainableBehavior は CakePHP のコアの新機能です。このビヘイビアは find を実行するときに関連したモデルを選別したり限定したりするために使用します。コンテイナブル(<em>Containable</em>)は、データベース中の不要なものを削減し、アプリケーションの速度やパフォーマンスを改善します。このクラスを使うと、ユーザに対するデータの検索とフィルタを、美しく一貫した方法で行うこともできます。</p></blockquote>
<p>と、魅力的に説明されています。</p>
<p>使用方法や動作は、公式ページをみるのが手っ取り早いので割愛しますm(_ _)m<br />
#ググってください</p>
<p>このビヘイビアのメリットはたくさんあるのですが、<br />
特に便利だと思う点が</p>
<ul>
<li> recursiveやbindModelやunbindModelの記述がなくなって、<strong>ソースコードがきれいでわかりやすくなる</strong></li>
<li> あとで関連を選別できるからモデルの<strong>関連をモリモリの最大</strong>で書いておくことができる</li>
</ul>
<p>の２点で、とにかく便利です。</p>
<p>さらに以下のサンプルのようにモデルのrecursiveのデフォルト値を-1としておけば、<br />
何をcontainすればいいのか、しているのかがソースコードからわかりやすくなってお勧めです。</p>
<pre class="brush:php">class UserModel extends AppModel {

    public $recursive = -1;
    public $actsAs = array('Containable');
}</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/2299/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CakePHP 1.3でのプレフィックスルーティング(Prefix Routing)が簡単になった</title>
		<link>http://www.bpsinc.jp/blog/archives/2276</link>
		<comments>http://www.bpsinc.jp/blog/archives/2276#comments</comments>
		<pubDate>Tue, 24 Aug 2010 11:00:44 +0000</pubDate>
		<dc:creator>shibachan</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[芝原]]></category>
		<category><![CDATA[CakePHP1.3]]></category>
		<category><![CDATA[Prefix]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[プレフィックス]]></category>
		<category><![CDATA[ルーティング]]></category>
		<category><![CDATA[変更点]]></category>
		<category><![CDATA[移行]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=2276</guid>
		<description><![CDATA[CakePHPのPrefixRoutingは
例えばadmin_editという関数に対して、通常
/:controller/admin_edit
となるURLを
/admin/:controller/edit
とすること [...]]]></description>
			<content:encoded><![CDATA[<p>CakePHPのPrefixRoutingは<br />
例えばadmin_editという関数に対して、通常<br />
/:controller/admin_edit<br />
となるURLを<br />
/admin/:controller/edit<br />
とすることができるもの。</p>
<p>管理画面やWebAPIのために特別なURLを用意することができる。</p>
<p>このPrefixRoutingだが、設定の方法がCakePHP1.3より簡単なものに変更された。</p>
<p>そのやり方とは/app/config/core.phpで</p>
<pre class="brush:php">
Configure::write('Routing.prefixes', array('admin', 'api'));
</pre>
<p>とするだけ。<br />
# サンプルとしてadminとapiというプレフィックスを設定</p>
<p>ね、かんたんでしょ。</p>
<p>CakePHP1.2の時は複数のプレフィックス設定するのはRoutesをいちいち書いて結構めんどくさかったなー</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/2276/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RailsサーバUnicornを飼いならす! 運用時の便利技</title>
		<link>http://www.bpsinc.jp/blog/archives/2208</link>
		<comments>http://www.bpsinc.jp/blog/archives/2208#comments</comments>
		<pubDate>Wed, 28 Jul 2010 01:00:43 +0000</pubDate>
		<dc:creator>tomotaka</dc:creator>
				<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[伊藤]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[Unicorn]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=2208</guid>
		<description><![CDATA[伊藤です。
前回ブログで紹介したRailsサーバUnicornくんを運用し始めて結構時間が経ちました。
サービスを落とさないであるとか、システムの安定性を確保するために、
ちょっとしたユーティリティを作ったり監視ソフトM [...]]]></description>
			<content:encoded><![CDATA[<p>伊藤です。</p>
<p><a href="http://www.bpsinc.jp/blog/archives/2075">前回ブログで紹介</a>したRailsサーバ<a href="http://unicorn.bogomips.org/">Unicorn</a>くんを運用し始めて結構時間が経ちました。<br />
サービスを落とさないであるとか、システムの安定性を確保するために、<br />
ちょっとしたユーティリティを作ったり監視ソフト<a href="http://mmonit.com/monit/">Monit</a>の設定を行ったりしていました。</p>
<p>みなさんのお役に立つかわかりませんが、弊社でUnicornと組み合わせて運用に利用しているツールや設定をブログに掲載してみたいと思います。<br />
もっといいやり方がありましたら、ぜひコメント欄でご紹介頂ければと思います。</p>
<h2>ダウンしたら自動的に再起動</h2>
<p>これはMonitで行っています。<br />
もちろん同内容の監視ツールGodでも可能だと思いますが、以前設定した経験があって設定が楽そうだったので、Monitでやってみました。(事実楽でした)</p>
<pre class="brush:ruby">
check process unicorn with pidfile "/path/to/rails/tmp/pids/unicorn.pid"
  start program = "/home/tomotaka/monitor/unicorn_start" with timeout 10 seconds
  stop program = "/home/tomotaka/monitor/unicorn_stop"
  if 2 restarts within 3 cycles then timeout
  if cpu usage &#62; 95% for 3 cycles then restart
</pre>
<p>とりあえず, コピペで使う際に変更しなければいけない箇所は</p>
<ul>
<li>check processのあとの監視タスク名(なんでもよし, unicornを1個しか走らせてないならunicornでいいかも?)</li>
<li>start program(後述)</li>
<li>stop program(後述)</li>
</ul>
<p>お分かりかと思いますが、Unicorn起動/停止のためのコマンドは自作しました。<br />
startは簡単なシェルスクリプトです。</p>
<pre class="brush:bash">
#!/bin/bash
UNICORN_RAILS_BIN=/usr/local/bin/unicorn_rails
MY_RAILS_ROOT=/path/to/rails
MY_UNICORN_CONFIG=config/unicorn.rb
MY_RAILS_ENV=production

pushd $MY_RAILS_ROOT
$UNICORN_RAILS_BIN -c $MY_UNICORN_CONFIG -E $MY_RAILS_ENV -D
popd
</pre>
<p>簡単ですね。コピペで使う際は各変数を書き換えればオッケー。しいて言えばMY_UNICORN_CONFIGはRAILS_ROOTからの相対パスであることに注意でしょうか。(ディレクトリを移動してからコマンドを発行してるため) こういう簡単なツールを組み合わせて便利に使えるのがコンピュータのいいところですね。</p>
<p>unicorn_stopはUnicornのマスタープロセスのPIDをしらべて、それに対してQUITシグナルを送ればよいですね。シェルスクリプトでも出来るシンプルな内容ですが、unicornのプロセス制御のためのライブラリを作ったので、それを使ってやってます。(ライブラリについては後述)</p>
<pre class="brush:ruby">
require File.expand_path("./unicorn_manager.rb", File.dirname(__FILE__))

rails_root = "/path/to/rails"
pids = UnicornManager.get_unicorn_pids(rails_root)

puts "Sending signal to unicorn master &#91;pid=#{pids&#91;:master&#93;}&#93;"
Process.kill :QUIT, pids&#91;:master&#93;
puts "OK"
</pre>
<p>unicorn_manager.rbというのがライブラリですね。UnicornManager.get_unicorn_pidsというメソッドでUnicornのPID情報をハッシュ形式で返してくれるので、その情報をもとにProcess.killでシグナルを送ってます。unicorn_manager.rbについては、次の自動再起動の項で触れます。unicorn_manager.rbを同じディレクトリにおいて、rails_root変数を書き換えれば動作するはずです。</p>
<p>とりあえず、ライブラリunicorn_manager.rbと、上記のstart/stopコマンド2点があれば</p>
<ul>
<li>unicornが突然死したら自動的に起動</li>
<li>unicornがCPU食い過ぎてたら自動的に再起動</li>
<li>unicornがメモリ食い過ぎてたら自動的に再起動</li>
</ul>
<p>などのタスクがMonitだけで完了します。<br />
その他の複雑な条件も設定できる懐の深さがMonitにはありますので、ぜひ一度Monitのドキュメントに目を通してみてください。</p>
<p>これでとってもハッピーになれそうな感じですが、現時点のMonitにはstop programとstart programしかなく、&#8221;再起動&#8221;も&#8221;停止&#8221; => &#8220;起動&#8221;で実現される点が気になりました。というのもUnicornを使用するメリットのひとつはダウンタイムを作らずに子プロセスを新しく生まれ変わらせることができる点です。そのため、メモリを食べ過ぎて太っちゃった子プロセスにQUITシグナルを送るプログラムを先ほどunicorn_manager.rbというプログラムを作成してcronで利用しています。</p>
<h2>メモリ使用量の多い子プロセスを定期的にrespawn</h2>
<p>というわけで, fat-memory-process-killer.rbです。物騒な名前ですね。</p>
<pre class="brush:ruby">
#!/usr/local/bin/ruby
require File.expand_path("./unicorn_manager.rb", File.dirname(__FILE__))
require "logger"

logfile = "/path/to/logdir/fat-memory-process-killer.log"
loglevel = Logger::DEBUG # Logger::INFO if this script get enough stable
rails_root = "/path/to/rails"
threshhold = 40 # MB

# ----- end of config -----

puts "fat-memory-process-killer started at #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}"

logger = Logger.new(logfile)
logger.level = loglevel
logger.info "----- Start -----"
memory = UnicornManager::get_memories(rails_root)
logger.debug "** rails_root=#{rails_root}"
logger.debug "** threshhold=#{threshhold}MB"
logger.info "Got PIDs: m=#{memory&#91;:master&#93;.keys} c=#{memory&#91;:children&#93;.keys.join(",")}"
mpid = memory&#91;:master&#93;.keys&#91;0&#93;
mmem = memory&#91;:master&#93;&#91;mpid&#93;
logger.debug sprintf("** master-memory: % 7d =&#62; %4.2fMB", mpid, mmem.to_f/(1024*1024))

# kill fat child
memory&#91;:children&#93;.each do |pid, mem_size|
  logger.debug sprintf("** child-memory:  % 7d =&#62; %4.2fMB", pid, mem_size.to_f/(1024*1024))
  if 1024*1024*threshhold &#60; mem_size then
    logger.info "Sending QUIT signal to child(pid:#{pid}) memsize=#{mem_size} &#62; threshhold(#{1024*1024*threshhold})"
    Process.kill :QUIT, pid
  end
end

logger.info "Finish"
</pre>
<p>デーモンにすると面倒なことが多いので、5分間隔でcronで実行しています。コピペで利用するには、 end of configより上の部分を書き換えて、unicorn_manager.rbを同じディレクトリに置けば動作するはずです。このプログラムの働きぶりを観察するためにloggerでログに結果を出力していますが、興味がなければlogger関係のコードは削ってしまってもいいかもしれません。</p>
<p>いちおうこんな感じで報告されます。使用メモリが指定した40MBを超えたプロセスが3ついたので、QUITされてるようです。</p>
<pre class="brush:ruby">
I, &#91;2010-07-27T11:00:02.734795 #1161&#93;  INFO -- : ----- Start -----
D, &#91;2010-07-27T11:00:02.994152 #1161&#93; DEBUG -- : ** rails_root=/var/www/music-fly.net/webservice2
D, &#91;2010-07-27T11:00:02.994369 #1161&#93; DEBUG -- : ** threshhold=40MB
I, &#91;2010-07-27T11:00:02.994444 #1161&#93;  INFO -- : Got PIDs: m=13550 c=31905,748,30108,980,32380,30389,29861,981,745,31898,30105,746,31679,30106,747,417
D, &#91;2010-07-27T11:00:02.994639 #1161&#93; DEBUG -- : ** master-memory:   13550 =&#62; 30.49MB
D, &#91;2010-07-27T11:00:02.994680 #1161&#93; DEBUG -- : ** child-memory:    31905 =&#62; 35.09MB
D, &#91;2010-07-27T11:00:02.994720 #1161&#93; DEBUG -- : ** child-memory:      748 =&#62; 33.98MB
D, &#91;2010-07-27T11:00:02.994758 #1161&#93; DEBUG -- : ** child-memory:    30108 =&#62; 34.14MB
D, &#91;2010-07-27T11:00:02.994795 #1161&#93; DEBUG -- : ** child-memory:      980 =&#62; 46.07MB
I, &#91;2010-07-27T11:00:02.994830 #1161&#93;  INFO -- : Sending QUIT signal to child(pid:980) memsize=48304128 &#62; threshhold(41943040)
D, &#91;2010-07-27T11:00:02.994897 #1161&#93; DEBUG -- : ** child-memory:    32380 =&#62; 31.07MB
D, &#91;2010-07-27T11:00:02.994936 #1161&#93; DEBUG -- : ** child-memory:    30389 =&#62; 36.44MB
D, &#91;2010-07-27T11:00:02.994973 #1161&#93; DEBUG -- : ** child-memory:    29861 =&#62; 45.95MB
I, &#91;2010-07-27T11:00:02.995007 #1161&#93;  INFO -- : Sending QUIT signal to child(pid:29861) memsize=48177152 &#62; threshhold(41943040)
D, &#91;2010-07-27T11:00:02.995049 #1161&#93; DEBUG -- : ** child-memory:      981 =&#62; 31.05MB
D, &#91;2010-07-27T11:00:02.995086 #1161&#93; DEBUG -- : ** child-memory:      745 =&#62; 31.05MB
D, &#91;2010-07-27T11:00:02.995123 #1161&#93; DEBUG -- : ** child-memory:    31898 =&#62; 34.82MB
D, &#91;2010-07-27T11:00:02.995160 #1161&#93; DEBUG -- : ** child-memory:    30105 =&#62; 34.49MB
D, &#91;2010-07-27T11:00:02.995197 #1161&#93; DEBUG -- : ** child-memory:      746 =&#62; 45.10MB
I, &#91;2010-07-27T11:00:02.995231 #1161&#93;  INFO -- : Sending QUIT signal to child(pid:746) memsize=47288320 &#62; threshhold(41943040)
D, &#91;2010-07-27T11:00:02.995272 #1161&#93; DEBUG -- : ** child-memory:    31679 =&#62; 31.50MB
D, &#91;2010-07-27T11:00:03.057719 #1161&#93; DEBUG -- : ** child-memory:    30106 =&#62; 36.51MB
D, &#91;2010-07-27T11:00:03.057796 #1161&#93; DEBUG -- : ** child-memory:      747 =&#62; 31.52MB
D, &#91;2010-07-27T11:00:03.057835 #1161&#93; DEBUG -- : ** child-memory:      417 =&#62; 31.05MB
I, &#91;2010-07-27T11:00:03.057998 #1161&#93;  INFO -- : Finish
</pre>
<h2>ダウンタイムなしでコードをリロードするコマンド</h2>
<p>Railsのproduction環境では、新しいコードを動作中のサーバに反映するにはいったんサーバを再起動しないといけません。Unicornもダウンタイムこそ無いものの、マスタープロセスのpidを調べて, USR2シグナルを送るという作業が必要になります。UNIX界で長年暮らしてらっしゃる方はそんなもんpsやawk組み合わせたシェルスクリプトで一発だろ、って感じかと思いますが、僕は軟弱者なのでメモリ食い過ぎプロセスを殺すためにつくったライブラリを利用して、Rubyで作りました。unicorn_reloadコマンドです。</p>
<pre class="brush:ruby">
#!/usr/local/bin/ruby

require File.expand_path("./unicorn_manager.rb", File.dirname(__FILE__))

rails_root = "/path/to/rails"
pids = UnicornManager.get_unicorn_pids(rails_root)

puts "Sending signal to unicorn master &#91;pid=#{pids&#91;:master&#93;}&#93;"
Process.kill :USR2, pids&#91;:master&#93;
puts "OK"
</pre>
<p>unicorn_stopのところで紹介したプログラムとシグナルの名前しか変わってないですね&#8230;これはひどい。</p>
<h2>unicorn_manager.rb</h2>
<p>unicorn_manager.rbのコードも貼付けてやろうかと思いましたが、ちょっと長いのでダウンロードリンクだけにしておきます。</p>
<ul>
<li><a href="http://rain.bpsinc.jp/~tomotaka/pub/unicorn_manager.rb">unicorn_manager.rbのダウンロード</a>
</ul>
<p>その代わりといっては何ですが、unicorn_manager.rbの機能を紹介しておきます。</p>
<ol>
<li>Unicornのpidリストを得る: UnicornManager.get_unicorn_pids(&#8221;/path/to/rails&#8221;) => {:master => 123, :children => [124,125,126,127...] }</li>
<li>Unicornに設定を再読込みさせる(マスターにHUPシグナルを送る): UnicornManager.reload_config(rails_root)</li>
<li>Unicornにプログラムを再読み込みさせる(マスターにUSR2シグナルを送る): UnicornManager.reload_code(rails_root)</li>
<li>Unicornの子プロセスを全て再起動させる(各子プロセスに順番にQUITシグナルを送る): UnicornManager.restart_all_child(rails_root)</li>
<li>Unicornのメモリ使用量をプロセスごとに得る: UnicornManager.get_memories(rails_root) => {:master => {123 => 1000000}, :children => {124 => 1000000, :125 => 1000000, &#8230;}}</li>
</ol>
<p>動作要件:</p>
<ul>
<li>/path/to/railsがRAILS_ROOTとして与えられたとき、/tmp/pids/unicorn.pidにunicornのpidがあること</li>
<li>psの出力フォーマットがLinux互換であること</li>
</ul>
<p>書いてて気づきました。unicorn_stopとunicorn_reloadのコマンドは専用のメソッドがあるじゃないか&#8230; もっと短くできますね。</p>
<h2>まとめ</h2>
<p>Unicornが落ちないような仕組みをMonitで構築した。Unicornの子プロセスがメモリ食い過ぎたらrespawn(日本語でいい表現を思いつかない、再起動とはちょっと違うような&#8230;)する仕組みをcronとオリジナルスクリプトで実現した。開発の際に作ったライブラリを使ってコードでプロイ時にも楽できるスクリプトとかも作った。</p>
<p>それではみなさん快適なRailsライフを!</p>
<p>Unicornシリーズ前の記事: <a href="http://www.bpsinc.jp/blog/archives/2075">次世代RailsサーバーUnicornを使ってみた</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/2208/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RailsのログファイルをMessagePackで超高速解析!</title>
		<link>http://www.bpsinc.jp/blog/archives/2114</link>
		<comments>http://www.bpsinc.jp/blog/archives/2114#comments</comments>
		<pubDate>Fri, 16 Jul 2010 00:48:17 +0000</pubDate>
		<dc:creator>tomotaka</dc:creator>
				<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[伊藤]]></category>
		<category><![CDATA[MessagePack]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[Ruby]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=2114</guid>
		<description><![CDATA[こんにちは、伊藤です。
最近Railsがガンガン出力するproduction.logを解析してどのアクションがリクエスト多いのか、DBがボトルネックになってるアクションはないか、などを調べているんですが、producti [...]]]></description>
			<content:encoded><![CDATA[<p>こんにちは、伊藤です。</p>
<p>最近Railsがガンガン出力するproduction.logを解析してどのアクションがリクエスト多いのか、DBがボトルネックになってるアクションはないか、などを調べているんですが、production.logがどんどん肥大化して、解析ツールの開発イテレーションを回す際の効率が悪くなってきました。</p>
<p>また似たようなツールをやたらめったらコピペで作ってきたため、開発効率も悪くなってきたので、ここでAPIを整理しつつ、あわよくばログをなめる部分を高速化できないものかと考えました。</p>
<p>シンプルに正規表現をつかって変化する箇所を切り出していたのですが、これではかなり重いのは自明ですし、がんばって文字列をパースするパーサを作ってもrubyではあまりスピードは出なそう、でもrubyで書きたいし&#8230;.</p>
<p>ということで、一度パースしたデータを読込みに効率よさそうな形式に変換することで、次以降の解析を高速化しようと考えました。RubyのMarshalモジュールのdump/loadを使うのもよさそうでしたが、個人的にMessagePackというライブラリが気になっていたので、試してみました。特にウリ文句もread時のデシリアライズ性能がよいとのことでしたので、結果的にはマッチする用途だったのかなと思います。</p>
<p>んで今回つくったrails_log.rbというライブラリなのですが、以下のようにして使うことを想定してます。</p>
<pre class="brush:ruby">
require "rails_log"

# 普通のログファイルを生で解析
rlog = RailsLog.new("/hogege/log/production.log")
rlog.each do |row|
 # rowを使っていろいろ数えたりする
end

# binファイルに変換
rlog = RailsLog.new("...")
rlog.convert_to_bin

# binファイルを変換(はやい)
rlog = RailsLog.new("...")
rlog.bin_each do |row|
 # rowを使っていろいろ数えたりする
end
</pre>
<p>2つのイテレータeachとbin_eachが解析用のメソッドで、ブロック引数(この例ではともにrowとしている)はHashオブジェクトで、以下のようなキーと値を持ってます。</p>
<ul>
<li>:controller_action &#8211; HogeController#action_nameのような文字列</li>
<li>:format &#8211; &#8220;html&#8221;とか&#8221;json&#8221;(レスポンスの出力フォーマット)</li>
<li>:ip &#8211; リクエストしてきたクライアントのIPアドレス(文字列)</li>
<li>:datetime &#8211; リクエストされたアクセス時刻(Timeオブジェクト)</li>
<li>:http_method &#8211; GETとかPOSTとか</li>
<li>:params &#8211; paramsの中身(Hash)</li>
<li>:time_total &#8211; アクション処理にかかった総時間(単位msec, FixNum)</li>
<li>:time_view &#8211; View処理にかかった時間</li>
<li>:time_db &#8211; DB処理にかかった時間</li>
<li>:http_response &#8211; &#8220;200 OK&#8221;とかそういう文字列</li>
<li>:request_uri -<br />
&#8220;http://mogera.bpsinc.jp/fuga/hoge/?p1=value1&#038;p2=valuevalue22&#8243;のような文字列</li>
</ul>
<p>出力フォーマットがjsonなリクエストと、そうでないリクエストの数を数えるには以下のようにします。</p>
<pre class="brush:ruby">
rlog = RailsLog.new("production.log")
c_json, c_other = 0, 0
rlog.each do |row|
 if row&#91;:format&#93; == "json" then
   c_json += 1
 else
   c_other += 1
 end
end
# 数字をつかってなにかする
</pre>
<p>ただこれでは遅いというのが、当初の開発動機ですので、MessagePackを応用した形式に変換してから解析してみましょう。</p>
<pre class="brush:ruby">
# まず変換(これには多少時間がかかる)
raw_log = RailsLog.new("production.log")
raw_log.convert_to_bin("production.log.bin")

# 解析
bin_log = RailsLog.new("production.log.bin")
c_json, c_other = 0, 0
bin_log.bin_each do |row|
  if row&#91;:format&#93; == "json" then
    c_json += 1
  else
    c_other += 1
  end
end

# 以降はbin_eachですばやく解析できる
</pre>
<h2>注意</h2>
<p>キーのリストにある以外の値は全て葬りさられますので、元のファイルはバイナリファイルを作成しても手元に残しておくことをおすすめします。(ロガーオブジェクトで出力したデバッグメッセージなど)</p>
<h2>パフォーマンス</h2>
<p>実験環境: Core2Duo E8500, Mem2GB, VM(KVM), Ubuntu Linux 32bit, ruby1.8.7 enterprise<br />
ログファイル: 約1.4GB, 44万5000リクエストのログ</p>
<ul>
<li>binファイルに変換: だいたい150秒ぐらいかかりました。変換後のバイナリデータの容量は約185MBになりました。</li>
<li>eachによる生ログの解析(変換前): eachを全部回すのにだいたい40秒程度</li>
<li>bin_eachによる変換されたログの解析: bin_eachを全部回すのにだいたい5秒程度 => 8倍程度の高速化</li>
<li>Java版によるログの解析: (環境が違うので参考まで: MacOSX10.6 CPU C2D 2.66GHz, Mem4GB)約2.3秒 => 15倍以上の高速化</li>
</ul>
<p>常用していきたいruby版でも8倍ほど早くなりました。</p>
<h2>ダウンロード</h2>
<ul>
<li><a href="http://rain.bpsinc.jp/~tomotaka/pub/rails_log.rb">rails_log.rb</a> &#8211; ログ解析フレームワークとMessagePackを用いたバイナリ形式への変換機能の提供</li>
<li><a href="http://rain.bpsinc.jp/~tomotaka/pub/rails_log-java.zip">rails_log-java.zip</a> &#8211; ログ解析フレームワーク(バイナリのみ)の提供</li>
</ul>
<h2>まとまらないまとめ</h2>
<p>ログ解析なんてどうせ数日に1回とか、1日1回なので遅くてもいいや、という話もありますが、解析ツールを開発するときに実際のデータで素早く解析できる、eachとbin_eachを入れ替えるだけでアルゴリズムを触らず生のログファイル/変換済みのバイナリ形式ファイルと対応を切り替えることができるので割と気に入ってます。<br />
またJava版も勢いで作ってみたものの、やはりかなり早かったので自己満足してしまいました。<br />
MessagePackプロジェクトの皆さんにも感謝です!</p>
<h2>参考リンク</h2>
<ul>
<li><a href="http://msgpack.org/">MessagePack Project</a> &#8211; RPC機能も試してみたいですね!</li>
</ul>
<p>プログラムはご自由にお使い頂いて結構ですので、よければ皆さんもお試しください。<br />
# バグってたらごめんなさい&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/2114/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>次世代RailsサーバーUnicornを使ってみた</title>
		<link>http://www.bpsinc.jp/blog/archives/2075</link>
		<comments>http://www.bpsinc.jp/blog/archives/2075#comments</comments>
		<pubDate>Fri, 09 Jul 2010 01:14:18 +0000</pubDate>
		<dc:creator>tomotaka</dc:creator>
				<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[伊藤]]></category>
		<category><![CDATA[Rack]]></category>
		<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=2075</guid>
		<description><![CDATA[2010.07.20追記: prefixを指定した運用も可能でした。ご指摘頂きありがとうございます。
2010.07.28追記: 関連記事「RailsサーバUnicornを飼いならす! 運用時の便利技」へのリンクを張りま [...]]]></description>
			<content:encoded><![CDATA[<p>2010.07.20追記: prefixを指定した運用も可能でした。ご指摘頂きありがとうございます。<br />
2010.07.28追記: 関連記事「<a href="http://www.bpsinc.jp/blog/archives/2208">RailsサーバUnicornを飼いならす! 運用時の便利技</a>」へのリンクを張りました。</p>
<p>伊藤です。</p>
<p>Railsサーバはたくさんあってややこしいですね！<br />
最近さらに<a href="http://unicorn.bogomips.org/">Unicorn</a>というものが頭角を表してきたようで、<a href="http://twitter.com/">Twitter</a>や<a href="http://github.com/">github</a>も使っているようなので使ってみましたので、特徴や使い方などレポートしてみたいと思います。<br />
このブログの他にもEngine Yardのブログ記事「<a href="http://www.engineyard.com/blog/2010/everything-you-need-to-know-about-unicorn/">Everything You Need to Know About Unicorn</a>」やgithubの記事「<a href="http://github.com/blog/517-unicorn">Unicorn!</a>」が非常に参考になると思いますので、あわせてご覧ください。<br />
(そもそもUnicornは用途をRailsに限定しない汎用のRackアプリケーションサーバです。タイトルは煽り気味ですね。すいません。)</p>
<p>ざっくりと、Unicornのアーキテクチャとそれにまつわるメリットデメリットをリスト形式で。</p>
<ul>
<li>thinやmongrelみたいなマルチプロセスによるclusterではなく、forkを使ったmaster-slave
<ul>
<li>マルチプロセスモデルよりメモリ効率がいいかも?(copy-on-write)</li>
<li>ふくれあがったメモリ食い過ぎプロセスを殺しても、サービスにダウンタイムが発生しない
<ul>
<li>Monitとかでふくれあがったプロセスに対してQUITを送ると、そいつは処理中のリクエストを処理したら死ぬ
<ul>
<li>親がそいつの代わりをすぐrespawnする</li>
</ul>
</li>
</ul>
</li>
<li>デプロイが早い</li>
<li>デプロイ時のダウンタイムがない</li>
</ul>
</li>
<li>apache =&gt;&nbsp;app-server-clusterという風なpush requestではなくapache &lt;= unicornというpullリクエスト
<ul>
<li>slaveプロセスが共通のソケットを通じてリクエストを受け取る</li>
<li>ひまなプロセスが処理を開始する(ソケットからリクエストを取り出す)</li>
<li>処理中のもっさりアプリケーションサーバにあたることがない
<ul>
<li>もっさりサーバは処理がおわってないのでリクエストを取りにいくことがないから</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>それでもって、以下が実際に使ってみた印象です。</p>
<ul>
<li>CPUあんまり食わない</li>
<li><strong>メモリあんまり食わない</strong></li>
<li>確かにデプロイ(起動/再起動)早い。</li>
<li>たまに重いアクションを叩かれるような場合ではそのリクエストを処理しているworkerにあたることがなくなるので、全体のスループットを向上できる?</li>
<li><span style="text-decoration: line-through;">prefixを指定した運用ができない</span> prefixを指定して運用可能です：unicorn_railsの&#8211;pathオプションで指定できます。</li>
</ul>
<p>RailsアプリをMongrelやThinのクラスターで運用するとメモリをたくさんお食べになられるのが、結構悩みのタネだと思います。<br />
スモールスタートのプロジェクトではフロントのロードバランサ、アプリケーションサーバ、DBサーバも全て1台でやるのが経済的理由からあたり前ですので、ThinやMongrelがメモリを食いまくるからといってサーバをもう1台追加しなくてはならないようでは積極的に使いにくいですよね。<br />
しかしこのメモリ食いまくる現象にも理由はあり、Engine Yardのブログ記事「<a href="http://www.engineyard.com/blog/2009/thats-not-a-memory-leak-its-bloat/">That’s Not a Memory Leak, It’s Bloat</a>」でもActiveRecordのインスタンスが大量生成され、Rubyがそれを解放しないからふくれあがってしまうという主な理由が説明されていました。<br />
もちろん優れたハッカーが多数いるRailsコミュニティでは対策も当然あり、<a href="http://mmonit.com/monit/">Monit</a>やそれに似たRubyベースのモニタリング(&#038;再起動)ツール<a href="http://god.rubyforge.org/">God</a>などを駆使してCPUを使いすぎていたり、メモリを使いすぎているインスタンスに対して自動的に再起動をかけるのが一般的です。</p>
<p>しかしこのモデルでは以下のような一般的な構成で、サービスの安定性を追求した際に問題があります。</p>
<p><img src="http://www.bpsinc.jp/blog/wp-content/uploads/2010/07/loadbalance1.gif" alt="ロードバランサとThinクラスタ" title="ロードバランサとThinクラスタ" width="300" height="250" class="alignnone size-full wp-image-2076" /></p>
<p>ロードバランサは重み付けなしの設定を行うと、リクエストごとに、バックエンドのアプリケーションサーバに対して順番にリクエストを行います。ここで3番目(右はじ)のサーバに対して「レスポンスを返すまで時間がかかる地雷アクション」へのリクエストが来たとします。</p>
<p><img src="http://www.bpsinc.jp/blog/wp-content/uploads/2010/07/loadbalance2.gif" alt="クラスタの一部が重くなってる状態" title="クラスタの一部が重くなってる状態" width="300" height="250" class="alignnone size-full wp-image-2077" /></p>
<p>このまま6回目のアクセスがくると、6回目のリクエストには3回目のリクエストが終わってないので処理が返せません。<br />
こ6回目のアクセスがくる前に、左側のthinインスタンスと真ん中のthinインスタンスが4回目のリクエストと5回目のリクエストの処理を終えていれば、処理を肩代わりしてほしいところですが、リクエストの振り分けはapacheが上流で行っているので、難しいという状況です。</p>
<p>これと同じことがプロセスの再起動時にもおこります。ここで同様に右端のインスタンスが激重アクションによりメモリ使用量が爆増し、監視しているMonitなりGodなりが再起動をかけているとします。Railsのスタックのロードには1〜2秒程度時間がかかるので、アクセス数の多いサイトだったらこの間にリクエストをapacheから振られる可能性はゼロではありません。</p>
<p><img src="http://www.bpsinc.jp/blog/wp-content/uploads/2010/07/loadbalance31.gif" alt="再起動中のインスタンスがある場合" title="再起動中のインスタンスがある場合" width="300" height="250" class="alignnone size-full wp-image-2079" /></p>
<p>この例では右端のインスタンスが再起動を開始した1秒以内に3つのアクセスがくると、3つ目が準備が終わっていない3つめのインスタンスに来てしまいます。ここでは直感的には1つめの左端のインスタンスが、リクエストが終わり次第処理してほしい気がします。</p>
<p>ここでUnicornのロードバランシングのアーキテクチャを図にして見ました。</p>
<p><img src="http://www.bpsinc.jp/blog/wp-content/uploads/2010/07/unicorn.gif" alt="Unicornのアーキテクチャ" title="Unicornのアーキテクチャ" width="300" height="250" class="alignnone size-full wp-image-2080" /></p>
<p>Unicornでは、MasterとSlave(図ではchildと書いています)がおり、Masterは起動するとSlaveをforkして生産します。mongrelをたくさん立ち上げるのと同じイメージですね。そしてApacheからのロードバランシングでは、Apache(ロードバランサ)が接続する先はMaster1つで、Unicornの内部でMasterとSlaveがリクエストをやりとりし、Slaveが処理した結果をMaster経由でApache(ロードバランサ)に返します。このやりとりというのがミソで、ひまになったSlaveがMasterに対して「次に処理するリクエストをくれ」という風にPULLしにくるのです。Masterは一番最初にpullしてきたSlaveに処理を行わせるだけでよいですね。こうすると、なにがうれしいかというとさっきのような例で「重い処理をしている」「再起動中」といったプロセスがリクエストの処理を担当することが原理的に起こりえません。このような状態では処理すべきリクエストをmasterに対してpullすることができないからですね。</p>
<p>また、forkを使うことによってOSのcopy-on-write機能により実際に使う実メモリ使用量が減る、Railsのコードをロードしなおさなくてよいなどのメリットがあるようです。</p>
<p>また、プログラムを更新する際にダウンタイムを作らない仕組みをうまく作っているのも面白いです。MongrelやThinクラスターをproductionモードで動かしている場合はクラスタ全体のの再起動が必要で、1〜2秒 * インスタンス数という時間が必要です。慌てて何度もやっているとサービスに影響が出てしまいますし、そもそもアプリケーションサーバがたくさんあるような場合では時間がかかってめんどくさいですね。Unicornではこの辺もエレガントに解決できており、UnicornのマスタープロセスにUSR2シグナルを送ると、もうひとつmasterプロセスを作って引き継ぎを開始します。新しいmasterプロセスは古いmasterプロセスから、上流のロードバランサと通信しているポートの引き継ぎを行います。古いmasterプロセスはUSR2シグナルを送っただけでは死なないのですが、後半に載せてあるgithubに公開されている設定スクリプトのように古いmasterに対してQUITシグナルを送る処理を自動化することも可能です。USR2シグナルを使って新しいmasterを起動するときはだいたい古いmasterはいらないと思われるので、before_forkでQUITシグナルを送ってしまうのがよいでしょう。</p>
<pre class="brush:bash">
$ sudo kill -USR2 (masterのpid)
</pre>
<p>だけですみます。楽ですね！しかも今までthinインスタンス10個でクライアントの処理が完了するのを待っているのを含めて10秒以上かかっていたのがものの2〜3秒でできてしまいました。</p>
<p>という感じで、非常にいい感じのUnicornくんですが、日本語ではまだあまり情報がないので以下にハウツーとして実際に使えるコマンドや設定などを列挙していきたいと思います。</p>
<h2>インストール</h2>
<pre class="brush:bash">
$ sudo gem install unicorn
</pre>
<p>※Windowsではまともに動かないっぽいです</p>
<h2>起動</h2>
<pre class="brush:bash">
# productionモード(-E), デーモン化する(-D), 詳細をconfigファイルで指定
$ cd RAILS_ROOT
$ cd unicorn_rails -c config/unicorn-config.rb -E production -D
</pre>
<p>-Dオプションでデーモン化を指定しない場合フォアグラウンドプロセスとして動き、production.logに記録される内容も標準出力として出力されます。フォアグラウンドプロセスとして動いている場合、Ctrl+Cでunicorn masterと全てのunicorn workersの動作を停止させることができます。</p>
<p>設定ファイル(ここではconfig/unicorn-config.rbとして指定されている)の書き方については後述。</p>
<h2>停止</h2>
<p>そもそもUnicornではダウンタイムなしで新しいコードをproductionモードでデプロイできるので、停止の必要はめったにありませんので注意。<br />
Unicornにおいては設定最読み込みとどうようにhogehoge stopというようなコマンドはなく、INT(TERM)/QUITシグナルを送ることにより、停止させます。QUITシグナルはgraceful shutdownで、全てのworkerがリクエストの処理を終えるのを待ちます。INTまたはTERMシグナルを送った場合は、すぐにworkerプロセスを全て皆殺しにします。(quick shutdown)</p>
<pre class="brush:bash">
# unicornのmasterプロセスのIDを特定する
$ sudo pgrep -f 'unicorn_rails master'
12345

# graceful shutdown: 現在処理中の全てのリクエストの処理が終わるのをまってからシャットダウン
$ sudo kill -QUIT 12345

# quick shutdown: 現在処理中の全てのリクエストの処理を中断し、シャットダウン
$ sudo kill -INT 12345
</pre>
<h2>設定再読み込み</h2>
<p>Unicornにおいては設定を再読み込みさせるにはhogehoge reloadというようなコマンドはなく、HUPシグナルを送ることによって実現される。シグナルを送る先はunicornのmasterプロセス。masterのプロセスID(pid)を特定するには以下のようにする。<br />
preload_app(デフォルトfalse)をtrueにしていると、プログラムコードはリロードされません。(逆にいえばpreload_appがfalseのままならmasterにHUPを送るとリロードされる)</p>
<pre class="brush:bash">
# unicornのmasterプロセスのIDを特定する
$ sudo pgrep -f 'unicorn_rails master'
12345

# masterプロセスにHUPシグナルを送る
$ sudo kill -HUP 12345
</pre>
<h2>プログラムのデプロイ</h2>
<p>手順は公式のSIGNALのページに詳しく書いてあります。</p>
<pre class="brush:bash">
# unicornのmasterプロセスのIDを特定する
$ sudo pgrep -f 'unicorn_rails master'
12345

# masterプロセスにUSR2シグナルを送る
$ sudo kill -USR 12345
</pre>
<h2>再起動</h2>
<p>先にも述べましたが、そもそもUnicornではダウンタイムなしで新しいコードをproductionモードでデプロイできるので、停止の必要はめったにありません。<br />
それでも再起動するなら、停止→起動ですね。</p>
<h2>設定ファイルの書き方</h2>
<p>まずは、公式で参考として配布されている2つを見てみてフィーリングをつかむのがよさそうです：</p>
<ul>
<li>フルセット版: <a href="http://unicorn.bogomips.org/examples/unicorn.conf.rb">http://unicorn.bogomips.org/examples/unicorn.conf.rb</a></li>
<li>ミニマム版: <a href="http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb">http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb</a></li>
</ul>
<p>githubのブログ記事で公開されているものを改変して、うちで使っているものも公開しちゃいます。</p>
<pre class="brush:ruby">
$default_env = "production" # デフォルトRailsEnv
$unicorn_user = "bps" # slaveの実行ユーザ
$unicorn_group = "bps" # slaveの実行グループ

$dev_processes = 4 # dev環境用子プロセス数
$prod_processes = 16 # 本番環境用子プロセス数

$timeout = 75 # タイムアウト秒数。タイムアウトしたslaveは再起動される

# String =&#62; UNIX domain socket / FixNum =&#62; TCP socket
#$listen = "/home/bps/tmp/unicorn.sock"
$listen = 5000

# ---- end of config ----

# Main Config for Unicorn
rails_env = ENV&#91;'RAILS_ENV'&#93; || $default_env
worker_processes (rails_env == 'production' ? $prod_processes : $dev_processes)
preload_app true
timeout $timeout
listen $listen, :backlog =&#62; 2048

# For RubyEnterpriseEdition: http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
if GC.respond_to?(:copy_on_write_friendly=)
  GC.copy_on_write_friendly = true
end

# workerをフォークする前の処理
before_fork do |server, worker|
  ##
  # When sent a USR2, Unicorn will suffix its pidfile with .oldbin and
  # immediately start loading up a new version of itself (loaded with a new
  # version of our app). When this new Unicorn is completely loaded
  # it will begin spawning workers. The first worker spawned will check to
  # see if an .oldbin pidfile exists. If so, this means we've just booted up
  # a new Unicorn and need to tell the old one that it can now die. To do so
  # we send it a QUIT.
  #
  # Using this method we get 0 downtime deploys.

  old_pid = RAILS_ROOT + '/tmp/pids/unicorn.pid.oldbin'
  if File.exists?(old_pid) &amp;&amp; server.pid != old_pid
    begin
      # 古いマスターがいたら死んでもらう
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

# workerをフォークしたあとの処理
after_fork do |server, worker|
  ##
  # Unicorn master loads the app then forks off workers - because of the way
  # Unix forking works, we need to make sure we aren't using any of the parent's
  # sockets, e.g. db connection

  ActiveRecord::Base.establish_connection
  #CHIMNEY.client.connect_to_server
  # Redis and Memcached would go here but their connections are established
  # on demand, so the master never opens a socket

  ##
  # Unicorn master is started as root, which is fine, but let's
  # drop the workers to git:git

  begin
    uid, gid = Process.euid, Process.egid
    user, group = $unicorn_user, $unicorn_group
    target_uid = Etc.getpwnam(user).uid
    target_gid = Etc.getgrnam(group).gid
    worker.tmp.chown(target_uid, target_gid)
    if uid != target_uid || gid != target_gid
      Process.initgroups(user, target_gid)
      Process::GID.change_privilege(target_gid)
      Process::UID.change_privilege(target_uid)
    end
  rescue =&#62; e
    if RAILS_ENV == 'development'
      STDERR.puts "couldn't change user, oh well"
    else
      raise e
    end
  end
end
</pre>
<h2>nginxをロードバランサにした場合の設定例</h2>
<pre class="brush:ruby">
upstream backend-unicorn {
        server 192.168.xxx.xxx:3000;
}

server {
        listen   80;
        server_name hogehoge.bpsinc.jp;

        access_log  /var/log/nginx/hogehoge.bpsinc.jp.access.log;

        location / {
                proxy_pass_header Server;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-Host $host;
                proxy_set_header X-Forwarded-Server $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                #proxy_set_header X-Geo $geo;
                proxy_read_timeout 75; # unicorn設定ファイルのtimeoutも忘れずに
                proxy_pass http://backend-unicorn; # upstreamで定義したバックエンド

                # 通常と違うポートでフロントサーバ(ロードバランサ)を動かしているときはこれが必要
                #proxy_redirect http://hogehoge.bpsinc.jp/ http://hogehoge.bpsinc.jp:8080/;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
                root   /var/www/nginx-default;
        }
}
</pre>
<h2>参考リンク</h2>
<ul>
<li>Unicorn公式: <a href="http://unicorn.bogomips.org/">http://unicorn.bogomips.org/</a>
<ul>
<li>Read Meページ(インストールや起動の仕方など): <br/><a href="http://unicorn.bogomips.org/README.html">http://unicorn.bogomips.org/README.html</a></li>
<li>SIGNALに関するページ(再起動や停止やシグナルを送ったときの挙動): <br/><a href="http://unicorn.bogomips.org/SIGNALS.html">http://unicorn.bogomips.org/SIGNALS.html</a></li>
</ul>
</li>
<li>Unicorn! &#8211; github: <a href="http://github.com/blog/517-unicorn">http://github.com/blog/517-unicorn</a></li>
</ul>
<p>どうでしたでしょうか？<br />
BPSでもまだUnicornはノウハウが十分に蓄積されていませんが、積極的に使っていってノウハウをためていこうと思います。</p>
<p>運用時のTipsについて、続編を書きましたので合わせてごらんください：<a href="http://www.bpsinc.jp/blog/archives/2208">RailsサーバUnicornを飼いならす! 運用時の便利技</a></p>
<p>※7/13追記: USR2シグナルをmasterプロセスに送ったときの挙動の記述について、一部間違いがあったので修正。古いmasterは自動では死にませんね&#8230; あとnginxでの設定例を追加してみました。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/2075/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>カタぞうの画像表示高速化</title>
		<link>http://www.bpsinc.jp/blog/archives/2004</link>
		<comments>http://www.bpsinc.jp/blog/archives/2004#comments</comments>
		<pubDate>Mon, 05 Jul 2010 00:20:10 +0000</pubDate>
		<dc:creator>baba</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[カタぞう]]></category>
		<category><![CDATA[馬場]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[キャッシュ]]></category>
		<category><![CDATA[画像]]></category>
		<category><![CDATA[高速化]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=2004</guid>
		<description><![CDATA[Webプログラミングの超基本ですが、画像表示の最適化です。
カタぞうでは、画像を動的に出力（サイズを変えたりなど）するために、いったんPHPを通しています。
投稿時に縮小しておくやりかたもありますが、ディスク容量を圧迫す [...]]]></description>
			<content:encoded><![CDATA[<p>Webプログラミングの超基本ですが、画像表示の最適化です。</p>
<p><a href="http://katazou.jp/">カタぞう</a>では、画像を動的に出力（サイズを変えたりなど）するために、いったんPHPを通しています。</p>
<p>投稿時に縮小しておくやりかたもありますが、ディスク容量を圧迫するのと、今後違うサイズが求められる可能性があるため、基本的に最大サイズで保存しておいて出力時に縮小画像を生成する仕組みを採用しています。</p>
<p>ところでこの仕組み、CakePHPのアクション内で実行すると、恐ろしく時間がかかります。</p>
<p>単純に画像をfpassthruするだけのコードで、600ミリ秒程度もかかりました。</p>
<p>主な原因は、Routingやフレームワークの初期処理が300msec程度、DBへのDESCRIBEが100msec程度、各種コンポーネントの初期化が200msec程度でした。</p>
<p>さすがに苦しいため、ここだけ生PHPで書いたところ、平均5msecに短縮。<br />
これなら、画像をwebrootにキャッシュしてApacheのキャッシュを使わせる・・・　等の処理をしなくても、遜色ありません。</p>
<p>今後のスケールに向けてもう少し練らないといけませんが、やっぱり基本は、不要な複雑処理を省くところですね。</p>
<p>（Railsの高速化テクを見ていて、「とにかくRailsに到達させるな」という方針で悲しくなりましたが、それと同じ）</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/2004/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CakePHPで簡単Basic認証</title>
		<link>http://www.bpsinc.jp/blog/archives/1966</link>
		<comments>http://www.bpsinc.jp/blog/archives/1966#comments</comments>
		<pubDate>Wed, 30 Jun 2010 07:25:51 +0000</pubDate>
		<dc:creator>shibachan</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[芝原]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=1966</guid>
		<description><![CDATA[CakePHPで簡単な管理画面を作ったので、認証をかけたいけど、UserテーブルつくってAuthComponentっていうのはめんどくさい。
複数人のアカウント制御できる必要もないし、そんなのオーバースペックだ。
つい先 [...]]]></description>
			<content:encoded><![CDATA[<p>CakePHPで簡単な管理画面を作ったので、認証をかけたいけど、UserテーブルつくってAuthComponentっていうのはめんどくさい。<br />
複数人のアカウント制御できる必要もないし、そんなのオーバースペックだ。</p>
<p>つい先ほど、まさにこの状態になって、簡単に認証を設定できないかと<br />
ネットの海をさまよっていると、やはりありました。<br />
<a href="http://blog.spicebox.jp/labs/2009/03/cakephp_12_basic.html">CakePHP 1.2 の Basic 認証設定があまりにも簡単すぎる </a></p>
<p>SecurityComponentを使ってController#beforeFillterでベーシック認証を設定するという方法です。</p>
<pre class="brush:php">
class HogeController extends AppController {

    public $components = array('Security');

    public function beforeFilter() {
        $this-&#62;Security-&#62;loginOptions = array('type'=&#62;'basic');
        $this-&#62;Security-&#62;loginUsers = array('username'=&#62;'password');
        $this-&#62;Security-&#62;requireLogin('*');  // 全アクションを指定（特定アクションも設定できる）
    }
}
</pre>
<p>確かにすごい簡単でした。<br />
パスワードが平文というのが少し気になりますが、簡単な認証でしたらこれで十分ですね。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/1966/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OAuthライブラリが謎の停止</title>
		<link>http://www.bpsinc.jp/blog/archives/1903</link>
		<comments>http://www.bpsinc.jp/blog/archives/1903#comments</comments>
		<pubDate>Sun, 27 Jun 2010 01:03:47 +0000</pubDate>
		<dc:creator>baba</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[馬場]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[バグ]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=1903</guid>
		<description><![CDATA[CakePHPでOAuthを使うのに、このライブラリが有名だと思います。
http://code.42dh.com/oauth/
MITライセンスなのが良いですよね。
ところが、これを使ったCakePHPのシステムで、d [...]]]></description>
			<content:encoded><![CDATA[<p>CakePHPでOAuthを使うのに、このライブラリが有名だと思います。</p>
<p><a href="http://code.42dh.com/oauth/">http://code.42dh.com/oauth/</a></p>
<p>MITライセンスなのが良いですよね。</p>
<p>ところが、これを使ったCakePHPのシステムで、debug=2の時は動くのに、debug=0にすると動かないという謎の問題に出くわしました。</p>
<p>止まる場所は、認証ページへのリダイレクトに使う、</p>
<pre class="brush:php">
$consumer-&#62;getRequestToken('http://twitter.com/oauth/request_token', $callback);
</pre>
<p>のあたりです。</p>
<p>ソースを追ってみたところ、OAuth.phpの以下の場所で止まっていました。</p>
<pre class="brush:php">
public function get_normalized_http_url() {
  $parts = parse_url($this-&#62;http_url);

  $port = @$parts&#91;'port'&#93;; // ←ここ！！
  $scheme = $parts&#91;'scheme'&#93;;
  $host = $parts&#91;'host'&#93;;
  $path = @$parts&#91;'path'&#93;;

  $port or $port = ($scheme == 'https') ? '443' : '80';

  if (($scheme == 'https' &#038;&#038; $port != '443')
      || ($scheme == 'http' &#038;&#038; $port != '80')) {
    $host = "$host:$port";
  }
  return "$scheme://$host$path";
}
</pre>
<p>いや、確かに、portというキーは無かったんですけど、<br />
そのための＠演算子だし、そもそもこんなのでFatal Errorになるはずもなく。</p>
<p>要するに、PHPエンジンの方のバグなのか、謎ですね・・・</p>
<p>解決策は以下。</p>
<pre class="brush:php">
public function get_normalized_http_url() {
  $parts = parse_url($this-&#62;http_url);

  //== add this ==
  if (!array_key_exists('port', $parts)) {
      $parts&#91;'port'&#93; = (isset($parts&#91;'scheme'&#93;) &#038;&#038; $parts&#91;'scheme'&#93; == 'https') ? '443' : '80';
  }
  //== add this ==

  $port = @$parts&#91;'port'&#93;;
  $scheme = $parts&#91;'scheme'&#93;;
  $host = $parts&#91;'host'&#93;;
  $path = @$parts&#91;'path'&#93;;

  $port or $port = ($scheme == 'https') ? '443' : '80';

  if (($scheme == 'https' &#038;&#038; $port != '443')
      || ($scheme == 'http' &#038;&#038; $port != '80')) {
    $host = "$host:$port";
  }
  return "$scheme://$host$path";
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/1903/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RailsでTokyoTyrantを使ってみたらフリーズした</title>
		<link>http://www.bpsinc.jp/blog/archives/1906</link>
		<comments>http://www.bpsinc.jp/blog/archives/1906#comments</comments>
		<pubDate>Thu, 24 Jun 2010 05:27:33 +0000</pubDate>
		<dc:creator>tomotaka</dc:creator>
				<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[伊藤]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[TokyoTyrant]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=1906</guid>
		<description><![CDATA[※6/25にサンプルコードを修正しました
※7/1にサンプルコードを修正しました＆補足を入れました
伊藤です。
最近社内のRailsを使ったプロジェクトで、外部サービスからのレスポンスを使う処理で、
いちいち外部サービス [...]]]></description>
			<content:encoded><![CDATA[<p>※6/25にサンプルコードを修正しました<br />
※7/1にサンプルコードを修正しました＆補足を入れました</p>
<p>伊藤です。</p>
<p>最近社内のRailsを使ったプロジェクトで、外部サービスからのレスポンスを使う処理で、<br />
いちいち外部サービスにリクエストを行って取得していてはとても時間がかかるので、<br />
シンプルなキャッシュシステムを構築することになりました。</p>
<p><a href="http://www.bpsinc.jp/blog/archives/1661">最初は馬場が記事にしてくれている</a>ようにファイルベースのものを用いていましたが(実際はもうちょっと複雑なものを使っていました)、<br />
ファイル数(エントリ)が数万単位で増えた際、パフォーマンスが指数関数的に悪くなってしまったので、キーと値の格納にTokyoTyrantを利用することにしました。<br />
TokyoTyrantの使い方はシステムエンジニアブログにも書いてみました(<a href="http://www.bpsinc.jp/slog/archives/49">TokyoTyrantをRubyで使ってみた</a>)ので、TokyoTyrantそのものについてはこちらも見てみてください。</p>
<p>Railsから、利用するためにlib/util/data_cache.rbといったかんじで、以下のようなモジュールを定義してみました。<br />
任意のオブジェクトをデータ構造を保ったまま保存できるようにMarshalクラスによりシリアライズ/デシリアライズを行っています。<br />
コントローラからはincludeして使うイメージです。</p>
<pre class="brush:ruby">
require "tokyotyrant"
module DataCache
  def cache_read(key)
    init_tt if !defined?(@@tokyotyrant)
    v = @@tokyotyrant.get(key)
    return v ? Marshal.load(v) : nil
  end

  def cache_write(key, value)
    init_tt if !defined?(@@tokyotyrant)
    @@tokyotyrant.put key, Marshal.dump(value)
  end

  private

  def init_tt
    @@tokyotyrant = TokyoTyrant::RDB.new
    @@tokyotyrant.open("localhost", 1978)
  end
end
</pre>
<p>これをいろんなコントローラから使っていると、自分のデスクトップから開発している分には正常に動作するのですが、<br />
多人数が同時でアクセスしたりする環境で使うとフリーズします。</p>
<p>どうやらgetやputを行っているときに、並行してこれらのTokyoTyrantとの通信が発生するメソッドを呼び出すと<br />
通信の内容がごちゃごちゃになり、TTから正常にレスポンスを取得することができなくなっていたものと思われました。<br />
マルチスレッド動作のサポートが各方面から望まれていたRailsですが、意外にもこのようなところでも弊害が発生しました。</p>
<p>対処としては、以下のようにRuby標準のthreadライブラリにあるMutexを使って、<br />
TokyoTyrantへのアクセスを行う部分をクリティカルセクションとすることで、解決しました。</p>
<pre class="brush:ruby">
require "thread"
require "tokyotyrant"
module DataCache
  MTX = Mutex.new
  def cache_read(key)
    init_tt if !defined?(@@tokyotyrant)
    v = nil
    MTX.synchronize do
      v = @@tokyotyrant.get(key)
    end
    return v ? Marshal.load(v) : nil
  end

  def cache_write(key, value)
    init_tt if !defined?(@@tokyotyrant)
    MTX.synchronize do
      @@tokyotyrant.put key, Marshal.dump(value)
    end
  end

  private

  def init_tt
    MTX.synchronize do
      return if defined?(@@tokyotyrant)
      @@tokyotyrant = TokyoTyrant::RDB.new
      @@tokyotyrant.open("localhost", 1978)
    end
  end
end
</pre>
<p>このクリティカルセクションに限らずRailsではまだ1プロセスで大量のリクエストをさばけるほどマルチスレッド処理のスループットが<br />
出ませんので、本番環境の実運用では複数プロセスを上げて動かすというのが一般的、というのがまだしばらく続きそうですね。</p>
<p>問題意識を煽るようなタイトルをつけてしまいましたが、コントローラ部分の実装がスレッドセーフかどうかを保証するのはユーザの責任であるというRailsの仕様と、get/putなどの呼び出しはスレッドセーフではないというTokyoTyrantクライアントライブラリの仕様の問題ですね。</p>
<p>TokyoTyrant自体はクリティカルセクションで保護してもサクサク動作しているので、今後もキャッシュ機構が必要になったら積極的に使っていこうと思います。</p>
<p>7/1追記:<br />
今回実装例で挙げているモジュールでは、1つのアプリケーションプロセスで最大で1つのTokyoTyrantコネクションしか使わないため、大量にTokyoTyrantにアクセスを行う場合TokyoTyrantにいちいちつなぎ直すか、複数のコネクションをプーリングするなどの戦略が必要です。調べたところTokyoTyrantのセッション確率のコストはそんなに高くないのでgetやputを少ない回数発行するような利用の場合いちいち接続する設計にするとシンプルで、バグが少なくてよいと思います。また、アプリケーションサーバとTokyoTyrantサーバを一つのマシンで動かしていると、TTへのリクエストの並列度が増えてくるとだんだんレスポンスが悪くなるようです。(アプリサーバとTTサーバに分けて実行すると数倍の速度が出る)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/1906/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CakePHP findの形式</title>
		<link>http://www.bpsinc.jp/blog/archives/1874</link>
		<comments>http://www.bpsinc.jp/blog/archives/1874#comments</comments>
		<pubDate>Tue, 22 Jun 2010 00:35:57 +0000</pubDate>
		<dc:creator>baba</dc:creator>
				<category><![CDATA[CakePHP]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[馬場]]></category>
		<category><![CDATA[find]]></category>
		<category><![CDATA[配列]]></category>

		<guid isPermaLink="false">http://www.bpsinc.jp/blog/?p=1874</guid>
		<description><![CDATA[CakePHPのfind自体は使いやすいのですが、返ってくるのが配列で、しかもへんてこな形式なので、結構めんどくさいですよね。
たとえば、Userテーブルでfind(&#8217;all&#8217;)すると、

arr [...]]]></description>
			<content:encoded><![CDATA[<p>CakePHPのfind自体は使いやすいのですが、返ってくるのが配列で、しかもへんてこな形式なので、結構めんどくさいですよね。</p>
<p>たとえば、Userテーブルでfind(&#8217;all&#8217;)すると、</p>
<pre class="brush:php">
array(
  'User' =&#62; array(
    'id' =&#62; 1,
    'name' =&#62; 'yamada'
  ),
  'User' =&#62; array(
    'id' =&#62; 2,
    'name' =&#62; 'tanaka'
  ),
  'User' =&#62; array(
    'id' =&#62; 3,
    'name' =&#62; 'suzuki'
  ),
);
</pre>
<p>ですが、</p>
<p>Group hasMany Users の場合、Groupをfindすると、</p>
<pre class="brush:php">
array(
  'Group' =&#62; array(
    'id' =&#62; 1,
    'name' =&#62; 'Group-A'
  ),
  'User' =&#62; array(
    array(
      'id' =&#62; 1,
      'name' =&#62; 'yamada',
    ),
    array(
      'id' =&#62; 2,
      'name' =&#62; 'tanaka',
    ),
    array(
      'id' =&#62; 3,
      'name' =&#62; 'suzuki',
    ),
  ),
);
</pre>
<p>となり、形式が全然違います。</p>
<p>そのため、配列を引数にとるelementを作って、Viewを効率的にまとめる、という当たり前のことが、非常にやりにくい。<br />
こんなときは仕方ないので、後者の形式が来ることを期待して、前者の形式が来たら、以下のような姑息な手段が有効です。</p>
<pre class="brush:php">
if (isset($user&#91;'User'&#93;)) {
  $user+= $user&#91;'User'&#93;;
}
</pre>
<p>ほんっと、<br />
Rails → コード書いている時楽しいけど、その後がめんどくさい<br />
Cake → コード書いている時は苦痛だけど、インストールも運用も簡単<br />
ですよね・・・</p>
<p>↑のようなことをTwitterでつぶやいていたら、中の人から、Set::mapやFilterを使うと良いとアドバイスを頂きました。感謝感謝です。<br />
ただ、paginatorなどとの互換性を維持しつつ、望んでいる利便性を確保するには、少し工夫が必要そうですね。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.bpsinc.jp/blog/archives/1874/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

