備忘録的な何か

技術ブログ的な何かです

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

}