PoC: deduplicate structurally identical structs in the provider generator - #390
Draft
jsteinich wants to merge 1 commit into
Draft
PoC: deduplicate structurally identical structs in the provider generator#390jsteinich wants to merge 1 commit into
jsteinich wants to merge 1 commit into
Conversation
Proof of concept, not ready to merge -- opened to show the code behind the proposal. Gated behind CDKTN_STRUCT_DEDUP=1; unset, generated output is byte-identical to today. The generator names a struct after the path that reaches it, so one block shape reachable by many paths is emitted once per path, along with its mapper functions and OutputReference/List classes (~84% of generated bytes). On datadog 4.18, 95.5% of generated interfaces are structurally identical duplicates; the largest equivalence class is 364 byte-identical interfaces. This is an artifact of the schema serialization rather than the provider. `terraform providers schema -json` inlines every call site: DataDog's source reaches a single getComputeSchema() through getApmLogNetworkRumSecurityAuditQuerySchema() (48 call sites) plus four sibling query helpers, and it comes back out as 364 interfaces. detectAttributeLoops already collapses repeats, but only among a struct's own ancestors (true recursion); sibling branches are never compared and each top-level attribute restarts from an empty map. It is working as designed -- its scope simply cannot see this. This pass compares every struct in a resource against every other and repoints duplicates at one canonical struct. Equality is a full recursive signature and deliberately does NOT reuse getAttributeIdentifier: that one-level comparison is safe for the ancestor-only case but resource-wide it merges DashboardV2WidgetCohortDefinition into DashboardV2WidgetGroupDefinitionWidgetSloListDefinition, which would emit incorrect bindings. Measured on datadog 4.18, end to end: generated TS 99.6 MB -> 12.3 MB (-87.7%) interfaces 13,220 -> 1,491 tsc 0 errors, peak 4,674 MB -> 744 MB jsii 0 errors, assembly 22 MB pacmak go rc=0, 8,110 files, 56.5 MB go build ./... 925 MB peak, 11.1 s, exit 0 (GOMAXPROCS=8, cold cache) against ~18 GB for a single package today, which is what OOM-kills package-go. NOT ready to merge. The canonical-name rule here is a placeholder: it is order-independent but still not stable across provider releases (0.2-0.8% of shapes renamed on datadog 4.17 -> 4.18). A signature -> name registry is the intended fix and is not implemented. Merging shapes is also a breaking API change requiring a major bump per provider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposal: #389 — this PR is the proof-of-concept code referenced there. Read #389 first for the full investigation; this PR covers the implementation only.
What this is
The generator names a struct after the path that reaches it, so one block shape reachable by many paths is emitted once per path — along with its mapper functions and
OutputReference/Listclasses, which are 84% of generated bytes.On datadog 4.18, 95.5% of generated interfaces are structurally identical duplicates. The largest equivalence class is 364 byte-identical interfaces.
This is what OOM-kills
package-go: not the Node heap ceiling (tuned three times), but thego buildverification insidejsii-pacmak, which needs ~18 GB for a single generated package on a runner with ~24 GB usable. That memory is not governed byNODE_OPTIONSat all.The duplication is an artifact of schema serialization
terraform providers schema -jsoninlines every call site and discards sharing the provider author wrote by hand:Cross-resource duplication has the same origin:
resource_datadog_powerpack.gocallsgetNonGroupWidgetSchema()defined inresource_datadog_dashboard.go. Dedup recovers structure the provider actually expressed — it is not merging things that merely look alike.detectAttributeLoopsis not brokenIt threads
knownStructsdown each DFS branch, so it merges only when a shape reappears among its own ancestors (true recursion). Sibling branches are never compared and each top-level attribute restarts from{}. The 364-member class is...ApmQueryComputeQueryvs...LogQueryComputeQuery— siblings, invisible to an ancestor-only check by construction. It works as designed; its scope cannot see this.Equality must be a full recursive signature
This pass deliberately does not reuse
getAttributeIdentifier. That comparison uses attribute names plus one level of nesting (with an in-code caveat that it is an approximation). Safe for the ancestor-only case; applied resource-wide it yields 199 classes where an exact recursive hash yields 200 — wrongly mergingDashboardV2WidgetCohortDefinitionwithDashboardV2WidgetGroupDefinitionWidgetSloListDefinition, which share a shallow shape and diverge deeper. That would emit incorrect bindings.Measured — datadog 4.18, end to end
tscjsiijsii-pacmak --target gogo build ./...(GOMAXPROCS=8, cold)925 MB fits a standard 7 GB runner at full parallelism, with the compilation check kept.
Why this is not ready to merge
interface A extends B {}would not help since it retains the per-name classes and functions that are 84% of the bytes. Major bump per provider.Scope: per-resource, not provider-wide (tested)
Provider-wide is never clearly better and is actively bad for
awscc(machine-generated CloudFormation schemas give every resource the same generic block names).Fleet impact — datadog is an outlier
Most providers' duplication is across resources, which per-resource scope does not capture. Six providers (
acme,cfncompat,external,http,null,time) have 0% redundancy — a no-op with no API impact.Caveats
dashboardv2compile figures measured on a Windows host with 32 GB, not Linux CI.🤖 Generated with Claude Code