ここのところqiitaの方ばっかり書いていて放置していたけど、たまにはこちらへ。

.tmux.confにnew-window -n pry pryと書いて自動的にpryのウィンドウを開いてたが、brew upgradeとかやってるうちに正常に起動しなくなった。
エラーメッセージが一瞬で消えてしまうので分かりにくいが、tmux new-window “pry;read v”とやればじっくり見ることができる。
それによるとどうやらrvmで入れたpryがインタプリタとして/usr/bin/rubyを起動しようとしてSEGVしているらしい。
Twitterで呟いて悩んでいたらサジェストを貰った。

おお?PATHの先頭に/usr/binが入っているので、rvmのrubyではなくシステムのrubyが呼び出されているようだ。

tmux内のプロンプトでecho $PATHすると正常なんだけど、これは

ということらしい。なるほど!
pryが起動していたのは[[ -s “$HOME/.rvm/scripts/rvm” ]] && source “$HOME/.rvm/scripts/rvm”が.zloginに入っていたため・・・なのかな?
とりあえず.zshenvに

を書き、.zshrcや.zloginからrvmの設定を削除したところ、正常に起動するようになった。

また、これらのファイルのどれが読み込まれるか、またその順番についてはこちらの記事が参考になった。

MySQLのJOINが遅いというのはよく言われるが、複数回SQLを発行するのとJOINするのがどちらがましなのか疑問だったので、実際どのくらい遅いのか試してみた。

Rails3.2のプロジェクトを作成し、適当なモデルを作ってconsoleでSQLの実行時間を見る。
サンプルコードはgithubに置いた
モデルはUser -< Item -< Extraで、Userは100、Itemは10ずつで1000、Extraは10ずつで合計10000のレコードを作成することにする。
seed_fuのフィクスチャを使い、rake db:seed_fuで一気にレコードが作成されるようにした。

この状態(commit:830f6dae26189d4ea83b8753471a166712d50568)で全てのエントリを取得させてみた。

User全件(100件)

> User.all.count
# SELECT `users`.* FROM `users`
=> 1.0ms

Item全件(100*10件)
1.JOINした場合

> User.joins(:items).all.count
# SELECT `users`.* FROM `users` INNER JOIN `items` ON `items`.`user_id` = `users`.`id`
=> 3.2ms

2.JOINせず複数回SQLした場合

> Item.where(:user_id => User.where(:id => {:not => nil}).pluck(:id)).all.count
# SELECT id FROM `users` WHERE (`users`.`id` IS NOT NULL)
# SELECT `items`.* FROM `items` WHERE `items`.`user_id` IN (1..100)
=> 0.6ms + 4.5ms = 5.1ms

Extra全件(100*10*10件)
1.JOINした場合

> User.joins(:items => :extras).all.count
# SELECT `users`.* FROM `users` INNER JOIN `items` ON `items`.`user_id` = `users`.`id` INNER JOIN `extras` ON `extras`.`item_id` = `items`.`id`
=> 35.4ms

2.JOINせず複数回SQLした場合

> Extra.where(:item_id => (Item.where(:user_id => User.where(:id => {:not => nil}).pluck(:id)).pluck(:id))).all.count
# SELECT id FROM `users` WHERE (`users`.`id` IS NOT NULL)
# SELECT id FROM `items` WHERE `items`.`user_id` IN (1..100)
# SELECT `extras`.* FROM `extras` WHERE `extras`.`item_id` IN (1..1000)
=> 0.6ms + 1.8ms + 30.9ms = 33.3ms

このケースだとあまり差が出ないので、Extraの件数を10倍にしてみた。(commit:96ee43e9319a363666033ac725950283bb80fab8

Extra全件(100*10*100件)
1.JOINした場合

> User.joins(:items => :extras).all.count
# SELECT `users`.* FROM `users` INNER JOIN `items` ON `items`.`user_id` = `users`.`id` INNER JOIN `extras` ON `extras`.`item_id` = `items`.`id`
=> 358.9ms

2.JOINせず複数回SQLした場合

> Extra.where(:item_id => (Item.where(:user_id => User.where(:id => {:not => nil}).pluck(:id)).pluck(:id))).all.count
# SELECT id FROM `users` WHERE (`users`.`id` IS NOT NULL)
# SELECT id FROM `items` WHERE `items`.`user_id` IN (1..100)
# SELECT `extras`.* FROM `extras` WHERE `extras`.`item_id` IN (1..1000)
=> 0.7ms + 1.9ms + 303.0ms = 305.6ms

件数が増えるほど差が広がっている。件数が多い時はJOINしない方が良さそうではある。
まぁ、300msもかかるようだと他の高速化手段を取るべきだろうけど。

ついでにindexを追加してみた。(commit:bac53fb2569b5a681a6bacad3e8c13535e4aa141

Item全件(100*10件)
1.JOINした場合

> User.joins(:items).all.count
# SELECT `users`.* FROM `users` INNER JOIN `items` ON `items`.`user_id` = `users`.`id`
=> 4.0ms

2.JOINせず複数回SQLした場合

> Item.where(:user_id => User.where(:id => {:not => nil}).pluck(:id)).all.count
# SELECT id FROM `users` WHERE (`users`.`id` IS NOT NULL)
# SELECT `items`.* FROM `items` WHERE `items`.`user_id` IN (1..100)
=> 0.7ms + 4.7ms = 5.4ms

Extra全件(100*10*100件)
1.JOINした場合

> User.joins(:items => :extras).all.count
# SELECT `users`.* FROM `users` INNER JOIN `items` ON `items`.`user_id` = `users`.`id` INNER JOIN `extras` ON `extras`.`item_id` = `items`.`id`
=> 212.1ms

2.JOINせず複数回SQLした場合

> Extra.where(:item_id => (Item.where(:user_id => User.where(:id => {:not => nil}).pluck(:id)).pluck(:id))).all.count
# SELECT id FROM `users` WHERE (`users`.`id` IS NOT NULL)
# SELECT id FROM `items` WHERE `items`.`user_id` IN (1..100)
# SELECT `extras`.* FROM `extras` WHERE `extras`.`item_id` IN (1..1000)
=> 0.6ms + 3.3ms + 556.9ms = 560.8ms

む、逆転した。

以上はローカル環境で適当にやっただけなので、そのうちAWSのインスタンスでも立ててちゃんと調べよう。

追記:分かりやすいようにSQL入れました。

Rails3.1+capistranoの続き的なもの。
しばらくローカルでassets:precompileする運用をやっていたが、色々問題があった。具体的には以下のような感じ。

  • デザインの細かな修正ごとにprecompileするので、コミットがassets:precompileだらけになる。
  • precompileした状態でdevelopment環境をロードすると、coffeescriptで書いた関数が2回実行されたりおかしなことになる。
  • Rails3.2だとprecompile時にデフォルトでproductionデータベースに接続しようとするので、そのままのdatabase.ymlのままローカルで実行するとエラーになる。(これは一応RAILS_ENV=development rake assets:precompileで解決可能)

More »

AASMをアップデートしたら、テストで例外が出るようになった。
正しくない状態遷移を行うと例外を返すようになったらしい。
AASMの初期化オプションに:whiny_transitions => falseを与えてやればいい。
以下は例。

aasm :column => :aasm_status, :whiny_transitions => false do
  state :prepared, :initial => true
  state :queued
 state :accepted
...
  event :accept do
    transitions :to => :accepted, :from => [:queued]
  end
...
end

この例だと、whiny_transitionsがtrueならpreparedから直接accept!とかすると例外を投げる。
以前は単に遷移せずpreparedのままだった。

まぁ、例外を投げるのが正しい動作だとは思う。作者もそう言ってるみたい
あくまで既存のコードをいじりたくない場合ってことで。

default_scopeを外そうとしてunscopedしてからkaminariのpageオブジェクトにすると奇妙なことになる。

class Item < ActiveRecord::Base
  default_scope where(:closed => nil)
  paginates_per 20
end
> Item.scoped.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`closed` IS NULL"
> Item.scoped.page.to_sql
# => "SELECT `items`.* FROM `items` WHERE `items`.`closed` IS NULL LIMIT 20 OFFSET 0"
> Item.unscoped.to_sql
# => "SELECT `items`.* FROM `items`"
# ここまでは想定通り
> Item.unscoped.page.to_sql
# => "SELECT  `items`.* FROM `items` WHERE `items`.`closed` IS NULL LIMIT 20 OFFSET 0"
# えええなんで?

Rails3.0.10で確認。3.1.3だと直っているので、3.0系のみの問題かな。

回避方法探してstackoverflow漁ったらamatsudaさんが回答してた

> Item.unscoped{ Item.page.to_sql }
# => "SELECT  `items`.* FROM `items` LIMIT 20 OFFSET 0"

3.0系でdefault_scopeは注意した方がいいよねってことで。

faviconを設定してないとログに404がずらずら並んで鬱陶しいですね。

そんな時はfaviconを適当に作って、headerに
<%= favicon_link_tag 'favicon.ico' %>
とか書いておけばOK。
rails3.1ならファイルはapp/assets/imagesに置く。

なお、faviconのために毎回インスタンス食われるのはもったいないので、前の記事を参考にnginxで静的ファイルを返すようにしておくのが良いかと。