Techracho

RailsサーバUnicornを飼いならす! 運用時の便利技

このエントリーをはてなブックマーク Share
2010.07.28    Ruby, Ruby on Rails, 伊藤   タグ: , , —    tomotaka   

伊藤です。

前回ブログで紹介したRailsサーバUnicornくんを運用し始めて結構時間が経ちました。
サービスを落とさないであるとか、システムの安定性を確保するために、
ちょっとしたユーティリティを作ったり監視ソフトMonitの設定を行ったりしていました。

みなさんのお役に立つかわかりませんが、弊社でUnicornと組み合わせて運用に利用しているツールや設定をブログに掲載してみたいと思います。
もっといいやり方がありましたら、ぜひコメント欄でご紹介頂ければと思います。

ダウンしたら自動的に再起動

これはMonitで行っています。
もちろん同内容の監視ツールGodでも可能だと思いますが、以前設定した経験があって設定が楽そうだったので、Monitでやってみました。(事実楽でした)

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 > 95% for 3 cycles then restart

とりあえず, コピペで使う際に変更しなければいけない箇所は

  • check processのあとの監視タスク名(なんでもよし, unicornを1個しか走らせてないならunicornでいいかも?)
  • start program(後述)
  • stop program(後述)

お分かりかと思いますが、Unicorn起動/停止のためのコマンドは自作しました。
startは簡単なシェルスクリプトです。

#!/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

簡単ですね。コピペで使う際は各変数を書き換えればオッケー。しいて言えばMY_UNICORN_CONFIGはRAILS_ROOTからの相対パスであることに注意でしょうか。(ディレクトリを移動してからコマンドを発行してるため) こういう簡単なツールを組み合わせて便利に使えるのがコンピュータのいいところですね。

unicorn_stopはUnicornのマスタープロセスのPIDをしらべて、それに対してQUITシグナルを送ればよいですね。シェルスクリプトでも出来るシンプルな内容ですが、unicornのプロセス制御のためのライブラリを作ったので、それを使ってやってます。(ライブラリについては後述)

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 [pid=#{pids[:master]}]"
Process.kill :QUIT, pids[:master]
puts "OK"

unicorn_manager.rbというのがライブラリですね。UnicornManager.get_unicorn_pidsというメソッドでUnicornのPID情報をハッシュ形式で返してくれるので、その情報をもとにProcess.killでシグナルを送ってます。unicorn_manager.rbについては、次の自動再起動の項で触れます。unicorn_manager.rbを同じディレクトリにおいて、rails_root変数を書き換えれば動作するはずです。

とりあえず、ライブラリunicorn_manager.rbと、上記のstart/stopコマンド2点があれば

  • unicornが突然死したら自動的に起動
  • unicornがCPU食い過ぎてたら自動的に再起動
  • unicornがメモリ食い過ぎてたら自動的に再起動

などのタスクがMonitだけで完了します。
その他の複雑な条件も設定できる懐の深さがMonitにはありますので、ぜひ一度Monitのドキュメントに目を通してみてください。

これでとってもハッピーになれそうな感じですが、現時点のMonitにはstop programとstart programしかなく、”再起動”も”停止” => “起動”で実現される点が気になりました。というのもUnicornを使用するメリットのひとつはダウンタイムを作らずに子プロセスを新しく生まれ変わらせることができる点です。そのため、メモリを食べ過ぎて太っちゃった子プロセスにQUITシグナルを送るプログラムを先ほどunicorn_manager.rbというプログラムを作成してcronで利用しています。

メモリ使用量の多い子プロセスを定期的にrespawn

というわけで, fat-memory-process-killer.rbです。物騒な名前ですね。

#!/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[:master].keys} c=#{memory[:children].keys.join(",")}"
mpid = memory[:master].keys[0]
mmem = memory[:master][mpid]
logger.debug sprintf("** master-memory: % 7d => %4.2fMB", mpid, mmem.to_f/(1024*1024))

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

logger.info "Finish"

デーモンにすると面倒なことが多いので、5分間隔でcronで実行しています。コピペで利用するには、 end of configより上の部分を書き換えて、unicorn_manager.rbを同じディレクトリに置けば動作するはずです。このプログラムの働きぶりを観察するためにloggerでログに結果を出力していますが、興味がなければlogger関係のコードは削ってしまってもいいかもしれません。

いちおうこんな感じで報告されます。使用メモリが指定した40MBを超えたプロセスが3ついたので、QUITされてるようです。

I, [2010-07-27T11:00:02.734795 #1161]  INFO -- : ----- Start -----
D, [2010-07-27T11:00:02.994152 #1161] DEBUG -- : ** rails_root=/var/www/music-fly.net/webservice2
D, [2010-07-27T11:00:02.994369 #1161] DEBUG -- : ** threshhold=40MB
I, [2010-07-27T11:00:02.994444 #1161]  INFO -- : Got PIDs: m=13550 c=31905,748,30108,980,32380,30389,29861,981,745,31898,30105,746,31679,30106,747,417
D, [2010-07-27T11:00:02.994639 #1161] DEBUG -- : ** master-memory:   13550 => 30.49MB
D, [2010-07-27T11:00:02.994680 #1161] DEBUG -- : ** child-memory:    31905 => 35.09MB
D, [2010-07-27T11:00:02.994720 #1161] DEBUG -- : ** child-memory:      748 => 33.98MB
D, [2010-07-27T11:00:02.994758 #1161] DEBUG -- : ** child-memory:    30108 => 34.14MB
D, [2010-07-27T11:00:02.994795 #1161] DEBUG -- : ** child-memory:      980 => 46.07MB
I, [2010-07-27T11:00:02.994830 #1161]  INFO -- : Sending QUIT signal to child(pid:980) memsize=48304128 > threshhold(41943040)
D, [2010-07-27T11:00:02.994897 #1161] DEBUG -- : ** child-memory:    32380 => 31.07MB
D, [2010-07-27T11:00:02.994936 #1161] DEBUG -- : ** child-memory:    30389 => 36.44MB
D, [2010-07-27T11:00:02.994973 #1161] DEBUG -- : ** child-memory:    29861 => 45.95MB
I, [2010-07-27T11:00:02.995007 #1161]  INFO -- : Sending QUIT signal to child(pid:29861) memsize=48177152 > threshhold(41943040)
D, [2010-07-27T11:00:02.995049 #1161] DEBUG -- : ** child-memory:      981 => 31.05MB
D, [2010-07-27T11:00:02.995086 #1161] DEBUG -- : ** child-memory:      745 => 31.05MB
D, [2010-07-27T11:00:02.995123 #1161] DEBUG -- : ** child-memory:    31898 => 34.82MB
D, [2010-07-27T11:00:02.995160 #1161] DEBUG -- : ** child-memory:    30105 => 34.49MB
D, [2010-07-27T11:00:02.995197 #1161] DEBUG -- : ** child-memory:      746 => 45.10MB
I, [2010-07-27T11:00:02.995231 #1161]  INFO -- : Sending QUIT signal to child(pid:746) memsize=47288320 > threshhold(41943040)
D, [2010-07-27T11:00:02.995272 #1161] DEBUG -- : ** child-memory:    31679 => 31.50MB
D, [2010-07-27T11:00:03.057719 #1161] DEBUG -- : ** child-memory:    30106 => 36.51MB
D, [2010-07-27T11:00:03.057796 #1161] DEBUG -- : ** child-memory:      747 => 31.52MB
D, [2010-07-27T11:00:03.057835 #1161] DEBUG -- : ** child-memory:      417 => 31.05MB
I, [2010-07-27T11:00:03.057998 #1161]  INFO -- : Finish

ダウンタイムなしでコードをリロードするコマンド

Railsのproduction環境では、新しいコードを動作中のサーバに反映するにはいったんサーバを再起動しないといけません。Unicornもダウンタイムこそ無いものの、マスタープロセスのpidを調べて, USR2シグナルを送るという作業が必要になります。UNIX界で長年暮らしてらっしゃる方はそんなもんpsやawk組み合わせたシェルスクリプトで一発だろ、って感じかと思いますが、僕は軟弱者なのでメモリ食い過ぎプロセスを殺すためにつくったライブラリを利用して、Rubyで作りました。unicorn_reloadコマンドです。

#!/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 [pid=#{pids[:master]}]"
Process.kill :USR2, pids[:master]
puts "OK"

unicorn_stopのところで紹介したプログラムとシグナルの名前しか変わってないですね…これはひどい。

unicorn_manager.rb

unicorn_manager.rbのコードも貼付けてやろうかと思いましたが、ちょっと長いのでダウンロードリンクだけにしておきます。

その代わりといっては何ですが、unicorn_manager.rbの機能を紹介しておきます。

  1. Unicornのpidリストを得る: UnicornManager.get_unicorn_pids(”/path/to/rails”) => {:master => 123, :children => [124,125,126,127...] }
  2. Unicornに設定を再読込みさせる(マスターにHUPシグナルを送る): UnicornManager.reload_config(rails_root)
  3. Unicornにプログラムを再読み込みさせる(マスターにUSR2シグナルを送る): UnicornManager.reload_code(rails_root)
  4. Unicornの子プロセスを全て再起動させる(各子プロセスに順番にQUITシグナルを送る): UnicornManager.restart_all_child(rails_root)
  5. Unicornのメモリ使用量をプロセスごとに得る: UnicornManager.get_memories(rails_root) => {:master => {123 => 1000000}, :children => {124 => 1000000, :125 => 1000000, …}}

動作要件:

  • /path/to/railsがRAILS_ROOTとして与えられたとき、/tmp/pids/unicorn.pidにunicornのpidがあること
  • psの出力フォーマットがLinux互換であること

書いてて気づきました。unicorn_stopとunicorn_reloadのコマンドは専用のメソッドがあるじゃないか… もっと短くできますね。

まとめ

Unicornが落ちないような仕組みをMonitで構築した。Unicornの子プロセスがメモリ食い過ぎたらrespawn(日本語でいい表現を思いつかない、再起動とはちょっと違うような…)する仕組みをcronとオリジナルスクリプトで実現した。開発の際に作ったライブラリを使ってコードでプロイ時にも楽できるスクリプトとかも作った。

それではみなさん快適なRailsライフを!

Unicornシリーズ前の記事: 次世代RailsサーバーUnicornを使ってみた

RailsのログファイルをMessagePackで超高速解析!

このエントリーをはてなブックマーク Share
2010.07.16    Ruby on Rails, 伊藤   タグ: , , , —    tomotaka   

こんにちは、伊藤です。

最近Railsがガンガン出力するproduction.logを解析してどのアクションがリクエスト多いのか、DBがボトルネックになってるアクションはないか、などを調べているんですが、production.logがどんどん肥大化して、解析ツールの開発イテレーションを回す際の効率が悪くなってきました。

また似たようなツールをやたらめったらコピペで作ってきたため、開発効率も悪くなってきたので、ここでAPIを整理しつつ、あわよくばログをなめる部分を高速化できないものかと考えました。

シンプルに正規表現をつかって変化する箇所を切り出していたのですが、これではかなり重いのは自明ですし、がんばって文字列をパースするパーサを作ってもrubyではあまりスピードは出なそう、でもrubyで書きたいし….

ということで、一度パースしたデータを読込みに効率よさそうな形式に変換することで、次以降の解析を高速化しようと考えました。RubyのMarshalモジュールのdump/loadを使うのもよさそうでしたが、個人的にMessagePackというライブラリが気になっていたので、試してみました。特にウリ文句もread時のデシリアライズ性能がよいとのことでしたので、結果的にはマッチする用途だったのかなと思います。

んで今回つくったrails_log.rbというライブラリなのですが、以下のようにして使うことを想定してます。

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

2つのイテレータeachとbin_eachが解析用のメソッドで、ブロック引数(この例ではともにrowとしている)はHashオブジェクトで、以下のようなキーと値を持ってます。

  • :controller_action – HogeController#action_nameのような文字列
  • :format – “html”とか”json”(レスポンスの出力フォーマット)
  • :ip – リクエストしてきたクライアントのIPアドレス(文字列)
  • :datetime – リクエストされたアクセス時刻(Timeオブジェクト)
  • :http_method – GETとかPOSTとか
  • :params – paramsの中身(Hash)
  • :time_total – アクション処理にかかった総時間(単位msec, FixNum)
  • :time_view – View処理にかかった時間
  • :time_db – DB処理にかかった時間
  • :http_response – “200 OK”とかそういう文字列
  • :request_uri -
    “http://mogera.bpsinc.jp/fuga/hoge/?p1=value1&p2=valuevalue22″のような文字列

出力フォーマットがjsonなリクエストと、そうでないリクエストの数を数えるには以下のようにします。

rlog = RailsLog.new("production.log")
c_json, c_other = 0, 0
rlog.each do |row|
 if row[:format] == "json" then
   c_json += 1
 else
   c_other += 1
 end
end
# 数字をつかってなにかする

ただこれでは遅いというのが、当初の開発動機ですので、MessagePackを応用した形式に変換してから解析してみましょう。

# まず変換(これには多少時間がかかる)
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[:format] == "json" then
    c_json += 1
  else
    c_other += 1
  end
end

# 以降はbin_eachですばやく解析できる

注意

キーのリストにある以外の値は全て葬りさられますので、元のファイルはバイナリファイルを作成しても手元に残しておくことをおすすめします。(ロガーオブジェクトで出力したデバッグメッセージなど)

パフォーマンス

実験環境: Core2Duo E8500, Mem2GB, VM(KVM), Ubuntu Linux 32bit, ruby1.8.7 enterprise
ログファイル: 約1.4GB, 44万5000リクエストのログ

  • binファイルに変換: だいたい150秒ぐらいかかりました。変換後のバイナリデータの容量は約185MBになりました。
  • eachによる生ログの解析(変換前): eachを全部回すのにだいたい40秒程度
  • bin_eachによる変換されたログの解析: bin_eachを全部回すのにだいたい5秒程度 => 8倍程度の高速化
  • Java版によるログの解析: (環境が違うので参考まで: MacOSX10.6 CPU C2D 2.66GHz, Mem4GB)約2.3秒 => 15倍以上の高速化

常用していきたいruby版でも8倍ほど早くなりました。

ダウンロード

  • rails_log.rb – ログ解析フレームワークとMessagePackを用いたバイナリ形式への変換機能の提供
  • rails_log-java.zip – ログ解析フレームワーク(バイナリのみ)の提供

まとまらないまとめ

ログ解析なんてどうせ数日に1回とか、1日1回なので遅くてもいいや、という話もありますが、解析ツールを開発するときに実際のデータで素早く解析できる、eachとbin_eachを入れ替えるだけでアルゴリズムを触らず生のログファイル/変換済みのバイナリ形式ファイルと対応を切り替えることができるので割と気に入ってます。
またJava版も勢いで作ってみたものの、やはりかなり早かったので自己満足してしまいました。
MessagePackプロジェクトの皆さんにも感謝です!

参考リンク

プログラムはご自由にお使い頂いて結構ですので、よければ皆さんもお試しください。
# バグってたらごめんなさい…

次世代RailsサーバーUnicornを使ってみた

このエントリーをはてなブックマーク Share
2010.07.09    Ruby, Ruby on Rails, 伊藤   タグ: , , , —    tomotaka   

2010.07.20追記: prefixを指定した運用も可能でした。ご指摘頂きありがとうございます。
2010.07.28追記: 関連記事「RailsサーバUnicornを飼いならす! 運用時の便利技」へのリンクを張りました。

伊藤です。

Railsサーバはたくさんあってややこしいですね!
最近さらにUnicornというものが頭角を表してきたようで、Twittergithubも使っているようなので使ってみましたので、特徴や使い方などレポートしてみたいと思います。
このブログの他にもEngine Yardのブログ記事「Everything You Need to Know About Unicorn」やgithubの記事「Unicorn!」が非常に参考になると思いますので、あわせてご覧ください。
(そもそもUnicornは用途をRailsに限定しない汎用のRackアプリケーションサーバです。タイトルは煽り気味ですね。すいません。)

ざっくりと、Unicornのアーキテクチャとそれにまつわるメリットデメリットをリスト形式で。

  • thinやmongrelみたいなマルチプロセスによるclusterではなく、forkを使ったmaster-slave
    • マルチプロセスモデルよりメモリ効率がいいかも?(copy-on-write)
    • ふくれあがったメモリ食い過ぎプロセスを殺しても、サービスにダウンタイムが発生しない
      • Monitとかでふくれあがったプロセスに対してQUITを送ると、そいつは処理中のリクエストを処理したら死ぬ
        • 親がそいつの代わりをすぐrespawnする
    • デプロイが早い
    • デプロイ時のダウンタイムがない
  • apache => app-server-clusterという風なpush requestではなくapache <= unicornというpullリクエスト
    • slaveプロセスが共通のソケットを通じてリクエストを受け取る
    • ひまなプロセスが処理を開始する(ソケットからリクエストを取り出す)
    • 処理中のもっさりアプリケーションサーバにあたることがない
      • もっさりサーバは処理がおわってないのでリクエストを取りにいくことがないから

それでもって、以下が実際に使ってみた印象です。

  • CPUあんまり食わない
  • メモリあんまり食わない
  • 確かにデプロイ(起動/再起動)早い。
  • たまに重いアクションを叩かれるような場合ではそのリクエストを処理しているworkerにあたることがなくなるので、全体のスループットを向上できる?
  • prefixを指定した運用ができない prefixを指定して運用可能です:unicorn_railsの–pathオプションで指定できます。

RailsアプリをMongrelやThinのクラスターで運用するとメモリをたくさんお食べになられるのが、結構悩みのタネだと思います。
スモールスタートのプロジェクトではフロントのロードバランサ、アプリケーションサーバ、DBサーバも全て1台でやるのが経済的理由からあたり前ですので、ThinやMongrelがメモリを食いまくるからといってサーバをもう1台追加しなくてはならないようでは積極的に使いにくいですよね。
しかしこのメモリ食いまくる現象にも理由はあり、Engine Yardのブログ記事「That’s Not a Memory Leak, It’s Bloat」でもActiveRecordのインスタンスが大量生成され、Rubyがそれを解放しないからふくれあがってしまうという主な理由が説明されていました。
もちろん優れたハッカーが多数いるRailsコミュニティでは対策も当然あり、Monitやそれに似たRubyベースのモニタリング(&再起動)ツールGodなどを駆使してCPUを使いすぎていたり、メモリを使いすぎているインスタンスに対して自動的に再起動をかけるのが一般的です。

しかしこのモデルでは以下のような一般的な構成で、サービスの安定性を追求した際に問題があります。

ロードバランサとThinクラスタ

ロードバランサは重み付けなしの設定を行うと、リクエストごとに、バックエンドのアプリケーションサーバに対して順番にリクエストを行います。ここで3番目(右はじ)のサーバに対して「レスポンスを返すまで時間がかかる地雷アクション」へのリクエストが来たとします。

クラスタの一部が重くなってる状態

このまま6回目のアクセスがくると、6回目のリクエストには3回目のリクエストが終わってないので処理が返せません。
こ6回目のアクセスがくる前に、左側のthinインスタンスと真ん中のthinインスタンスが4回目のリクエストと5回目のリクエストの処理を終えていれば、処理を肩代わりしてほしいところですが、リクエストの振り分けはapacheが上流で行っているので、難しいという状況です。

これと同じことがプロセスの再起動時にもおこります。ここで同様に右端のインスタンスが激重アクションによりメモリ使用量が爆増し、監視しているMonitなりGodなりが再起動をかけているとします。Railsのスタックのロードには1〜2秒程度時間がかかるので、アクセス数の多いサイトだったらこの間にリクエストをapacheから振られる可能性はゼロではありません。

再起動中のインスタンスがある場合

この例では右端のインスタンスが再起動を開始した1秒以内に3つのアクセスがくると、3つ目が準備が終わっていない3つめのインスタンスに来てしまいます。ここでは直感的には1つめの左端のインスタンスが、リクエストが終わり次第処理してほしい気がします。

ここでUnicornのロードバランシングのアーキテクチャを図にして見ました。

Unicornのアーキテクチャ

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することができないからですね。

また、forkを使うことによってOSのcopy-on-write機能により実際に使う実メモリ使用量が減る、Railsのコードをロードしなおさなくてよいなどのメリットがあるようです。

また、プログラムを更新する際にダウンタイムを作らない仕組みをうまく作っているのも面白いです。MongrelやThinクラスターをproductionモードで動かしている場合はクラスタ全体のの再起動が必要で、1〜2秒 * インスタンス数という時間が必要です。慌てて何度もやっているとサービスに影響が出てしまいますし、そもそもアプリケーションサーバがたくさんあるような場合では時間がかかってめんどくさいですね。Unicornではこの辺もエレガントに解決できており、UnicornのマスタープロセスにUSR2シグナルを送ると、もうひとつmasterプロセスを作って引き継ぎを開始します。新しいmasterプロセスは古いmasterプロセスから、上流のロードバランサと通信しているポートの引き継ぎを行います。古いmasterプロセスはUSR2シグナルを送っただけでは死なないのですが、後半に載せてあるgithubに公開されている設定スクリプトのように古いmasterに対してQUITシグナルを送る処理を自動化することも可能です。USR2シグナルを使って新しいmasterを起動するときはだいたい古いmasterはいらないと思われるので、before_forkでQUITシグナルを送ってしまうのがよいでしょう。

$ sudo kill -USR2 (masterのpid)

だけですみます。楽ですね!しかも今までthinインスタンス10個でクライアントの処理が完了するのを待っているのを含めて10秒以上かかっていたのがものの2〜3秒でできてしまいました。

という感じで、非常にいい感じのUnicornくんですが、日本語ではまだあまり情報がないので以下にハウツーとして実際に使えるコマンドや設定などを列挙していきたいと思います。

インストール

$ sudo gem install unicorn

※Windowsではまともに動かないっぽいです

起動

# productionモード(-E), デーモン化する(-D), 詳細をconfigファイルで指定
$ cd RAILS_ROOT
$ cd unicorn_rails -c config/unicorn-config.rb -E production -D

-Dオプションでデーモン化を指定しない場合フォアグラウンドプロセスとして動き、production.logに記録される内容も標準出力として出力されます。フォアグラウンドプロセスとして動いている場合、Ctrl+Cでunicorn masterと全てのunicorn workersの動作を停止させることができます。

設定ファイル(ここではconfig/unicorn-config.rbとして指定されている)の書き方については後述。

停止

そもそもUnicornではダウンタイムなしで新しいコードをproductionモードでデプロイできるので、停止の必要はめったにありませんので注意。
Unicornにおいては設定最読み込みとどうようにhogehoge stopというようなコマンドはなく、INT(TERM)/QUITシグナルを送ることにより、停止させます。QUITシグナルはgraceful shutdownで、全てのworkerがリクエストの処理を終えるのを待ちます。INTまたはTERMシグナルを送った場合は、すぐにworkerプロセスを全て皆殺しにします。(quick shutdown)

# unicornのmasterプロセスのIDを特定する
$ sudo pgrep -f 'unicorn_rails master'
12345

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

# quick shutdown: 現在処理中の全てのリクエストの処理を中断し、シャットダウン
$ sudo kill -INT 12345

設定再読み込み

Unicornにおいては設定を再読み込みさせるにはhogehoge reloadというようなコマンドはなく、HUPシグナルを送ることによって実現される。シグナルを送る先はunicornのmasterプロセス。masterのプロセスID(pid)を特定するには以下のようにする。
preload_app(デフォルトfalse)をtrueにしていると、プログラムコードはリロードされません。(逆にいえばpreload_appがfalseのままならmasterにHUPを送るとリロードされる)

# unicornのmasterプロセスのIDを特定する
$ sudo pgrep -f 'unicorn_rails master'
12345

# masterプロセスにHUPシグナルを送る
$ sudo kill -HUP 12345

プログラムのデプロイ

手順は公式のSIGNALのページに詳しく書いてあります。

# unicornのmasterプロセスのIDを特定する
$ sudo pgrep -f 'unicorn_rails master'
12345

# masterプロセスにUSR2シグナルを送る
$ sudo kill -USR 12345

再起動

先にも述べましたが、そもそもUnicornではダウンタイムなしで新しいコードをproductionモードでデプロイできるので、停止の必要はめったにありません。
それでも再起動するなら、停止→起動ですね。

設定ファイルの書き方

まずは、公式で参考として配布されている2つを見てみてフィーリングをつかむのがよさそうです:

githubのブログ記事で公開されているものを改変して、うちで使っているものも公開しちゃいます。

$default_env = "production" # デフォルトRailsEnv
$unicorn_user = "bps" # slaveの実行ユーザ
$unicorn_group = "bps" # slaveの実行グループ

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

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

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

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

# Main Config for Unicorn
rails_env = ENV['RAILS_ENV'] || $default_env
worker_processes (rails_env == 'production' ? $prod_processes : $dev_processes)
preload_app true
timeout $timeout
listen $listen, :backlog => 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) && 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 => e
    if RAILS_ENV == 'development'
      STDERR.puts "couldn't change user, oh well"
    else
      raise e
    end
  end
end

nginxをロードバランサにした場合の設定例

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;
        }
}

参考リンク

どうでしたでしょうか?
BPSでもまだUnicornはノウハウが十分に蓄積されていませんが、積極的に使っていってノウハウをためていこうと思います。

運用時のTipsについて、続編を書きましたので合わせてごらんください:RailsサーバUnicornを飼いならす! 運用時の便利技

※7/13追記: USR2シグナルをmasterプロセスに送ったときの挙動の記述について、一部間違いがあったので修正。古いmasterは自動では死にませんね… あとnginxでの設定例を追加してみました。

Rubyでマルチスレッドプログラミング

このエントリーをはてなブックマーク Share
2010.07.05    Ruby, 伊藤   タグ: , —    tomotaka   

伊藤です。

RubyはWeb界隈でよく使われてるスクリプト言語の中では比較的簡単に割と本格的なマルチスレッドプログラムを書くことができます。
うまく使うとI/O待ちで遅くなっているが、必ずしも順番に行う必要のないプログラムの実行時間を短縮したりすることができます。(たくさんのURLにアクセスしてレスポンスを取得してくるクロウラーなど)
しかし現在最も広く使われているRuby1.8ではスレッドの実装はいわゆるグリーンスレッドという実装で、OSによるスレッドを使えないため、マルチコアの恩恵を受けることができないので、大量のデータをマルチコアで処理する目的などで使っても効果が薄い(どころかまったくない)可能性が高いです。
Ruby1.9ではRubyでスレッドの動きを制御しているものの、ネイティブスレッドベースでの動作となっているため、マルチコアの恩恵を受けることができます。

それではさっそく、配列の各要素を並列処理できるルーチンを貼付けてみます。
これは一番シンプルな実装で、配列に含まれる要素分だけスレッドを新規に立ち上げて、ガンガン処理させるというものです。

def multi_process(ary)
  threads = []
  ary.each_with_index do |e, i|
    threads << Thread.start(e, i){|tle, tli| yield(tle, tli) }
  end
  threads.each{|t| t.join }
end

# --- sample
a = [1, 2, 3]
multi_process(a) do |item, index|
  sleep 0.1*rand(10)
  puts "[#{index}]" + (item * 2).to_s
end

しかし、この実装では配列の個数の数だけスレッドが走ってしまうので、1つずつの要素に対する処理が重くて個数が多い場合、大量の要素を処理する場合Rubyが固まったようになってしまうでしょう。それにスレッドの切り替えや、実行チェックなどに多くのCPUタイムを取られてしまって実際の処理が思ったより進まないと思われます。この場合マルチスレッドプログラムにおけるProducer-Consumerパターンを適用して、Consumer(Worker)スレッドの数を限定することで同時にN個のスレッドしか動作させないことを保証することで並列性と処理効率のバランスを図ることができそうです。

Producer-Consumerパターンについて軽く解説すると、実際に処理を行うConsumer(Worker)スレッドをN個走らせて、これらに与えるタスクはブロッキングキューを使ってProducerスレッドから提供されます。ブロッキングキューは同期処理されたキューで、同時にアクセスされてもデータ構造が壊れることなく、正確に1つエンキュー(キューに追加)したり正確に1つデキュー(キューから取り出し)することができます。バグのないブロッキングキューを実装するのは思い出したり調べたりしないといけなくて意外と面倒だったりするのですが、rubyでは標準ライブラリthreadをrequireすることでQueueやSizedQueueといったクラスで利用することができます。SizedQueueではキューの最大の長さを指定することができ、キューが指定された大きさの状態でエンキューしようとすると他のスレッドからデキューされるまでエンキューメソッドはブロックします。

require "thread"

def multi_process2(ary, concurrency = 10, qsize = nil)
  q = (qsize) ? SizedQueue.new(qsize) : Queue.new

  producer = Thread.start(q, concurrency){|p_q, p_c|
    ary.each_with_index do |item, index|
      q.enq [ item, index, true]
    end

    p_c.times{ q.enq [nil, nil, false] }
  }

  workers = []
  concurrency.times do
    workers << Thread.start(q){ |w_q|
      task, index, flag = w_q.deq
      while flag
        yield task, index
        task, index, flag = w_q.deq
      end
    }
  end

  producer.join
  workers.each{|w| w.join }
end

# --- sample
a = [1, 2, 3]
multi_process2(a) do |item, index|
  sleep 0.1*rand(10)
  puts "[#{index}] #{item*2}"
end

こんなかんじになりました。
やはり多少コードは増えてしまいましたね。
multi_process2メソッドの第2引数では並列度としてConsumerスレッドの数を指定できるようにし、第3引数ではキューの長さを指定できるようにしました。キューの長さは明示的に指定しない場合無限(Queue.new)にするようになっています。

こんな感じで、案外簡単にマルチスレッド動作するプログラムが書けてしまいます。
ここで最後に、マルチスレッド初心者の方向けではありますが、基本的な落とし穴をご紹介しておきます。

以下の1〜10000の数をマルチスレッドで全て合計するプログラムを作ってみました。
数学的には1..nの合計はn(n+1)/2で求められることがわかっていますので、50005000が答えになるはずです。
しかし何回かこのプログラムを走らせると答えがおかしくなることがあります。

a = (1..10000).to_a
total = 0
multi_process2(a) do |item, index|
  total += item
end
puts total

これはtotalを読み書きしているdo〜endの部分で、total += itemという1文の命令が、実際には
1. totalを読み出す
2. (1)で読み出したtotalの値にitemの値を足す
3. (2)で計算した値をtotalに代入する
という分割されたフェーズで実行されることが原因です。
これが各スレッドで、まとまった固まりとして実行されず、互い違いに実行されるとおかしくなります。
これを防ぐには、一定の文の固まりを互い違いに実行させない仕組みが必要です。
RubyではMutexクラスのオブジェクトを使って、排他制御を行うことでこれを実現できます。

a = (1..10000).to_a
total = 0
lock = Mutex.new
multi_process2(a) do |item, index|
  lock.synchronize{ total += item }
end
puts total

これで答えがおかしくなることはなくなりました。
マルチスレッドから変数を操作する際には排他制御に気を配りましょう!
しかし、このスレッドのように並行実行しようとしている全てのコードが排他制御されているようでは本質的にマルチスレッドで実行する意味がまったくなくなってしまうので注意ですね。

RailsでTokyoTyrantを使ってみたらフリーズした

このエントリーをはてなブックマーク Share
2010.06.24    Ruby on Rails, 伊藤   タグ: , , , —    tomotaka   

※6/25にサンプルコードを修正しました
※7/1にサンプルコードを修正しました&補足を入れました

伊藤です。

最近社内のRailsを使ったプロジェクトで、外部サービスからのレスポンスを使う処理で、
いちいち外部サービスにリクエストを行って取得していてはとても時間がかかるので、
シンプルなキャッシュシステムを構築することになりました。

最初は馬場が記事にしてくれているようにファイルベースのものを用いていましたが(実際はもうちょっと複雑なものを使っていました)、
ファイル数(エントリ)が数万単位で増えた際、パフォーマンスが指数関数的に悪くなってしまったので、キーと値の格納にTokyoTyrantを利用することにしました。
TokyoTyrantの使い方はシステムエンジニアブログにも書いてみました(TokyoTyrantをRubyで使ってみた)ので、TokyoTyrantそのものについてはこちらも見てみてください。

Railsから、利用するためにlib/util/data_cache.rbといったかんじで、以下のようなモジュールを定義してみました。
任意のオブジェクトをデータ構造を保ったまま保存できるようにMarshalクラスによりシリアライズ/デシリアライズを行っています。
コントローラからはincludeして使うイメージです。

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

これをいろんなコントローラから使っていると、自分のデスクトップから開発している分には正常に動作するのですが、
多人数が同時でアクセスしたりする環境で使うとフリーズします。

どうやらgetやputを行っているときに、並行してこれらのTokyoTyrantとの通信が発生するメソッドを呼び出すと
通信の内容がごちゃごちゃになり、TTから正常にレスポンスを取得することができなくなっていたものと思われました。
マルチスレッド動作のサポートが各方面から望まれていたRailsですが、意外にもこのようなところでも弊害が発生しました。

対処としては、以下のようにRuby標準のthreadライブラリにあるMutexを使って、
TokyoTyrantへのアクセスを行う部分をクリティカルセクションとすることで、解決しました。

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

このクリティカルセクションに限らずRailsではまだ1プロセスで大量のリクエストをさばけるほどマルチスレッド処理のスループットが
出ませんので、本番環境の実運用では複数プロセスを上げて動かすというのが一般的、というのがまだしばらく続きそうですね。

問題意識を煽るようなタイトルをつけてしまいましたが、コントローラ部分の実装がスレッドセーフかどうかを保証するのはユーザの責任であるというRailsの仕様と、get/putなどの呼び出しはスレッドセーフではないというTokyoTyrantクライアントライブラリの仕様の問題ですね。

TokyoTyrant自体はクリティカルセクションで保護してもサクサク動作しているので、今後もキャッシュ機構が必要になったら積極的に使っていこうと思います。

7/1追記:
今回実装例で挙げているモジュールでは、1つのアプリケーションプロセスで最大で1つのTokyoTyrantコネクションしか使わないため、大量にTokyoTyrantにアクセスを行う場合TokyoTyrantにいちいちつなぎ直すか、複数のコネクションをプーリングするなどの戦略が必要です。調べたところTokyoTyrantのセッション確率のコストはそんなに高くないのでgetやputを少ない回数発行するような利用の場合いちいち接続する設計にするとシンプルで、バグが少なくてよいと思います。また、アプリケーションサーバとTokyoTyrantサーバを一つのマシンで動かしていると、TTへのリクエストの並列度が増えてくるとだんだんレスポンスが悪くなるようです。(アプリサーバとTTサーバに分けて実行すると数倍の速度が出る)

Android: ダイアログを表示して縦横が変わるとdismissでエラー

このエントリーをはてなブックマーク Share
2010.06.18    android, 伊藤   タグ: —    tomotaka   

伊藤です。

最近Androidアプリケーションの開発に関わっているのですが、
時間のかかる処理を行っているときにProgressDialogでクルクルを出して進捗や、時間がかかっていることを知らせるのはよくある方法かと思いますが、
今回記事にしようと思ったのは普通に実装するとProgressDialogが表示されているときに、画面の縦横を切り替えると、
ダイアログを消せないという問題に遭遇しました。

問題のコードは以下のようなものです。
少し長いですが、おつきあいください。

// 時間のかかる処理の前後にProgressDialogをshow/dismissするユーティリティクラス
public abstract class ShowProgressTask<Params, Progress, Result> extends
        AsyncTask<Params, Progress, Result> {

    private ProgressDialog mProgressDialog;

    public ShowProgressTask(ProgressDialog progressDialog) {
        super();
        mProgressDialog = progressDialog;
    }

    @Override
    protected void onPreExecute() {
        showProgressDialog();
    }

    @Override
    protected void onPostExecute(Result result) {
        dismisProgressDialog();
    }

    protected void showProgressDialog() { mProgressDialog.show(); }

    protected void dismisProgressDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    protected ProgressDialog getProgressDialog() { return mProgressDialog; }

}

class HogeActivity extends Activity {

	// Fileオブジェクトを引数にとり、処理結果をMyDataを返すような処理
	public class MyTask extends ShowProgressTask<File, Void, MyData> {
		public MyTask(ProgressDialog progressDialog) { super(progressDialog); }
		public MyData doBackInGround(File... params) {
			// 時間のかかる処理, MyDataを返す
		}
		public void onPostExecute(MyData result) {
			super.onPostExecute(result);
			// doBackInGroundがおわったあとに呼び出される
		}
	}

	// MyTaskをMyTask myTask = new MyTask(new ProgressDialog(this));
	// のように生成してmyTask.execute(fileObject);のように呼び出す処理

}

めでたくHogeActivityがうまくうごき、時間のかかる処理をプログレスダイアログ表示するためにMyTaskwを呼び出し、
いいかんじにプログレスダイアログのクルクルが回ってくれるとします。
まわっているときにAndroid端末を傾けて縦横を変えてみてください。
マニフェストファイルでorientationが固定されていないActivityであれば、このタイミングで現在フォアグラウンドにあるアクティビティのonPauseがよばれ、
このアクティビティは破棄されます。その後、新しい向きのActivityが生成されます。
MyTaskの処理はAsyncTaskを継承していますので、Activityのライフサイクルとは独立して並行動作しているので、
いずれ終了するとonPostExecuteのdismissが呼ばれ、ダイアログを閉じる処理が実行されますが、このタイミングで以下のURLで報告されているような
Activity名 has leaked window
といった例外が発生します。
http://groups.google.co.jp/group/android-developers/browse_thread/thread/5ec21fac2a7f52d8?pli=1

これはどうやらMyTask(ShowProgressDialog)が保持しているProgressDialog、が保持しているActivityのインスタンスが不正な参照というカンジのようでしたので、
以下のような変更を行うことで、縦横を切り替えて、AndroidがActivityオブジェクトをとっかえひっかえしても処理中のプログレスダイアログを持続的に表示させることが可能になりました。

  • MyTaskクラスはstaticクラスに
  • MyTaskを格納する変数はstatic変数に
  • ShowProgressDialogで実行中かどうかの状態を管理する
  • Activity#onPause()でMyTaskのdialogをdismissする
  • Activity#onCreate()でMyTaskオブジェクトがまだタスク処理中であれば新しいプログレスダイアログを設定し, 表示させる

最後に以上を適用したバージョンのコードを貼付けておきます。

// 時間のかかる処理の前後にProgressDialogをshow/dismissするユーティリティクラス
public abstract class ShowProgressTask<Params, Progress, Result> extends
        AsyncTask<Params, Progress, Result> {

    private ProgressDialog mProgressDialog;
    private boolean isInProcess; // 新しく追加: 状態を管理している変数(trueなら処理中)

    public ShowProgressTask(ProgressDialog progressDialog) {
        super();
        mProgressDialog = progressDialog;
    }

    // 新しく追加: Activity#onPauseで呼んであげる
    public void onActivityPause() {
    	dismisProgressDialog();
    }

    // 新しく追加: Activity#onCreateで, 処理中であることを確認した上で呼んであげる
    public void onActivityCreate(ProgressDialog progressDialog) {
    	mProgressDialog = progressDialog;
    	showProgressDialog();
    }

    // 新しく追加: 処理中かどうかを調べるメソッド
    public synchronized boolean isInProcess() { return isInProcess; }

    @Override
    protected void onPreExecute() {
    	isInProcess = true; // 新しく追加: 処理開始
        showProgressDialog();
    }

    @Override
    protected void onPostExecute(Result result) {
        dismisProgressDialog();
        isInProcess = false; // 新しく追加: 処理おわり
    }

    protected void showProgressDialog() { mProgressDialog.show(); }

    protected void dismisProgressDialog() {
        if (mProgressDialog != null && mProgressDialog.isShowing()) {
            mProgressDialog.dismiss();
        }
    }

    protected ProgressDialog getProgressDialog() { return mProgressDialog; }

}

class HogeActivity extends Activity {

	public static MyTask myTask;

	// Fileオブジェクトを引数にとり、処理結果をMyDataをかえすような処理
	public static class MyTask extends ShowProgressTask<File, Void, MyData> {
		public MyTask(ProgressDialog progressDialog) { super(progressDialog); }
		public MyData doBackInGround(File... params) {
			// 時間のかかる処理, MyDataを返す
		}
		public void onPostExecute(MyData result) {
			super.onPostExecute(result);
			// doBackInGroundがおわったあとに呼び出される
		}
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// 新しく追加
		if (myTask != null && myTask.isInProcess()) {
			myTask.onActivityCreate(new ProgressDialog(this)); // 新しいアクティビティへの参照を持つダイアログを設定してあげる
		}
	}

	@Override
	public void onPause() {
		super.onPause();

		// 新しく追加
		if (myTask != null && myTask.isInProcess()) {
			myTask.onActivityPause(); // アクティビティが消える前にダイアログを終了させる
		}
	}

	// MyTaskをMyTask myTask = new MyTask(new ProgressDialog(this));
	// のように生成してmyTask.execute(fileObject);のように呼び出す処理

}

Have a nice hacking day!

FirefoxアドオンJSONViewでJSONを見やすくする

このエントリーをはてなブックマーク Share
2010.06.15    Web, 伊藤   タグ: , —    tomotaka   

伊藤です。

最近JSONをやりとりするWebシステムの開発に関わっていて、プログラム用に加工されたフラットなJSONが見づらくて困っていました。
そこで軽く検索してみたら、JSONViewという非常に便利なFirefoxアドオンを発見しました!

jsonview

1行につめこまれていたJSONが画像のような感じで見れます。バッククオートなどのエスケープも外してくれるし、URLはクリックでそのまま飛ぶことができます。
注意としては、サーバ側のレスポンスのContent-typeがapplication/jsonじゃないと、この整形機能は発動しないようです。
JSONを使ったシステムの開発をされている方は入れてみてください。

PHP: Windowsでrename, unlinkができなくてハマる

このエントリーをはてなブックマーク Share
2010.06.03    PHP, Windows, 伊藤   タグ: , —    tomotaka   

伊藤です。

Linux/Mac上で開発されていたコードをWindows上でXAMPP(Apache, MySQL, PHP)を使って動かそうとしたところ、動かないコードがありました。
それはファイル操作のrenameです。

windowsではファイルオープン中にrenameやunlinkでファイルをリネームしたり、削除したりすることができないようです。

Rubyのメーリングリストでも、話題になっていました。(PHPとは関係ないですが、プラットフォームの問題だと思いますので同じでしょう)
http://pub.cozmixng.org/~the-rwiki/rw-cgi.rb?cmd=view;name=Windows+%A4%CE+IO

対策としては、きちんとcloseしてからrename, unlinkすればOKでした。

また、Windowsでは、rename(old, new)としたときにnewのパスに既にファイルが存在していると、
エラーになるという報告もありますので、併せて気をつけたいですね。
http://ml.php.gr.jp/pipermail/php-users/2005-October/027828.html

UNIX互換OSばかり使っていると、Windowsでの開発のハマりどころを忘れてしまいそうです^^;

ではでは

サーバで動いているWordPressをローカルに再現する

このエントリーをはてなブックマーク Share
2010.05.27    PHP, 伊藤      tomotaka   

伊藤です。

サーバで動いているWordPressをローカルで再現して、テーマを編集したいときのやりかたを紹介します。
ローカルはWindows+XAMPPを想定していますが、基本的にapacheとmysqlを使っているならとくにどんな環境でもいけそうです。

  1. ファイル一式をサーバから持ってくる。これはtarで固めてscpなどでよいでしょう。
  2. サーバのデータベースデータを持ってくる。mysqldumpやphpMyAdminのエクスポートなどを使いましょう。
  3. ローカルのMySQLにデータベースを作る(wp-config.phpを参考に)
  4. ローカルのMySQLにユーザを作る(wp-config.phpを参考に)
  5. ローカルのMySQLにサーバのデータベースデータを流しこむ。 mysql -u root < serverdata.sql
  6. ローカルのphpMyAdminでwp_optionsテーブルのsiteurl, homeを編集する(ローカルで運用したいURLに変更)
  7. ローカルのApacheでDocumentRootやAliasを設定する

基本的にはこれでOKでした。

コマンド作業などをもうすこし補足しておきます。

サーバ上のファイルを固めてダウンロード

$ sudo tar cvzf /home/myself/blog.tar.gz /path/to/blog

blog.tar.gzのダウンロードはWinSCPなどを使うとよいでしょう。

サーバのデータベースデータをもってくる

$ mysqldump -u root wordpress > /home/myself/blog.sql

MySQLのrootパスワードをきちんと設定している場合は-pオプションをつけて入力します。
MySQLのrootを使えない場合はwp-config.phpを参考に、wordpress用ユーザでDBを指定して出力させます。

$ mysqldump -u USER -p DATABASE > /home/myself/blog.sql
Enter password: (PASSWORD)

blog.sqlのダウンロードはWinSCPなどを使うとよいでしょう。

ローカルのMySQLにDB, ユーザを作る

コマンドプロンプトでxamppのmysqlを起動しましょう。
この例ではmysql.exeまでの絶対パスを記述していますが、環境に合わせてこのパスを書き換えてください。

> C:\Users\tomotaka\Documents\xampp\mysql\bin\mysql -u root
mysql> CREATE DATABASE `(DB名)`;
mysql> GRANT ALL ON `(DB名)`.* TO '(ユーザ名)'@localhost IDENTIFIED BY '(パスワード)';
mysql> \q

これでデータベースとユーザができましたね。
あとはサーバ上の記事データを流し込みましょう。

ローカルのMySQLにデータを流し込む

> C:\Users\tomotaka\Documents\xampp\mysql\bin\mysql -u root < C:\Users\tomotaka\Documents\blog.sql

簡単です。

phpMyAdminでwp_optionsを編集

  1. phpMyAdminに入る
  2. wordpressのDBを選択する(流しこみが成功してればある)
  3. wp_optionsのテーブルを選択して、表示する
  4. option_nameカラムがsiteurlの行のエンピツマークをクリックして編集モードになる
  5. サーバで運用してたブログのアドレスが入っているが、ローカルで使うアドレスに書きかえる
  6. option_nameカラムがhomeの行のエンピツマークをクリックして編集モードになる
  7. サーバで運用してたブログのアドレスが入っているが、ローカルで使うアドレスに書きかえる

こんなかんじ。

以上です!
みなさんの快適なWordPress生活のお役に立てば幸いです。

分散バージョン管理Bazaarのチュートリアル(Eclipse対応!)

このエントリーをはてなブックマーク Share
2010.05.25    Eclipse, 伊藤   タグ: , , , —    tomotaka   

こんにちは。伊藤です。

今日はアプリケーション開発とは切っても切り離せないバージョン管理システムについて書こうと思います。
BPSでは現在バージョン管理システムとしてSubversionをメインに使用していますが、
ここ数年の分散バージョン管理システムの盛り上がりから、
分散バージョン管理システムについて最近調査・検証しています。

ある共同プロジェクトでGit, Mercurialとならぶ分散バージョン管理システム3強のうちの1つ、Bazaarを試す機会があったので、
EclipseでBazaarを使うところまでを共有したいと思います。

Bazaarのインストール

Bazaarのインストールはとても簡単です。

Bazaar公式「Download and Install」のページから自分の環境を選んで、インストーラをダウンロードします。
または説明に沿ってインストールします。

WindowsもBazaarプロジェクトがインストーラを用意しています。
MacOSX用もdmgがありましたので、インストーラが用意されているものと思います。

WindowsではTortoiseBZRというTortoiseSVNのようなシェル統合もインストールされるようですが、
2010/05時点でTortoiseSVNほどの完成度はないようです。

Windowsの場合インストールが終わったら環境変数PATHが更新されているので、いったん再起動するとコマンドラインにもパスが通ります。
bzrコマンドのオプションなどは環境に依存せず同じですので、安心して使えますね。

ローカルにリポジトリを作って使ってみる

windowsでも、UNIX系OSでも、適当にディレクトリを作成して、そこにcdして
bzr init
と打ってみます。
これだけでそのディレクトリは既に立派なBazaarリポジトリです!

ここにtest.txtというファイルを作ってみましょう。
名前や内容はなんでもいいですが…

$ echo "test" > ./test.txt

この状態で、いまBazaarでどういう状態かというのを訪ねるにはbzr statusコマンドを使います。
省略してstatでもいけます。

$ bzr stat
unknown:
  test.txt

こんな感じになるはずです。
これは現在bazaarで管理されてないファイルtest.txtがあるよ、という意味です。
test.txtをBazaarに管理させましょう。svnと同じように、addしてcommitです。

$ bzr add test.txt
adding test.txt
$ bzr commit -m "added test.txt by tomotaka"
Committing to: /home/tomotaka/code/test/
added test.txt
Committed revision 1.

こんな感じになるはずです。
add, stat, commitの他にもmv, log, mkdir, remove, remove-tree, diff, revertなどのコマンドがあります。
だいたいsvnなんかと同じですよね。

詳しいコマンドのリファレンスは

bzr help
bzr help COMMAND

などで調べることができます。

もちろんBazaar公式のオンラインリファレンスを見るのもいいでしょう!

これで一つのディレクトリをバージョン管理することはできるようになりましたね。
次のセクションではネットワークのむこうにあるリポジトリのやりとりを行う実践編です!

リモートのサーバからリポジトリをbranch

ここでは前提として、リモートサーバ上にプロジェクト全員で共有するリポジトリがあり、
最終的な変更はすべてそこに集約しようという前提で話を進めます。
(ようするにSubversionの作業フローを維持してどうBazaarでモアベターに置き換えるかということです。)

どうやら分散管理システムでは、プロジェクトを複数人で共同開発する際は個人ごとにリポジトリを持つのが普通のようなので(そうしないと分散の意味がない)、
checkoutではなく、branchコマンドでリモートにあるリポジトリの複製を作りましょう。(gitならgit cloneかな?)
リモートサーバへはbzr+sshプロトコルで接続するのが簡単そうです。
sshのサービスがあがっていればなにも設定しなくても使えます。便利ですね。

$ bzr branch bzr+ssh://tomotaka@honyarara.bpsinc.jp/path/to/repository/ ./localbranch

これで自分のデスクトップで作業を行うことができますね。
このlocalbranchに対するコミットは自分だけのコミットになるので、
自分の中でのひと区切りや、一定機能単位などで好きにコミットできます。
ここが分散バージョン管理システムの利点です!
subversionなどの集中型のバージョン管理システムでは、
(とくにメンバーの多いプロジェクトでは)リビジョン番号の爆発を防ぐためや、トラッキングのしやすさを維持するために
ある程度ローカルでまとめあげてから(バグを取り除いて、完全に動く状態にする)コミットするのが慣例かと思います。

しかし、これのよくないところは「バグが取り除かれて、完全に動く状態になる」までの一切の変更はバージョン管理されていないのです!
つまり「バグが取り除かれて、完全に動く状態」にならない限り、昨日のコードに戻したい
とかができないわけですね。上記のポリシーだと。

分散リポジトリなら、自分のリポジトリにガンガンコミットしても、誰にも迷惑はかかりませんし、
むしろバージョン管理ができるのでガンガンコミットするべきです。
適切な単位=バグが取り除かれて、完全に動く状態になったら、それをまとめてリモートのリポジトリに反映してあげましょう。

リポジトリ間での差分をやりとりするには、pushとpullというコマンドを使います。
pushは自分の変更履歴を引数で指定されたリポジトリに送ります。
pullは指定したリポジトリの変更履歴を取り寄せて自分のリポジトリに反映します。

リポジトリは互いに対等ですが、Bazaarではコピーしたリポジトリは自分のコピー元の
リポジトリのURLを覚えているため、リポジトリを指定しないbzr pushを実行すると
コピー元リポジトリにpushしてくれます。これがsvnでいう中央サーバへのコミットのようなイメージですね。

$ bzr push

うーん、簡単。

となりの人のマシン(192.168.0.123)の、となりの人のリポジトリから中央サーバを介さずに
となりの人が施した変更をもらってくることもできます。

$ bzr pull bzr+ssh://tomotaka@192.168.0.123/tonari/no/hito/no/repository

これでネットワーク経由でのバージョン管理もマスターできました!?

bzr-eclipseを使ってみる

BPSではいろいろな言語をプラグインを利用して統一的なインタフェースで編集することができるEclipseを
共通の開発環境として使っています。
普段はSubclipseプラグインを使ってサーバ上のSubversionリポジトリとやりとりを行っているため、
似たような使い勝手のEclipseプラグインを探してみたところ、
どうやらBazaarにもbzr-eclipseというEclipseプラグインがあることがわかりました。

ただ、このプラグインは単独では動かず、bazaarが実行するPCにインストールされていないといけません。
先にBazaarをインストールしましょう!
あ、Bazaarだけでなくbzr-xmloutpuptプラグインが必要でした。
Windowsのインストーラは標準でbzr-xmloutputを入れてくれます。
Ubuntuだったらaptitude install bzr-xmloutputでインストールできます。

インストールは簡単で、いつものeclipseのプラグインインストーラにURLを入れればいけます。
最新バージョンがアップロードされているURLは、Bazaar公式のbzr-eclipse intallationページから参照してください。
うまくインストールできましたでしょうか。
インストールできたら設定画面のTeam > Bazaarから、Bazaarのバイナリ(bzr.exe)を選択する必要があります。
Windows 7ではインストーラのデフォルトのままインストールしたらC:\Program Files (x86)\Bazaar\bzr.exeでした。
環境に合わせて適切に設定しましょう。

しかしこれだけではbzr+sshプロトコルでリモートサーバとうまくやりとりすることができません。
とくにWindowsでは設定がめんどくさいです!(Linux版Eclipseは普段使わないのでここでは触れられません, すいません!)
bzr+sshをうまくいかせるためにいろいろ悪戦苦闘したので、ここで共有します。

まず、PuTTYで鍵交換方式によるSSH接続のページを参考に、
コマンドプロンプトで

$ bzr branch bzr+ssh://目的のリモートリポジトリURL/

を実行してパスワードが出なくなるところまで持っていきます。
おおざっぱに書くと

  1. puttygen, pagentをDLする
  2. puttygenでprivate keyとpublic keyをつくる, パスフレーズはなしで。
  3. pagentをdouble clickで起動, タスクトレイにいる帽子かぶってるやつ右クリックでadd key => つくったprivate key登録
  4. 公開鍵をサーバに転送。 ssh-keygen -i -f (公開鍵ファイル) >> authorized_keysで変換してauthorized_keysに追加
  5. コマンドラインでbzr branch bzr+ssh://user@host/path/to/repos/ localrepos/ とかやって認証なしでbranch切れるか確認する
  6. うまくいけばbzr-eclispeでもいけるはず。(File > New > Project > Bazaarにbranch作成がある)

ですね。

普段サーバ作業をあまりしない方には以下のチェックリストが参考になるかもしれません:

  • 公開鍵をauthorized_keysに登録するときに変換し忘れてない?
  • .sshディレクトリのパーミッションは700 ? オーナーは自分 ?
  • authorized_keysのパーミッションは600 ? オーナーは自分 ?
  • ファイル名間違ってない ?
  • 公開鍵と秘密鍵間違えてない ?

コマンドプロンプトでうまくいけば、bzr-eclipseでもうまくいきます。
bzr-eclipseでリモートリポジトリからbranchをつくるには、new project > Bazaarから行います。
Importではないので注意!

—-

いかがだったでしょうか?
以上で今回のBazaarチュートリアルはおしまいです。

それではBazaarで楽しい開発ライフを!

古い投稿 »

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.