Rails ActiveRecordでランダムにレコードを1件取得する

スポンサーリンク
【お知らせ】 英単語を画像イメージで楽に暗記できる辞書サイトを作りました。英語学習中の方は、ぜひご利用ください!
画像付き英語辞書 Imagict | 英単語をイメージで暗記
【開発記録】
英単語を画像イメージで暗記できる英語辞書サービスを作って公開しました

Rails の ActiveRecord で、MySQL データベースのテーブルから、ランダムにレコードを1件取得するコードを4つの方法で試しました。Rails は便利とはいえ、ActiveRecord が絡む部分でまずい書き方をすると、劇的に動作が遅くなってしまう場合もある。なので、自信がない時は、ちょっと手間がかかりますけどベンチマークを取るようにしてます。

※ 2015/03/18 1つ目のやり方に欠点がありましたので、追記しました。参考にされる場合は、追記部分も読まれるようお願いいたします。

スポンサーリンク

— 環境 —
Rails 4.1
Ruby 2.1
Macbook Air OS X

4種類の書き方を用意

以下4つの方法を書いて試しました。後半で Benchmark 計測。

1つ目

この書き方は、最初に思いついた方法で、おそらく結構速いだろうと見当がついた。以前に、ActiveRecord モデルのインスタンスの前後レコードを取得するコードを書いたことがあり、それに似ていましたので。

Rails ActiveRecordで前後のレコードを取得する | EasyRamble

レコード削除等で id が抜けている可能性があるので、等号による比較は使わないほうが良い。あと、レコード削除等で id が抜けている場合、レコード総数の数値よりレコード最終行の id の数値が大きい可能性があるために、rand の引数には先頭レコード id と最終レコード id の range を渡しています。

単純に SomeModel.count を使う書き方…

この書き方でも良いけど、id の抜け落ちがあった場合、rand で返される数値に、最後のほうのレコードの id が含まれない。SomeModel.count < SomeModel.last.id になるため。

【追記 2015/03/18】
はてなブックマークでコメントを頂きました。すいません、この1つ目のやり方は欠点がありました。

milk1000cc
1つ目のやり方、id の抜け具合によって出現頻度にばらつきが出るような気が..?

たしかに、id の抜けがあると、特定のレコードの出現頻度が高くなる場合があります。たとえば、レコードの id が “1, 2, 3, 4, 9, 10” となっていたら、5 ~ 8 が抜けていることにより 9 のレコードが出やすくなってしまうはず。なので、このやり方はランダムさの精度をさほど要求されない場合や、id の抜けがない場合にのみ使うほうが良いかと思います。milk1000cc さん、コメントありがとうございました。

それからもう一つ、pluck を使う方法をはてなブックマークでコメント頂きました。

a2ikm
pluck(:id).sampleで得たIDでfindしてた。

pluck を使うやり方もあるのですね。この方法は思いつかなかったので、後半で行ったベンチマークと同条件で計測してみました。

a2ikm さん、コメントありがとうございました。ランダムの正確さが要求される場合は、この pluck か2つ目の offset を使う方法が良いかと思います。

正確なランダムさで、高速に動作する書き方をご存じの方がおられましたら、ぜひご教示ください。
【追記ここまで】

2つ目

offset を使う方法は、ぐぐって調べた以下のページで知った。なるほどな〜。

ActiveRecord にてランダムなレコードが欲しい時 | yukku++

3つ目

これは間違いなく遅い&メモリを圧迫する。この書き方はNGですけど、どのくらい遅いか比較用に用意しました。

4つ目

MySQL のネイティブ関数 RAND() を使って、ORDER BY RAND() でクエリーを発行する方法。速いかもしれないけど、MySQL 自体に依存する関数はできたら Rails では使いたくない。一応、後ほど行う比較計測のため。

Rails Quick Tips – Random Records – The Hashrocket Blog

とりあえず pry で実験

どんな SQL が発行されるか確認するために、まずは pry で試す。SQL 発行にかかる時間も出力されるので、ある程度の目安にはなります。試したのは、レコード数が約40万件の MySQL データベースのテーブルです。

SQL が3回発行されてしまいますけど、やはり一番上の書き方が圧倒的に速いと、この時点で確信しました。SomeModel.first.id, SomeModel.last.id を取得後に使いまわしたりハードコードすれば、SQL のクエリ発行を減らすことはできます。

SomeModel.all.sample は、SQL 実行後のメモリ上の処理でめっちゃ時間かかります。RAND() を使う方法は意外と速くなさそう。

ベンチマークを計測して比較

Ruby の Benchmark モジュールで計測しました。

結果。

やはり、一番目の書き方が速い結果に。ということで、

を採用。id が使えない場合は、offset を使う方法ですかね。

スポンサーリンク
パーフェクト Ruby on Rails は、最近読んだ Rails 本の中では一番役に立った本です。Chef や Capistrano など Rails と共によく使用される技術にも触れてあります。Ruby on Rails 4 アプリケーションプログラミングは、入門的な内容で Rails の機能全体を網羅されています。
スポンサーリンク
 
Twitterを使っていますのでフォローお願いたします!ブログの更新情報もつぶやいてます^^
(英語学習用)

Leave Your Message!