- 平成24年5月15日(火) 17時05分02秒
-
区分
- Rails
-
報告者:
- tahara
こんにちは、tahara です。
ようやくいこーよを like '%foo%' の(SQL 力づく)全件検索から、MySQL の全文検索に変更しました。
最初は mroonga を使おうかと思ったのですが、結局は MeCab を使って MySQL の全文検索をそのまま使うことにしました。
MySQL の全文検索は MyISAM じゃないと動かないので ENGINE = MyISAM で検索用のテーブルを作成します。
class CreateFacilityIndices < ActiveRecord::Migration
def up
execute <<SQL
create table facility_indices (
`id` int(11) NOT NULL AUTO_INCREMENT,
`facility_id` int(11) NOT NULL,
`content` text NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`),
FULLTEXT INDEX (content)
) ENGINE = MyISAM DEFAULT CHARSET utf8;
SQL
Facility.find_each {|f|f.update_facility_index()}
end
def down
execute "drop table facility_indices"
end
end
検索したいテーブル(モデル)の after_save で上記のテーブ(facilities_indices)の登録、更新を行うようにします。
また、全文検索用のスコープも作成します。
order by で floor しているのは、同じようなスコアの場合は他の条件でソートしたいめです。
class Facility < ActiveRecord::Base
after_save :update_facility_index
has_one(:facility_index, :dependent => :destroy)
def update_facility_index
index = self.facility_index || self.build_facility_index
index.update_from_facility
end
scope(:scoped_by_word,
lambda { |text|
joins(:facility_index).
where("MATCH (facility_indices.content) AGAINST (?)", FacilityIndex.normalize(text)).
order(sanitize_sql(["floor(MATCH (facility_indices.content) AGAINST (?)) desc", FacilityIndex.normalize(text)]))
})
end
FacilityIndex では次のように、全文検索のインデックスを作ります。
- 検索対象の検索対象フィールドを一つも文字列にコンカチします。
- 地域と都道府県は全文検索結果スコアへの影響を大きくするために、単純に数回繰り返します。
- nkf で文字種の正規化を行います。
- MeCab で分解します。
- 助詞、助動詞は除きます。
- 原形があれば、原形を使うようにします。
- 「ある」などは検索上無意味なのでストップワードしとて除外します。
- MeCab 前にやっちゃうと、うまくいかないカタカナひらがな変換を行います。
- 以上のように正規化したものを FULLTEXT INDEX の付いたカラムに登録します。
検索時にはユーザの入力した検索ワードを同様に FacilityIndex::normalize で正規化して MATCH (...) AGAINST で検索します。
上記の Facility::scoped_by_word です。
# -*- coding: utf-8 -*-
require 'nkf'
require 'MeCab'
# 一文字からも検索できるように
# sudo vi /etc/mysql/my.cfg
# [mysqld]
# ft_min_word_len = 1
class FacilityIndex < ActiveRecord::Base
belongs_to :facility
# Facility.find_each {|f|f.update_facility_index()}
def update_from_facility
facility = self.facility
# 都府県は除く
prefecture_name = facility.prefecture_name.try(:gsub, /(都|府|県)$/, '')
# region と prefecture は加重する。
self.content = FacilityIndex.normalize("#{facility.name} #{facility.kana} #{facility.pr} #{facility.description} #{([facility.prefecture.region.name]*7).join(' ')} #{([prefecture_name]*5).join(' ')}#{facility.address} #{facility.search_keyword} #{facility.features.map(&:name).join(' ')} #{facility.tag_list.join(' ')}")
self.save!
self
end
class << self
def normalize(text)
# UTF8
# 半角カタカナ => 全角カタカナ
# MIMEはデコードしない
# 全角アルファベット、全角スペースを半角に
#
# ひらがな、カタカナは変換すると MeCab トークナイザが
# 正しく動かないのでそのままにする。
text = NKF.nkf('-WwXm0Z1', text).gsub(/[\r、。・()「」【】!?]/, '')
mecab = MeCab::Tagger.new
node = mecab.parseToNode(text)
s = ''
while node
features = node.feature.force_encoding('UTF-8').split(/,/)
unless %w[助詞 助動詞].include?(features[0])
# 原形を使う。
word = features[6]
# 原形がなければ、表層形を使う。
word = node.surface.force_encoding('UTF-8') if word == '*'
unless stop_word?(word)
s += word + ' '
end
end
node = node.next
end
# Mecab の後にカタカナをひらがなに変換する
NKF.nkf('-Wwh1', s)
end
def stop_word?(word)
# 下の select_stop_word でひっかかったワード
return true if ["", "ある", "市", "OK", "いる", "施設", "する", "-", "お", "れる", "できる"].include?(word)
false
end
# 全件の 50% にでてくるものは MySQL の全文検索でひっかからないので、
# そのようなワードをストップワードにする。
def select_stop_word()
hash = Hash.new(0)
FacilityIndex.find_each do |x|
x.content.split(/ /).uniq.each do |word|
hash[word] += 1
end
end
half = FacilityIndex.count / 2
stop_words = hash.select do |k, v|
v > half
end
puts stop_words
stop_words.map {|k, v| k}
end
end
end
これで、全文検索ができました。
ある程度あいまいな検索ができるようになった上に、検索も速くなりました。
最後に、弊社ではシステムエンジニア、プログラマ、インフラエンジニアなどを募集しています。
おきがるにお問い合わせください。
>View Comments
このページの上へ戻る
- 平成24年5月7日(月) 17時22分51秒
-
区分
- Rails
-
報告者:
- tahara
いこーよ の GW の負荷対策として MySQL のレプリケーションを使いマスタースレーブ構成にしてみました。
一番悩んだのがマスタースレーブ構成のためにどのライブリを使うか。
次のような理由から seamless_database_pool をフォークして使うことにしました。
- アプリ起動時にスレーブが落ちていても動く。
ただし、この場合は途中からスレーブが動きだしてもスレーブにはつながらない。
- アプリ起動中にスレーブが落ちても動く。
- 途中でスレーブが復帰すればまたスレーブにつながるようになる。
- マスターをスレーブに含めることも、含めないこともできる。
- マスター、および各スレーブの接続ウエイト指定ができる。
フォークする理由は次のとおりです。
以下、セットアップ手順を書きていきます。
マスターとスレーブ両方にレプリケーション用のアカウントを作成します。
mysql -uroot
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl@'%' IDENTIFIED BY 'password';
AWS のセキュリティグループがあるので repl@'%' でよし。
マスターの my.cnf を編集します。
sudo vi /etc/mysql/my.cnf
[mysqld]
server-id = 10
log_bin = /var/log/mysql/mysql-bin.log
で MySQL を再起動。 sudo service mysql restart
スレーブの my.cnf を編集します。
sudo vi /etc/mysql/my.cnf
server-id = 11
log_bin = /var/log/mysql/mysql-bin.log
relay_log = /var/log/mysql/mysql-relay-bin.log
log_slave_updates = 1
read_only = 1
slave_load_tmpdir = /var/tmp
slave_load_tmpdir はマシンを再起動してもファイルが消えないディレクトリにしておかないと問題があるようです。
で MySQL を再起動。 sudo service mysql restart
スレーブで次のようにしてマスターを指定します。
mysql -uroot
CHANGE MASTER TO MASTER_HOST='ec2-123-123-123-123.ap-northeast-1.compute.amazonaws.com', MASTER_USER='repl', MASTER_PASSWORD='password';
show slave status\G
マスターからデータをスレーブに投入します。
mysqldump -uroot --single-transaction --all-databases --master-data=1 | ssh -C deployer@ec2-54-248-109-31.ap-northeast-1.compute.amazonaws.com mysql -uroot
スレーブでレプリケーションを開始します。
mysql -uroot
start slave;
show slave status\G
Rails で seamless_database_pool を使うようにします。
application_controller.rb でデフォルトでマスターを使うようにします。
class ApplicationController < ActionController::Base
include SeamlessDatabasePool::ControllerFilter
use_database_pool :all => :master # 個別にスレーブを使うように指定する。
スレーブを参照したいコントローラで次のように書きます。
アクション毎に指定できます。
class FacilitiesController < ApplicationController
use_database_pool [:index, :show, :map_xhr] => :persistent
application_controller.rb で全てスレーブ参照にしても更新系の SQL はちゃんとマスターにいくのですが、
レプリケーションの遅延があった場合よくあるアップデートして show にリダイレクで古い情報を表示してしまう問題と、
ごく一部のアクションが全クエリーのほとんどをしめるという理由からデフォルトマスターで、個々にスレーブを使うように指定する方針にしました。
おかげさまで、今回の GW の瞬間的なピークは New Relic で 3,092rpm を記録し、 Google Analytics リアルタイムのアクディブユーザで 1,900 を越えました。
AWS のオートスケールでアプリサーバも1台から5台まで自動的にスケールしました。
いこーよ のご利用ありがとうございました。
最後に、弊社ではシステムエンジニア、プログラマ、インフラエンジニアなどを募集しています。
おきがるにお問い合わせください。
>View Comments
このページの上へ戻る
- 平成24年4月16日(月) 16時20分38秒
-
区分
- AWS
-
報告者:
- tahara
いこーよ を Amazon Web Service (AWS) に移行しました。
御蔭様でいこーよのアクセス数は伸びてきており、これで3回目の引越しです。
移行に際して一番悩んだのがオートスケールを前提としたデプロイ、監視でした。
オートスケールの設定をしている場合、インスタンスは負荷に応じて起動したり削除されたりします。
当然 IP アドレスも都度変っていきます。
- cap production deploy:migrations する時に生きているサーバはどれ?
- オートスケールでインスタンスが起動した時、ちゃんと最新のソースで起動するにはどうする?
- munin でリソース監視したいけど、監視対象サーバはどれ?
といったところが、全くわかりませんでした。
世のオートスケール利用者の方々はどうされているんでしょうか。
いろいろ試行錯誤し、結局のところ次のようにしました。
- unicorn を passenger に変更
- unicorn に問題があったわけではありません。
- cap は DB サーバにのみ行う。
- passenger が動くアプリサーバは DB サーバのデプロイディレクトリを nfs マウント。
これでデプロイは DB サーバに対してだけ行えばよく、アプリの再起動も DB
サーバで touch tmp/restart.txt を行えば、それを nfs マウントしているアプ
リサーバの passenger がみんな再起動する、という仕組みです。
munin の方はうまい方法を思い付かなかったのでシェルスクリプトを書きました。
DB サーバは Elastic IP を使っているのでホスト名が固定です。
ec2-describe-instances の出力から DB サーバを除いて起動しているホスト名を取得し、
/etc/munin/munin.conf を都度生成します。
#!/bin/sh
export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-i386
export EC2_HOME=/opt/ec2-api-tools
export PATH=$EC2_HOME/bin:$PATH
export EC2_PRIVATE_KEY=/foo/pk-KKKKKKKKKKKK.pem
export EC2_CERT=/foo/cert-XXXXXXXXXXXXXXX.pem
export EC2_URL=https://ec2.ap-northeast-1.amazonaws.com
ec2-describe-instances --show-empty-fields | \
sed -n '/^INSTANCE.*running/p' | \
sed -n '/54-248-122-232/!p' | \
awk '{ print $4, $5; }' | \
sed -e '=' | \
sed -e 'N;s/\(.*\)\n\(.*\) \(.*\)/[iko-yo.net;ap\1]\n address \2\n use_node_name yes\n/' > /tmp/aws-munin.conf.tmp
sed -e '/KOKO/r /tmp/aws-munin.conf.tmp' -e '/KOKO/d' /etc/munin/munin.conf.template > /etc/munin/munin.conf
/etc/munin/munin.conf.template はこんな感じ。
...前略...
# DB サーバ
[iko-yo.net;db.iko-yo.net]
address db.iko-yo.net
# アプリサーバ
KOKO
...後略...
さて DB サーバ m1.large x 1, アプリサーバ c1.medium x 1 で本番投入してみたのですが、DB サーバが重い。
その夜急遽 DB サーバを c1.xlarge に変更。
c1.xlarge にしたらリソースが余ったで、アプリも DB サーバで動かして c1.medium のアプリサーバ停止。
という構成になりました。
きっと GW か夏休みのピークにはオートスケールの出番が来るかと思います。
ちなみに最近のピーク時のアクセスは nginx のリクエスト数でいうと 90 request/second で
New Relic の Throughput でいうと 770 rpm くらいです。
はやく DB の全件 like 検索しているのをなんとかしたいところです。
最後に、弊社ではシステムエンジニア、プログラマ、インフラエンジニアなどを募集しています。
お気軽るにお問い合わせください。
>View Comments
このページの上へ戻る
- 平成24年3月21日(水) 16時07分50秒
-
区分
- Rails
-
報告者:
- tahara
こんにちは tahara です。
先日 いこーよ を Rails 3.1.0 から Rails 3.2.2 にアップグレードしました。
今日はその模様を報告したいと思います。
まず Gemfile で Rails のバージョン指定を 3.2.2 にし bundle update しました。
gem 'rails', '3.2.2'
Asset Pipeline は使っていなので、
jquery-rails-2.0.1/vendor/assets/javascripts/jquery_ujs.js を
public/javascripts にコピー。
config/environments/development.rb に次を追加。
開発環境で遅いクエリーは自動的に explain してくれるのはいいですね。
log/development.log に出力されます。
# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
config.active_record.auto_explain_threshold_in_seconds = 0.5
で、動かしてみたのですが ssl_requirement でエラーになりました。
どうやら bartt-ssl_requirement を使うのがよさそう。
Gemfile を次のように書き替えて bundle install したらうまく動きました。
gem 'bartt-ssl_requirement', '~>1.4.0', :require => 'ssl_requirement'
と思ったのですが、ssl_allowed しているアクションが http から https にリ
ダイレクトされてしまう現象に遭遇しました。
もともとの ssl_requirement では ssl_allowed が ssl_required より優先されていたのに、
bartt-ssl_requirement では ssl_required の方が優先されるようになっていました。
ここは github の慣例にならって fork し、ssl_allowed が優先されるようにしました。
修正は一行です。
という感じで いこーよ をRails 3.2.2 にアップグレードできました。
弊社ではエンジニアを募集しています。
詳細はこちらを御覧ください。
>View Comments
このページの上へ戻る
- 平成24年2月27日(月) 15時10分07秒
-
区分
- Titanium
-
報告者:
- tahara
こんにちは、tahara です。
CoffeeScript を使って Titanium でアプリを作るにはいくつか方法があるようです。
弊社ではこのような場合 Common Lisp を使います(個人的に)。
コードは下記のとおり。
もし動かしてみたいという方がいらっしゃるようでしたら、
defparameter しているものを環境に合わせて変更してください。
あと Quicklisp でインストールできるものの他に
https://github.com/quek/info.read-eval-print.series-ext も必要になります。
エラーがあれば repl に表示されます。
一度 Titanium Studio から Emulator でアプリを起動していれば、
CoffeeScript の保存で自動的に Eumlator での実行まで行います。
快適です。
;;;; CoffeeScript を使って Titanium で Android アプリを作る
;;;;
;;;; 参考にしたサイト
;;;; http://a-h.parfe.jp/einfach/archives/2011/0106235955.html
(eval-when (:compile-toplevel :load-toplevel :execute)
(require :alexandria)
(require :bordeaux-threads)
(require :cl-ppcre)
(require :info.read-eval-print.series-ext))
(info.read-eval-print.series-ext:sdefpackage
:compile-coffee
(:use :cl))
(in-package :compile-coffee)
(defparameter *builder.py*
"~/.titanium/mobilesdk/linux/1.8.1/android/builder.py"
"Titanium のビルドコマンド
iPhone で動かす場合は s/android/iphone/ でいいかもしれない")
(defparameter *project-dir*
"~/Titanium\\ Studio\\ Workspace/outing-app"
"Titanium のプロジェクトディレクトリ")
(defparameter *android-sdk*
"~/local/opt/android-sdk-linux"
"Android SDK のディレクトリ")
(defparameter *interval* 0.3
"ファイルの変更監視間隔")
(defvar *compile-titanium-process* nil)
(defun escape-sh-arg (arg)
(ppcre:regex-replace-all " " arg "\\ "))
(defun sh-async (control-string &rest format-arguments)
(let* ((command (apply #'format nil control-string format-arguments))
(process (progn (format *terminal-io* "~&~a" command)
(sb-ext:run-program "/bin/sh"
(list "-c" command)
:wait nil
:output :stream
:error :stream)))
(streams (list (sb-ext:process-output process)
(sb-ext:process-error process)))
(threads (labels ((cat (stream)
(bordeaux-threads:make-thread
(lambda ()
(collect-stream
*terminal-io*
(delete #\Return
(scan-stream stream #'read-line))
#'write-line)))))
(collect (cat (scan 'list streams))))))
(bordeaux-threads:make-thread
(lambda ()
(let ((exit-code (sb-ext:process-exit-code (sb-ext:process-wait process))))
(collect-ignore (progn
(bordeaux-threads:join-thread (scan 'list threads))
(close (scan 'list streams))))
(if (zerop exit-code)
(prog1 t (format *terminal-io* "~&ok"))
nil))))
process))
(defun sh (control-string &rest format-arguments)
(let* ((command (apply #'format nil control-string format-arguments))
(process (progn (format *terminal-io* "~&~a" command)
(sb-ext:run-program "/bin/sh"
(list "-c" command)
:wait nil
:output :stream
:error :stream)))
(streams (list (sb-ext:process-output process)
(sb-ext:process-error process)))
(threads (labels ((cat (stream)
(bordeaux-threads:make-thread
(lambda ()
(collect-stream
*terminal-io*
(delete #\Return
(scan-stream stream #'read-line))
#'write-line)))))
(collect (cat (scan 'list streams)))))
(exit-code (sb-ext:process-exit-code (sb-ext:process-wait process))))
(collect-ignore (progn
(bordeaux-threads:join-thread (scan 'list threads))
(close (scan 'list streams))))
(if (zerop exit-code)
(prog1 t (format *terminal-io* "~&ok"))
nil)))
(defun compile-titanium ()
(when *compile-titanium-process*
(sb-ext:process-kill *compile-titanium-process*
sb-posix:sigterm)
(setf *compile-titanium-process* nil))
(setf *compile-titanium-process*
(sh-async "~a run ~a ~a" *builder.py* *project-dir* *android-sdk*)))
(defun compile-coffee (file)
(when (sh "coffee -o ~a -c ~a"
(escape-sh-arg (directory-namestring file))
(escape-sh-arg (namestring file)))
(compile-titanium)))
(defun run ()
(sb-cltl2:compiler-let ((series::*suppress-series-warnings* t))
(collect-ignore
(compile-coffee
(choose-if (complement
(lambda (x)
(alexandria:starts-with-subseq ".#" (file-namestring x))))
(scan-file-change
(format nil "~a/**/*.coffee" *project-dir*)
:interval *interval*))))))
;; 実行
;; (bordeaux-threads:make-thread #'run :name "compile-coffee")
>View Comments
このページの上へ戻る
- 平成24年2月21日(火) 15時30分47秒
-
区分
- WordPress
-
報告者:
- tahara
こんにちは tahara です。
今回は WordPress です。
http://example.com/foo がリクエストされた時、クライアントがスマホだったら、
リダイレクトすることなく、同じ URL のまま、スマホ用のページを表示するには、
どうしたらいいか? 「、」が多い。
これがリダクレクトして別 URL になっても OK だよ、なら話は簡単だったのですが、
WordPress のパーマリンクの関連でかなり悩みました。
mod_rewrite で /foo が来たら /smartphon/foo に書き替えればいいや、と思っ
ていたのですが、WordPress のパーマリンクは /smartphon/foo に書き替えて
も最初のブラウザの要求の /foo をもとにページを返してきます。
で、結局は自分自身に proxy して解決しました。
しかし、自分自身への proxy は
http://httpd.apache.org/docs/current/mod/mod_rewrite.html
によると
doesn't make sense, not supported
らしいです。
とりあえず思ったとおりに動いてはいますが、他にいい方法はないものでしょうか?
.htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# for smartphone
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/smartphone.*$
RewriteCond %{REQUEST_URI} !^/index.php
RewriteCond %{HTTP_USER_AGENT} (iPhone|iPod|Android|BlackBerry) [NC]
RewriteCond %{HTTP_USER_AGENT} !iPad [NC]
RewriteCond %{HTTP_COOKIE} !wptouch_switch_toggle=normal
RewriteRule .* - [E=SMART_PHONE_P:T]
RewriteCond %{ENV:SMART_PHONE_P} =T
RewriteRule ^$ http://example.com/smartphone [P,L]
RewriteCond %{ENV:SMART_PHONE_P} =T
RewriteRule ^foo$ http://example.com/smartphone/foo [P,L]
RewriteCond %{ENV:SMART_PHONE_P} =T
RewriteRule ^foo/bar$ http://example.com/smartphone/bar [P,L]
# for WordPress permlink
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
>View Comments
このページの上へ戻る
- 平成24年2月6日(月) 17時09分17秒
-
区分
- Common Lisp
-
報告者:
- tahara
こんにちは、tahara です。
前々回のRails3 への移行に続いて移行ネタです。
本ブログ「アクトインディ技術部隊報告書」は Common Lisp (SBCL) + Hunchentoot + Elephant で動いていました。
しかし、1年2ヶ月ぶりのエントリを書こうとした時、新規エントリの登録ができなくなっていました。
Elephant をアップグレードしたのが原因だったのですが、
新しいバージョンにマイグレーションするのがめんどうだったので、
Rucksack に移行してしまいました。
Rucksack は同時に一トランザクションしか時行できなかったのですが、
そのへんは適当に改造して同時に複数トランザクション実行できるようにしました。
そのソースはこちら https://github.com/quek/rucksack です。
Hunchentoot と Rucksack の連携は次のように行います。
Hunchentoot のリダイレクによる throw と Rucksack のトランザクション制御を
両立するためにすこしめんどうなことになってまいます。
(defmethod hunchentoot::acceptor-dispatch-request ((self my-acceptor) request)
(let (response
(handler-done t))
(rucksack:with-transaction ()
;; hunchentoot のリダイレクトのハンドリング
(catch 'hunchentoot::handler-done
(setf response (call-next-method))
(setf handler-done nil)))
(if handler-done
(throw 'hunchentoot::handler-done nil)
response)))
というわけで「アクトインディ技術部隊報告書」復活しましたので、
今後ともよろしくお願いいたします。
>View Comments
このページの上へ戻る
- 平成24年1月30日(月) 16時14分22秒
-
区分
- Rails
-
報告者:
- tahara
こんにちは tahara です。
デプロイ時の Unicorn リスタートがときどき失敗して悩んでいました。
幸い本番環境では発生せず、ステージング環境と開発環境で発生していました。
リスタートは unicorn-4.1.1/examples/init.sh の upgrade を使っています。
upgrade は sig USR2 && sleep 2 && sig 0 && oldsig QUIT とい一連の流れになっています。
調べてみると sig USR2 で新しい PID ファイルが作成されるのですが、
sig 0 の時点でまだそれができていなくて失敗していました。
本番環境はサーバの性能が高いので sleep 2 で間に合っていましたが、
ステージング環境等では間に合わなかったんですね。
そこで、次のように sig 0 が成功するまで一定期間リトライするようにしました。
upgrade)
echo -n "sig USR2"
if sig USR2
then
sleep 1
n=$UPGRADE_TIMEOUT
while ! sig 0 && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && ! sig 0
then
echo >&2 "sig SUR2 failed!"
exit 1
fi
echo -n "oldsig QUIT"
if oldsig QUIT
then
n=$TIMEOUT
while test -s $old_pid && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && test -s $old_pid
then
echo >&2 "$old_pid still exists after $TIMEOUT seconds"
exit 1
fi
echo "ok"
exit 0
fi
fi
echo
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
$CMD
;;
これでうまくリスタートできるようになりました。
ついでに、何かの拍子に古い方のプロセスに QUIT を送れなかった時の対策として、
Unicorn の設定ファイルで QUIT を送るようにしました。
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
# oldsig QUIT
old_pid = "#{server.config[: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
end
end
end
これなら init.sh の方では sig USR2 だけで、後は Unicorn に古いプロセスの QUIT を
まかせればいいかと思いましたが、
そうすると新しいプロセスのワーカが動き出すまで古いワーカが処理を行ってしまい、
DB のマイグレーションを行った時なんかは悲しいことになりそうです。
上記のように init.sh で古いプロセスを QUIT する場合は、
新しいプロセスのワーカが動き出すまでリクエストは待たされるので、
まだこちらの方がいいんじゃないかしらん、というところです。
>View Comments
このページの上へ戻る
- 平成24年1月24日(火) 23時01分53秒
-
区分
- Rails
-
報告者:
- tahara
こんにちは、tahara です。
突然ですが、弊社では現在エンジニアを募集しています。
仕事内容は情シス業務と自社サービの開発です。
開発は主に Ralis で PHP もときどきあります。
たまぁに Common Lisp もあます(増やしていきたいです)。
詳細はこちらをご覧ください。
それでは本題です。
いまさらではありますが、弊社で運営している http://iko-yo.net を Rails3 に移行しました。
今回はその移行作業について書いていきたいと思います。
rvm で ruby 1.9.3 をインストールし gemsent を作成する。
bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
rvm install ruby-1.9.3
rvm use ruby-1.9.3
gem install bundler
rvm gemset create iko-yo-rails3
rvm --rvmrc --create 1.9.3@iko-yo-rails3
cd ..
cd -
きれいする
rm -r vendor/rails
rm -r vendor/gems
rm -r vendor/plugins/*
gem を入れる
vi Gemfile
assets は使わない。。。
source 'http://rubygems.org'
gem 'rails', '3.1.0'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'mysql2'
gem 'jquery-rails'
gem 'exception_notification'
gem 'geokit-rails3'
gem 'jpmobile'
gem 'nokogiri'
gem 'paperclip'
gem 'restful-authentication'
gem 'ssl_requirement'
gem 'acts_as_taggable_on_steroids'
gem 'acts_as_commentable'
gem 'will_paginate'
gem 'dynamic_form'
gem 'mecab-ruby', :require => 'MeCab'
gem 'twitter'
gem 'oauth'
gem 'garb'
gem 'gdata_19', :require => 'gdata'
gem 'holiday_jp'
gem 'dalli'
gem 'newrelic_rpm'
#;; config.assets.enabled = false
#;; # Gems used only for assets and not required
#;; # in production environments by default.
#;; group :assets do
#;; gem 'sass-rails', " ~> 3.1.0"
#;; gem 'coffee-rails', "~> 3.1.0"
#;; gem 'uglifier'
#;; end
# Use unicorn as the web server
gem 'unicorn'
# Deploy with Capistrano
gem 'capistrano'
gem 'capistrano-ext'
# To use debugger
# gem 'ruby-debug19', :require => 'ruby-debug'
group :test, :development do
# Pretty printed test output
gem 'turn', :require => false
gem 'spork'
gem 'rspec-rails', "~> 2.6"
gem 'capybara'
gem 'ZenTest'
gem 'autotest-stumpwm'
gem 'remarkable_activerecord', '>=4.0.0.alpha4'
gem 'spork'
end
gem 入れて rails3 にする。
bundle install
bundle exec rails new
ソースの編集
config/rootes.rb はがんばる。
メールまわりも完全に書きなおし。
ソースをちまちま書きかえる(以下はイメージです。実際に動作するものではありません)。
config/boot.rb に次を追加
# /home/ancient/.rvm/rubies/ruby-1.9.2-p290/lib/ruby/1.9.1/psych.rb:148:in `parse': couldn't parse YAML at line 18 column 13 (Psych::SyntaxError)
require 'yaml'
YAML::ENGINE.yamler= 'syck'
各ファイルの1行目に次を追加
# -*- coding: utf-8 -*-
helper 系メソッドに .html_safe を付加
あは以下のようなイメージでどんどん書きかえていく。
s/adapter: mysql/adapter: mysql2/ database.yml.release
s/named_scope/scope/
s/RAILS_ROOT/Rails.root/
s/returning/tap/
s/request_uri/fullpath/
s/<% form/<%= form/
s/<%= f.fields_for/<%= f.fields_for/
layout が使われなかったのは ApplicationController#initialize で super を呼んでなかったからだった。
s/(.*).merge_conditions (.*)/where(\1).where(\2)/
s/mobile_filter :hankaku => true/hankaku_filter :input => true/
s/include ActionController::UrlWriter/include Rails.application.routes.url_helpers/
s/.class_name/.name/
s/link_to_remote .*/link_to \1, :remote => true/
:complete, :before 等は js で bind('ajax:complete', ...), bind('ajax:before', ...) にする。
http://www.alfajango.com/blog/rails-3-remote-links-and-forms/
error_messages がなくなったので gem 'dynamic_form'
s/choice/sample/
s/observe_field/ふつうの手書き jQuery/
s/action mailer/スーパークラスは Jpmobile::Mailer::Base で書きなおす/
/self.include_root_in_json = false/d config/initializers/wrap_parameters.rb
s/model.save(false)/model.save(:validate => false)/
s/errors.or\((.*)\)/errors[\1]/
他にもいっぱいあったような気もしますが、だいたいこんな感じです。
次に実行環境まわり。
実行環境
Apache と Passenger だったのを nginx と unicorn にしました。
unicorn についているサンプルをもとに設定しました。
まずは ngix
nginx.conf
user deployer;
worker_processes 2;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
client_max_body_size 50m;
client_header_buffer_size 4k;
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 15;
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
#open_file_cache max=2000 inactive=300s;
#open_file_cache_valid 360s;
#open_file_cache_min_uses 2;
#open_file_cache_errors off;
include /var/www/outing/current/config/unicorn/production/nginx-site.conf;
}
nginx-site.conf
upstream outing {
# for UNIX domain socket setups:
#server unix:/tmp/.outing.sock fail_timeout=0;
# for TCP setups, point these to your backend servers
server 127.0.0.1:8080 fail_timeout=0;
}
server {
listen 80;
root /var/www/outing/current/public;
server_name iko-yo.net;
location / {
#auth_basic "Restricted";
#auth_basic_user_file /etc/nginx/outing-password;
if ($request_uri ~* "\.(jpg|jpeg|gif|css|png|js|ico)\?[0-9]+$") {
expires max;
access_log off;
break;
}
if (-f $request_filename) {
expires 24h;
access_log off;
break;
}
try_files $uri @app;
}
location @app {
# an HTTP header important enough to have its own Wikipedia entry:
# http://en.wikipedia.org/wiki/X-Forwarded-For
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# enable this if and only if you use HTTPS, this helps Rack
# set the proper protocol for doing redirects:
# proxy_set_header X-Forwarded-Proto https;
# pass the Host: header from the client right along so redirects
# can be set properly within the Rack application
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
# set "proxy_buffering off" *only* for Rainbows! when doing
# Comet/long-poll/streaming. It's also safe to set if you're using
# only serving fast clients with Unicorn + nginx, but not slow
# clients. You normally want nginx to buffer responses to slow
# clients, even with Rails 3.1 streaming because otherwise a slow
# client can become a bottleneck of Unicorn.
#
# The Rack application may also set "X-Accel-Buffering (yes|no)"
# in the response headers do disable/enable buffering on a
# per-response basis.
# proxy_buffering off;
proxy_pass http://outing;
}
}
# HTTPS server
server {
listen 443;
root /var/www/outing/current/public;
server_name iko-yo.net;
ssl on;
ssl_certificate /etc/ssl/iko-yo.net/iko-yo.net.crt.cer;
ssl_certificate_key /etc/ssl/iko-yo.net/iko-yo.net.key;
ssl_session_timeout 5m;
ssl_protocols SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
ssl_prefer_server_ciphers on;
try_files $uri @app;
location @app {
# an HTTP header important enough to have its own Wikipedia entry:
# http://en.wikipedia.org/wiki/X-Forwarded-For
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# enable this if and only if you use HTTPS, this helps Rack
# set the proper protocol for doing redirects:
proxy_set_header X-Forwarded-Proto https;
# pass the Host: header from the client right along so redirects
# can be set properly within the Rack application
proxy_set_header Host $http_host;
# we don't want nginx trying to do something clever with
# redirects, we set the Host: header above already.
proxy_redirect off;
# set "proxy_buffering off" *only* for Rainbows! when doing
# Comet/long-poll/streaming. It's also safe to set if you're using
# only serving fast clients with Unicorn + nginx, but not slow
# clients. You normally want nginx to buffer responses to slow
# clients, even with Rails 3.1 streaming because otherwise a slow
# client can become a bottleneck of Unicorn.
#
# The Rack application may also set "X-Accel-Buffering (yes|no)"
# in the response headers do disable/enable buffering on a
# per-response basis.
# proxy_buffering off;
proxy_pass http://outing;
}
}
# for munin
server {
listen 127.0.0.1;
server_name localhost;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
# redirect sub domain
server {
listen 80;
server_name *.iko-yo.net;
rewrite ^(.*) http://iko-yo.net$1 permanent;
}
次に unicorn
次の unicorn-init.sh を /etc/init.d/outing へ ln -s します。
#!/bin/sh
### BEGIN INIT INFO
# Provides: outing
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Example initscript
# Description: This file should be used to construct scripts to be
# placed in /etc/init.d.
### END INIT INFO
# sudo ln -s /var/www/outing/current/config/unicorn/production/unicorn-init.sh /etc/init.d/outing
# sudo update-rc.d outing default
# sudo update-rc.d outing enable
set -e
# Example init script, this can be used with nginx, too,
# since nginx and unicorn accept the same signals
# Feel free to change any of the following variables for your app:
. "/usr/local/rvm/environments/ruby-1.9.3-p0@iko-yo-rails3"
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/var/www/outing/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="unicorn_rails -E production -D -c $APP_ROOT/config/unicorn/production/unicorn.conf.rb"
INIT_CONF=$APP_ROOT/config/init.conf
action="$1"
set -u
test -f "$INIT_CONF" && . $INIT_CONF
old_pid="$PID.oldbin"
cd $APP_ROOT || exit 1
sig () {
test -s "$PID" && kill -$1 `cat $PID`
}
oldsig () {
test -s $old_pid && kill -$1 `cat $old_pid`
}
case $action in
start)
sig 0 && echo >&2 "Already running" && exit 0
$CMD
;;
stop)
sig QUIT && exit 0
echo >&2 "Not running"
;;
force-stop)
sig TERM && exit 0
echo >&2 "Not running"
;;
restart|reload)
sig HUP && echo reloaded OK && exit 0
echo >&2 "Couldn't reload, starting '$CMD' instead"
$CMD
;;
upgrade)
if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
then
n=$TIMEOUT
while test -s $old_pid && test $n -ge 0
do
printf '.' && sleep 1 && n=$(( $n - 1 ))
done
echo
if test $n -lt 0 && test -s $old_pid
then
echo >&2 "$old_pid still exists after $TIMEOUT seconds"
exit 1
fi
exit 0
fi
echo >&2 "Couldn't upgrade, starting '$CMD' instead"
$CMD
;;
reopen-logs)
sig USR1
;;
*)
echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
exit 1
;;
esac
unicorn.conf.rb
# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
worker_processes 4
# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
user "deployer", "deployer"
# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
working_directory "/var/www/outing/current" # available in 0.94.0+
# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
#listen "/tmp/.outing.sock", :backlog => 64
listen 8080, :tcp_nopush => true
# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 60
# feel free to point this anywhere accessible on the filesystem
pid "/var/www/outing/current/tmp/pids/unicorn.pid"
# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path "/var/www/outing/current/log/unicorn.stderr.log"
stdout_path "/var/www/outing/current/log/unicorn.stdout.log"
# combine REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
GC.copy_on_write_friendly = true
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
# The following is only recommended for memory/DB-constrained
# installations. It is not needed if your system can house
# twice as many worker_processes as you have configured.
#
# # This allows a new master process to incrementally
# # phase out the old master process with SIGTTOU to avoid a
# # thundering herd (especially in the "preload_app false" case)
# # when doing a transparent upgrade. The last worker spawned
# # will then kill off the old master process with a SIGQUIT.
# old_pid = "#{server.config[:pid]}.oldbin"
# if old_pid != server.pid
# begin
# sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
# Process.kill(sig, File.read(old_pid).to_i)
# rescue Errno::ENOENT, Errno::ESRCH
# end
# end
#
# Throttle the master from forking too quickly by sleeping. Due
# to the implementation of standard Unix signal handlers, this
# helps (but does not completely) prevent identical, repeated signals
# from being lost when the receiving process is busy.
# sleep 1
end
after_fork do |server, worker|
# per-process listener ports for debugging/admin/migrations
# addr = "127.0.0.1:#{9293 + worker.nr}"
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
# the following is *required* for Rails + "preload_app true",
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,
# and Redis. TokyoCabinet file handles are safe to reuse
# between any number of forked children (assuming your kernel
# correctly implements pread()/pwrite() system calls)
end
問題は unicorn の upgrade (sudo service outing upgrade) で失敗する場合があること。
なぜでしょう。。。
あと unicorn は production 環境ではメモリ使用量が少ないのですが、
development 環境ではどんどんメモリをくっていきます。
これもなぜでしょう。。。
>View Comments
このページの上へ戻る
- 平成22年11月11日(木) 14時46分55秒
-
区分
- Emacs
-
報告者:
- chiba
こんにちは、Chibaです!
ネタ切れなので、ちょっとした自前便利Emacs lispの紹介です!
browse-url-at-pointとは、ポイント位置にURLの文字があれば、それをブラウザで開くというものです。
このbrowse-url-at-pointで使われている、thing-at-pointという関数が肝なのですが、この関数は、ポイント(カーソル)がある場所のオブジェクトを取得できるというEmacsの関数です。
この関数なのですが使い方次第では非常に便利です。
browse-url-at-pointでは、URLが決め打ちですが、thing-at-point(ポイント位置のオブジェクトを取得)とbrowse-url(ブラウザで開く)を組み合せることによって似たようなものを簡単に作成することができます。
例えば、ポイント位置のRedmineのチケット(#1234というような形式)を開きたい場合は、色々手抜きですが、
(defun show-ticket-at-point ()
(interactive)
(browse-url (format "https://example.com/issues/show/%s"
(thing-at-point 'word))))
のように書けると思います。
簡単な割には便利ですので、エディタ上の情報からブラウザで何か開きたいと思った時には、工夫して色々作成してみてはいかがでしょう。
■
>View Comments
このページの上へ戻る