Skip to content

perf: seed component decls so cold loads avoid Θ(n²) tree walks - #34068

Open
ljodea wants to merge 2 commits into
dagster-io:masterfrom
ljodea:perf/seed-component-decl-tree-lookups
Open

perf: seed component decls so cold loads avoid Θ(n²) tree walks#34068
ljodea wants to merge 2 commits into
dagster-io:masterfrom
ljodea:perf/seed-component-decl-tree-lookups

Conversation

@ljodea

@ljodea ljodea commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Cold multi-loc component loads rematerialized the full loc→decl map once per uncached loc, so loading n components did Θ(n²) tree walks.

  • After the first _component_decl_tree() walk, seed component_decl into the state tracker for cacheable locs (ComponentPath and keyed app-managed locs).
  • Leave ComponentRootLoc and the unkeyed app-managed aggregate unseeded so app-managed discovery still rebuilds from a fresh listing.
  • find_decl_at_path checks the state tracker first after a prior walk.

Complexity

Before After
Cold load of n sibling components Θ(n²) loc→decl map materializations O(1) materializations after the first walk; per-loc loads are O(1) cache hits

n = number of component locations in the project (YAML folders/files, Python defs modules, keyed app-managed IDs).

Note: The filesystem root decl was already cached (_get_filesystem_decl). The cost was re-walking the in-memory decl graph and rebuilding the loc→decl dict for every uncached child load—not re-scanning the filesystem each time.

Audit target

python_modules/dagster/dagster/components/core

Test plan

  • New: test_component_decl_tree_caching.py
    • cold build_defs of 12 siblings must not call _component_decl_tree once per sibling
    • first tree walk seeds child component_decl entries
  • Existing component tree / decl / app-managed integration tests pass
  • ruff check on touched files
pytest python_modules/dagster/dagster_tests/components_tests/component_tree_tests/ -q
pytest python_modules/dagster/dagster_tests/components_tests/unit_tests/test_app_managed_components_integration.py -q

Follow-ups

None. No other deferred complexity findings from this audit of components/core.

Cold multi-loc component loads rematerialized the full loc→decl map once
per uncached loc (Θ(n²) walks). Seed cacheable child component_decls after
the first walk so subsequent loads are O(1) cache hits.

Leave ComponentRootLoc and the unkeyed app-managed aggregate unseeded so
app-managed discovery keeps rebuilding from a fresh listing.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Improves cold component loading by seeding cacheable declarations after the first tree walk.

  • Checks the state tracker before materializing the declaration tree.
  • Excludes root and unkeyed app-managed aggregate locations from declaration seeding.
  • Adds regression coverage requiring exactly one tree materialization for a cold sibling load.

Confidence Score: 5/5

The PR appears safe to merge.

The prior regression-test issue is fixed by requiring exactly one declaration-tree materialization, and no blocking failure remains.

Important Files Changed

Filename Overview
python_modules/dagster/dagster/components/core/component_tree.py Adds declaration-cache lookup and seeds cacheable locations during the first full tree materialization while preserving fresh discovery locations.
python_modules/dagster/dagster_tests/components_tests/component_tree_tests/test_component_decl_tree_caching.py Adds cold-load and cache-seeding regression coverage, including the exact single-materialization assertion requested by the prior review.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Cold component load] --> B{Declaration cached?}
  B -->|Yes| C[Return cached declaration]
  B -->|No| D[Materialize declaration tree]
  D --> E[Seed cacheable child declarations]
  E --> F[Load requested component]
  F --> G[Later sibling loads use cache]
Loading

Reviews (2): Last reviewed commit: "test: assert single component-decl tree ..." | Re-trigger Greptile

Comment on lines +59 to +63
assert call_count < n_siblings, (
f"_component_decl_tree was called {call_count} times for "
f"{n_siblings} sibling components (expected O(1) materializations, "
f"not once per component)"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Call-count bound permits regression

The assertion accepts up to 11 tree materializations for 12 siblings, so loading nearly every sibling through a separate tree walk still passes despite the test's O(1) contract. Assert the expected single materialization so a regression toward quadratic cold-load behavior is detected.

Suggested change
assert call_count < n_siblings, (
f"_component_decl_tree was called {call_count} times for "
f"{n_siblings} sibling components (expected O(1) materializations, "
f"not once per component)"
)
assert call_count == 1, (
f"_component_decl_tree was called {call_count} times for "
f"{n_siblings} sibling components (expected one materialization)"
)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid — post-fix cold load materializes the tree once. Tightened to assert call_count == 1 in afe98d2.

Tighten the call-count check so near-quadratic cold loads cannot pass.
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.

1 participant