Skip to content

Fix O(n^2) transitive interface check in Schema.from_definition#5678

Merged
rmosolgo merged 1 commit into
rmosolgo:masterfrom
vahe:fix/transitive-interface-check-perf
Jul 24, 2026
Merged

Fix O(n^2) transitive interface check in Schema.from_definition#5678
rmosolgo merged 1 commit into
rmosolgo:masterfrom
vahe:fix/transitive-interface-check-perf

Conversation

@vahe

@vahe vahe commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

238f193 validates each object/interface type by calling document.definitions.find { ... } for every interface name of every type — a linear scan inside the definitions loop, so Schema.from_definition became O(n²) in the number of definitions. On a large real-world SDL (9MB, ~10,700 types), build time roughly doubled between v2.5.11 and v2.6.6; git bisect points at 238f193 as the sole cause.

Fix

Build a name => definition hash once (lazily, only if some type declares interfaces) and look up in O(1). Two details preserve the exact previous behavior:

  • by_name[d.name] ||= d: first occurrence wins, matching Array#find when a name appears as both a definition and a type-extension node (the Type extensions specs fail with last-wins).
  • Types with no interfaces skip the check entirely.

Same validation, same error message; all build_from_definition specs pass, including the ones added in 238f193.

Benchmark

N types implementing an interface that appears after them in the document (e.g. any alphabetically-ordered schema — if the interface comes first, find returns early and hides the cost):

s = +"type Query { f: Int }\n"
n.times { |i| s << "type T#{i} implements Node { id: ID! f1: String f2: Int }\n" }
s << "interface Node { id: ID! }\n"
GraphQL::Schema.from_definition(s)
types master this PR
2,000 582ms 363ms
4,000 1,316ms 477ms
8,000 4,693ms 1,409ms

The 9MB real-world schema: 1,598ms → 758ms. With this PR the check measures within noise of removing it entirely (700ms vs 708ms).

238f193 added a transitive-interface validation that scans
document.definitions linearly for every interface name of every
object/interface type. On large schemas this dominates build time:
a 9MB SDL (10.7k types) went from ~750ms to ~1.5s.

Build a name=>definition hash once (first occurrence wins, matching
the previous .find semantics for extension nodes) and skip the check
for types with no interfaces.

Schema.from_definition on the 9MB SDL: 1598ms -> 758ms.

Assisted-By: devx/57cd31bb-9c77-4723-8862-7b08599a5d4f
@rmosolgo

Copy link
Copy Markdown
Owner

Awesome, thanks for this improvement!

@rmosolgo
rmosolgo merged commit 07e5e88 into rmosolgo:master Jul 24, 2026
13 checks passed
@rmosolgo rmosolgo added this to the 2.6.7 milestone Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants