ActiveRecordのTimeZoneConversionが遅い件 (Rails小ネタ)
ActiveRecordでレコードを取得する場合、以下の様なコードを書いたとする。
Article.where(cond).map{|record| #condは検索条件
record.attributes.each{|name,value|
#hoge
}
}
この処理はレコード数が多い場合に非常に遅い。その理由は、record.attributesを実行した時に、ActiveRecord::TimeZoneConversion::Type#type_castが実行されるからである。
type_castの中の、ActiveRecord::ConnectionAdapters::Column#string_to_timeが非常に遅い。というより、Rubyの文字列をTimeに変換する関数はだいたい遅い。
では、どうすればよいか。Timeクラスに変換させなければよいのである。
そのためには、3つの方法がある。
①何も変換させない。(それでいいのか...)
activerecord-raw-dataを活用する。
http://subtech.g.hatena.ne.jp/secondlife/20120328/1332892079
②updated_atとcreated_atを取得しない。(他にTimeZoneのカラムがある場合は状況に応じて)
上記のコードをこのように変換する。
Article.where(cond).map{|record|
record.attribute_names.each{|name| #nameだけを取得
next if %w[updated_at created_at].include? name #TimeZoneのカラムをスキップ
value = record[name]
#hoge
}
}
③attributeをギリギリまで呼び出さない。
record.updated_atやrecord["updated_at"]などでアクセスされた時に始めて、TimeZoneConversionは起こるので、実際に利用するまではそこにアクセスしない。
records = Article.where(cond).to_a #まだTimeZoneConversionは動かない
・余談
めちゃくちゃ適当だが、postgresの場合は下のようにしてやればstring_to_valueが少しは早くなる。
ActiveRecord::ConnectionAdapters::Column.class_eval{
def self.string_to_time value
Time.local(*(value.split(/[ :\-\.]/,-1).map{|x| x.to_i}))
end
}