java-topology/docs/tickets/rails-0001-preloader-batch-future-tables.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

36 lines
1.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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