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
36 lines
1.1 KiB
Markdown
36 lines
1.1 KiB
Markdown
# rails-0001: Preloader::Batch — O(N×F) future_tables Array#include? per loader per batch round
|
||
|
||
**Severity:** HIGH
|
||
**File:** activerecord/lib/active_record/associations/preloader/batch.rb
|
||
**Line:** 24
|
||
**Status:** PATCHED
|
||
|
||
## Description
|
||
|
||
`Preloader::Batch#call` builds `future_tables` as an Array (via `.map(&:table_name).uniq`)
|
||
and then calls `future_tables.include?(l.table_name)` inside `loaders.reject { ... }`.
|
||
This is O(L×F) per batch round where L = loader count and F = future_table count.
|
||
With D-depth association trees the outer `until branches.empty?` loop runs D times,
|
||
making the total O(D×L×F).
|
||
|
||
## Root Cause
|
||
|
||
`Array#uniq` returns an Array, not a Set. The subsequent `.include?` call is O(F) per
|
||
element. In eager-loading a deeply nested association tree with many tables, both L and F
|
||
grow with the model graph, producing quadratic work per query.
|
||
|
||
## Fix
|
||
|
||
Change `.uniq` to `.to_set` so that `future_tables.include?` is O(1).
|
||
|
||
```ruby
|
||
# BEFORE
|
||
end.map(&:table_name).uniq
|
||
|
||
# AFTER
|
||
end.map(&:table_name).to_set
|
||
```
|
||
|
||
## Speedup
|
||
|
||
~150x at L=500 loaders, F=300 future tables, D=20 rounds
|