ActiveRecord::Base storeの速度検証

Rails 3.2.0から追加された機能

3.2.0リリースから1年近くたっていて、
今更な感じですが、最近使ってパフォーマンス的に痛い思いをしたので、
ちゃんと計測してみました。


機能/用途の説明は、こちらにおまかせ。
http://d.hatena.ne.jp/hichiriki/20120229


さっそく計測。(rails 3.2.11)


用意したテーブル


$ rails g model store3 # storeで3カラム (実際には textカラム1つ)
$ rails g model store10 # storeで10カラム (実際には textカラム1つ)
$ rails g model store30 # storeで30カラム (実際には textカラム1つ)

$ rails g model serialize3 # serializeで3カラム (実際には textカラム1つ)
$ rails g model serialize10 # serializeで10カラム (実際には textカラム1つ)
$ rails g model serialize30 # serializeで30カラム (実際には textカラム1つ)

$ rails g model normal3 # 実際に3カラム
$ rails g model normal10 # 実際に10カラム
$ rails g model normal30 # 実際に30カラム

各カラム数での、カラム名


3カラム: num1, str1, time1
10カラム: num1, num2, num3, num4, str1, str2, str3, str4, time1, time2
30カラム: num1-15, str1-10, time1-5

insert

require 'benchmark'

now = Time.now

three_columns = {num1: 1, str1: 'a', time1: now}
ten_columns = {num1: 1, num2: 2, num3: 3, num4: 4, str1: 'a', str2: 'b', str3: 'c', str4: 'd', time1: now, time2: now}
thirty_columns = {num1: 1, num2: 2, num3: 3, num4: 4, num5: 5, num6: 6, num7: 7, num8: 8, num9: 9,
  num10: 10, num11: 11, num12: 12, num13: 13, num14: 14, num15: 15,
  str1: 'a', str2: 'b', str3: 'c', str4: 'd', str5: 'e', str6: 'f', str7: 'g', str8: 'h', str9: 'i', str10: 'j',
  time1: now, time2: now, time3: now, time4: now, time5: now}

n = 1000
Benchmark.bm do |x|
  x.report('store3') { n.times{Store3.create!(three_columns)} }
  x.report('store10') { n.times{Store10.create!(ten_columns)} }
  x.report('store30') { n.times{Store30.create!(thirty_columns)} }

  x.report('serialize3') { n.times{Serialize3.create!(text: three_columns)} }
  x.report('serialize10') { n.times{Serialize10.create!(text: ten_columns)} }
  x.report('serialize30') { n.times{Serialize30.create!(text: thirty_columns)} }

  x.report('normal3') { n.times{Normal3.create!(three_columns)} }
  x.report('normal10') { n.times{Normal10.create!(ten_columns)} }
  x.report('normal30') { n.times{Normal30.create!(thirty_columns)} }
end


$ rails runner script/benchmark.rb
user system total real
store3 2.150000 0.160000 2.310000 ( 3.218724)
store10 2.600000 0.130000 2.730000 ( 3.445697)
store30 4.980000 0.170000 5.150000 ( 6.126592)
serialize3 1.770000 0.120000 1.890000 ( 2.484414)
serialize10 2.390000 0.130000 2.520000 ( 3.181018)
serialize30 4.060000 0.150000 4.210000 ( 5.224046)
normal3 1.330000 0.120000 1.450000 ( 1.945207)
normal10 1.800000 0.120000 1.920000 ( 2.531964)
normal30 3.240000 0.150000 3.390000 ( 4.348845)

たった1000回でもそれなりに差が出る。

select

require 'benchmark'

now = Time.now

three_columns = {num1: 1, str1: 'a', time1: now}
ten_columns = {num1: 1, num2: 2, num3: 3, num4: 4, str1: 'a', str2: 'b', str3: 'c', str4: 'd', time1: now, time2: now}
thirty_columns = {num1: 1, num2: 2, num3: 3, num4: 4, num5: 5, num6: 6, num7: 7, num8: 8, num9: 9,
  num10: 10, num11: 11, num12: 12, num13: 13, num14: 14, num15: 15,
  str1: 'a', str2: 'b', str3: 'c', str4: 'd', str5: 'e', str6: 'f', str7: 'g', str8: 'h', str9: 'i', str10: 'j',
  time1: now, time2: now, time3: now, time4: now, time5: now}

n = 1000
Benchmark.bm do |x|
  x.report('store3') { Store3.limit(n).all.map(&:num1) }
  x.report('store10') { Store10.limit(n).all.map(&:num1) }
  x.report('store30') { Store30.limit(n).all.map(&:num1) }

  x.report('serialize3') { Serialize3.limit(n).all.map{|s|s.text[:num1]} }
  x.report('serialize10') { Serialize10.limit(n).all.map{|s|s.text[:num1]} }
  x.report('serialize30') { Serialize30.limit(n).all.map{|s|s.text[:num1]} }

  x.report('normal3') { Normal3.limit(n).all.map(&:num1) }
  x.report('normal10') { Normal10.limit(n).all.map(&:num1) }
  x.report('normal30') { Normal30.limit(n).all.map(&:num1) }
end

.allでやめると、ロードされてなさそうだったので、カラムに触るという意味でmapしてます。


$ rails runner script/benchmark.rb
user system total real
store3 0.650000 0.040000 0.690000 ( 0.725642)
store10 0.630000 0.010000 0.640000 ( 0.652668)
store30 1.670000 0.010000 1.680000 ( 1.684106)
serialize3 0.330000 0.020000 0.350000 ( 0.385289)
serialize10 0.650000 0.010000 0.660000 ( 0.668932)
serialize30 1.690000 0.010000 1.700000 ( 1.716288)
normal3 0.090000 0.000000 0.090000 ( 0.093420)
normal10 0.130000 0.000000 0.130000 ( 0.132085)
normal30 0.200000 0.010000 0.210000 ( 0.216454)

オブジェクトにするところがとても重い。

容量

show table statusのData_length (1000件)


store3s: 131,072
store10s: 245,760
store30s: 1,589,248

serialize3s: 131,072
serialize10s: 245,760
serialize30s: 1,589,248

normal3s: 65,536
normal10s: 98,304
normal30s: 196,608

storeとserializeがまったく同じ。どっちもyamlかな?
当然ながらstoreとserializeは、とても大きい。

結論

基本的に遅いし容量も大きくなるので、使う場所はよく考えた方がいいかも