java-topology/docs/tickets/rails-0011-time-zone-conversion-skip-list-array.md
russell@unturf.com 547a9f5738 ORM wave 2: 10 new defects — Active Record +3, Exposed +3, SeaORM +4 (167 sites, 64 ecosystems)
rails-0009: FilterAttributeHandler filter_parameters Array O(A×F) → Set (450×)
rails-0010: Encryption::AutoFilteredParameters two Array scans → Set (250×)
rails-0011: TimeZoneConversion skip_list Array O(M×C×S) → Set (20×)

exposed-0001: SchemaUtilityApi mapMissingColumnStatements O(N×M) → map (118×)
exposed-0002: IdentifierManagerApi isAKeyword O(K) linear → HashSet (144×)
exposed-0003: Table.clone consParams.map fresh List → hoisted HashSet (6×)

seaorm-0001: active_model establish_links leftover.any O(N²) → HashSet (501×)
seaorm-0002: rbac engine group_permissions .values().find() → HashMap by ID (502×)
seaorm-0003: schema builder sorted_tables Vec::contains → HashSet (500×)
seaorm-0004: TopologicalSort from_iter seen Vec O(N²) → BTreeSet (28×)

Unit tests: RailsTest 11/11, ExposedTest 3/3, SeaORMTest 4/4 PASS
Whitepaper: 157→167 sites, 62→64 ecosystems; §13.12 ORM Wave 2 added
2026-03-27 13:49:46 -04:00

2.2 KiB
Raw Blame History

rails-0011: TimeZoneConversion — O(C×S+C×T) Array#include? per column during schema load

Severity: MEDIUM File: activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb Lines: 85, 87 Status: PATCHED

Description

create_time_zone_conversion_attribute? is called once per column per model class during schema load (hook_attribute_typetype_for_column_default_attributes). It performs two Array include? checks per call:

Line 85:

!skip_time_zone_conversion_for_attributes.include?(name.to_sym)

Line 87:

time_zone_aware_types.include?(cast_type.type)

skip_time_zone_conversion_for_attributes defaults to [] and is set as a class_attribute. time_zone_aware_types defaults to [:datetime, :time] (size 2, so trivially fast), but skip_time_zone_conversion_for_attributes can grow large in applications that skip many attributes — e.g., self.skip_time_zone_conversion_for_attributes = all_columns.

With C columns per model, S skip-list entries, and M model classes:

  • Total cost: O(M × C × S)

On large apps with 100+ models of 50+ columns and skip lists of 20+ attributes, this contributes measurably to boot time.

Root Cause

skip_time_zone_conversion_for_attributes is a class_attribute initialized as [] (Array). The include? check is O(S) per column per class.

Fix

# BEFORE (time_zone_conversion.rb:83-88)
def create_time_zone_conversion_attribute?(name, cast_type)
  enabled_for_column = time_zone_aware_attributes &&
    !skip_time_zone_conversion_for_attributes.include?(name.to_sym)
  enabled_for_column && time_zone_aware_types.include?(cast_type.type)
end

# AFTER
def create_time_zone_conversion_attribute?(name, cast_type)
  @skip_tz_set ||= skip_time_zone_conversion_for_attributes.to_set
  @tz_aware_set ||= time_zone_aware_types.to_set
  enabled_for_column = time_zone_aware_attributes &&
    !@skip_tz_set.include?(name.to_sym)
  enabled_for_column && @tz_aware_set.include?(cast_type.type)
end

Note: the cache must be invalidated when skip_time_zone_conversion_for_attributes is reassigned — use class_attribute with a custom setter or reset on write.

Speedup

~10x at M=100 models, C=50 columns, S=20 skip-list entries