Skip to content

Commit d79fb96

Browse files
committed
Define adapter type maps statically when possible
Each type map can use a non trivial amount of memory (over 10KiB in our app). Currently each connection build its own type map from scratch, but except for postgresql which has extension types, all connections end up with the same maps. So the more connections you have the more memory it wastes. By defining the type map statically for MySQL and SQLite3 connections we save some memory, share caches, and allow that memory to be handled by Copy on Write for forking setups.
1 parent a2cb0b2 commit d79fb96

File tree

7 files changed

+229
-190
lines changed

7 files changed

+229
-190
lines changed

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 57 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -632,78 +632,76 @@ def database_version # :nodoc:
632632
def check_version # :nodoc:
633633
end
634634

635-
private
636-
def type_map
637-
@type_map ||= Type::TypeMap.new.tap do |mapping|
638-
initialize_type_map(mapping)
635+
class << self
636+
private
637+
def initialize_type_map(m)
638+
register_class_with_limit m, %r(boolean)i, Type::Boolean
639+
register_class_with_limit m, %r(char)i, Type::String
640+
register_class_with_limit m, %r(binary)i, Type::Binary
641+
register_class_with_limit m, %r(text)i, Type::Text
642+
register_class_with_precision m, %r(date)i, Type::Date
643+
register_class_with_precision m, %r(time)i, Type::Time
644+
register_class_with_precision m, %r(datetime)i, Type::DateTime
645+
register_class_with_limit m, %r(float)i, Type::Float
646+
register_class_with_limit m, %r(int)i, Type::Integer
647+
648+
m.alias_type %r(blob)i, "binary"
649+
m.alias_type %r(clob)i, "text"
650+
m.alias_type %r(timestamp)i, "datetime"
651+
m.alias_type %r(numeric)i, "decimal"
652+
m.alias_type %r(number)i, "decimal"
653+
m.alias_type %r(double)i, "float"
654+
655+
m.register_type %r(^json)i, Type::Json.new
656+
657+
m.register_type(%r(decimal)i) do |sql_type|
658+
scale = extract_scale(sql_type)
659+
precision = extract_precision(sql_type)
660+
661+
if scale == 0
662+
# FIXME: Remove this class as well
663+
Type::DecimalWithoutScale.new(precision: precision)
664+
else
665+
Type::Decimal.new(precision: precision, scale: scale)
666+
end
667+
end
639668
end
640-
end
641669

642-
def initialize_type_map(m = type_map)
643-
register_class_with_limit m, %r(boolean)i, Type::Boolean
644-
register_class_with_limit m, %r(char)i, Type::String
645-
register_class_with_limit m, %r(binary)i, Type::Binary
646-
register_class_with_limit m, %r(text)i, Type::Text
647-
register_class_with_precision m, %r(date)i, Type::Date
648-
register_class_with_precision m, %r(time)i, Type::Time
649-
register_class_with_precision m, %r(datetime)i, Type::DateTime
650-
register_class_with_limit m, %r(float)i, Type::Float
651-
register_class_with_limit m, %r(int)i, Type::Integer
652-
653-
m.alias_type %r(blob)i, "binary"
654-
m.alias_type %r(clob)i, "text"
655-
m.alias_type %r(timestamp)i, "datetime"
656-
m.alias_type %r(numeric)i, "decimal"
657-
m.alias_type %r(number)i, "decimal"
658-
m.alias_type %r(double)i, "float"
659-
660-
m.register_type %r(^json)i, Type::Json.new
661-
662-
m.register_type(%r(decimal)i) do |sql_type|
663-
scale = extract_scale(sql_type)
664-
precision = extract_precision(sql_type)
665-
666-
if scale == 0
667-
# FIXME: Remove this class as well
668-
Type::DecimalWithoutScale.new(precision: precision)
669-
else
670-
Type::Decimal.new(precision: precision, scale: scale)
670+
def register_class_with_limit(mapping, key, klass)
671+
mapping.register_type(key) do |*args|
672+
limit = extract_limit(args.last)
673+
klass.new(limit: limit)
671674
end
672675
end
673-
end
674676

675-
def reload_type_map
676-
type_map.clear
677-
initialize_type_map
678-
end
677+
def register_class_with_precision(mapping, key, klass)
678+
mapping.register_type(key) do |*args|
679+
precision = extract_precision(args.last)
680+
klass.new(precision: precision)
681+
end
682+
end
679683

680-
def register_class_with_limit(mapping, key, klass)
681-
mapping.register_type(key) do |*args|
682-
limit = extract_limit(args.last)
683-
klass.new(limit: limit)
684+
def extract_scale(sql_type)
685+
case sql_type
686+
when /\((\d+)\)/ then 0
687+
when /\((\d+)(,(\d+))\)/ then $3.to_i
688+
end
684689
end
685-
end
686690

687-
def register_class_with_precision(mapping, key, klass)
688-
mapping.register_type(key) do |*args|
689-
precision = extract_precision(args.last)
690-
klass.new(precision: precision)
691+
def extract_precision(sql_type)
692+
$1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
691693
end
692-
end
693694

694-
def extract_scale(sql_type)
695-
case sql_type
696-
when /\((\d+)\)/ then 0
697-
when /\((\d+)(,(\d+))\)/ then $3.to_i
695+
def extract_limit(sql_type)
696+
$1.to_i if sql_type =~ /\((.*)\)/
698697
end
699-
end
698+
end
700699

701-
def extract_precision(sql_type)
702-
$1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
703-
end
700+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
704701

705-
def extract_limit(sql_type)
706-
$1.to_i if sql_type =~ /\((.*)\)/
702+
private
703+
def type_map
704+
TYPE_MAP
707705
end
708706

709707
def translate_exception_class(e, sql, binds)

activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,6 @@ def disable_referential_integrity #:nodoc:
185185
end
186186
end
187187

188-
# CONNECTION MANAGEMENT ====================================
189-
190-
def clear_cache! # :nodoc:
191-
reload_type_map
192-
super
193-
end
194-
195188
#--
196189
# DATABASE STATEMENTS ======================================
197190
#++
@@ -568,56 +561,67 @@ def check_version # :nodoc:
568561
end
569562
end
570563

571-
private
572-
def initialize_type_map(m = type_map)
573-
super
564+
class << self
565+
private
566+
def initialize_type_map(m)
567+
super
568+
569+
m.register_type(%r(char)i) do |sql_type|
570+
limit = extract_limit(sql_type)
571+
Type.lookup(:string, adapter: :mysql2, limit: limit)
572+
end
574573

575-
m.register_type(%r(char)i) do |sql_type|
576-
limit = extract_limit(sql_type)
577-
Type.lookup(:string, adapter: :mysql2, limit: limit)
574+
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
575+
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
576+
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
577+
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
578+
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
579+
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
580+
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
581+
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
582+
m.register_type %r(^float)i, Type::Float.new(limit: 24)
583+
m.register_type %r(^double)i, Type::Float.new(limit: 53)
584+
585+
register_integer_type m, %r(^bigint)i, limit: 8
586+
register_integer_type m, %r(^int)i, limit: 4
587+
register_integer_type m, %r(^mediumint)i, limit: 3
588+
register_integer_type m, %r(^smallint)i, limit: 2
589+
register_integer_type m, %r(^tinyint)i, limit: 1
590+
591+
m.alias_type %r(year)i, "integer"
592+
m.alias_type %r(bit)i, "binary"
593+
594+
m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
595+
m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
578596
end
579597

580-
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
581-
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
582-
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
583-
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
584-
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
585-
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
586-
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
587-
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
588-
m.register_type %r(^float)i, Type::Float.new(limit: 24)
589-
m.register_type %r(^double)i, Type::Float.new(limit: 53)
590-
591-
register_integer_type m, %r(^bigint)i, limit: 8
592-
register_integer_type m, %r(^int)i, limit: 4
593-
register_integer_type m, %r(^mediumint)i, limit: 3
594-
register_integer_type m, %r(^smallint)i, limit: 2
595-
register_integer_type m, %r(^tinyint)i, limit: 1
596-
597-
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
598-
m.alias_type %r(year)i, "integer"
599-
m.alias_type %r(bit)i, "binary"
600-
601-
m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
602-
m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
603-
end
598+
def register_integer_type(mapping, key, **options)
599+
mapping.register_type(key) do |sql_type|
600+
if /\bunsigned\b/.match?(sql_type)
601+
Type::UnsignedInteger.new(**options)
602+
else
603+
Type::Integer.new(**options)
604+
end
605+
end
606+
end
604607

605-
def register_integer_type(mapping, key, **options)
606-
mapping.register_type(key) do |sql_type|
607-
if /\bunsigned\b/.match?(sql_type)
608-
Type::UnsignedInteger.new(**options)
608+
def extract_precision(sql_type)
609+
if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
610+
super || 0
609611
else
610-
Type::Integer.new(**options)
612+
super
611613
end
612614
end
613-
end
615+
end
614616

615-
def extract_precision(sql_type)
616-
if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
617-
super || 0
618-
else
619-
super
620-
end
617+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
618+
TYPE_MAP_WITH_BOOLEAN = Type::TypeMap.new(TYPE_MAP).tap do |m|
619+
m.register_type %r(^tinyint\(1\))i, Type::Boolean.new
620+
end
621+
622+
private
623+
def type_map
624+
emulate_booleans ? TYPE_MAP_WITH_BOOLEAN : TYPE_MAP
621625
end
622626

623627
# See https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html

0 commit comments

Comments
 (0)