Rails 4.2.6, Ruby 2.3.1, PostgreSQL 9.5.2 の話
1MBくらいの要素を追加したら(!?)、SELECT 後のインスタンス初期化で非常に待たされました。
試しにカラムの型を jsonb
に変更してみたところ、多少はマシになりました。
ActiveRecord のコードを探ってみると、デフォルトでは Ruby で書かれたパーサによって PostgreSQL の Array を Ruby の Array にキャストしている事が分かりました。
lib/active_record/connection_adapters/postgresql/oid/array.rb
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Array < Type::Value # :nodoc:
include Type::Mutable
# Loads pg_array_parser if available. String parsing can be
# performed quicker by a native extension, which will not create
# a large amount of Ruby objects that will need to be garbage
# collected. pg_array_parser has a C and Java extension
begin
require 'pg_array_parser'
include PgArrayParser
rescue LoadError
require 'active_record/connection_adapters/postgresql/array_parser'
include PostgreSQL::ArrayParser
end
ということで、PostgreSQL の Array を使う場合は DockYard/pg_array_parser を使っておくと良さそうです。
(DockYard/pg_array_parser のコードは読んでませんし、2013年でコミットは止まっている様です。)
一方その頃 Rails 5.0.0 は
pg
が持っている機能を使っている様です。
lib/active_record/connection_adapters/postgresql/oid/array.rb
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module OID # :nodoc:
class Array < Type::Value # :nodoc:
include Type::Helpers::Mutable
attr_reader :subtype, :delimiter
delegate :type, :user_input_in_time_zone, :limit, to: :subtype
def initialize(subtype, delimiter = ',')
@subtype = subtype
@delimiter = delimiter
@pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter
@pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter
end