Skip to content

perf: filter resources before building dependency graph#524

Merged
michael-richey merged 4 commits intomainfrom
michael.richey/perf-filter-dependency-graph
Apr 14, 2026
Merged

perf: filter resources before building dependency graph#524
michael-richey merged 4 commits intomainfrom
michael.richey/perf-filter-dependency-graph

Conversation

@michael-richey
Copy link
Copy Markdown
Collaborator

@michael-richey michael-richey commented Apr 13, 2026

Summary

  • Filter resources in get_dependency_graph() before calling _resource_connections(), skipping deepcopy and connection computation for non-matching resources (e.g., 995 of 1000 dashboards when syncing 5)
  • Strip dependency references to filtered-out resources so they don't appear as phantom nodes in TopologicalSorter. Cross-type phantom deps (e.g., users → roles when --resources=users) are intentionally preserved — TopologicalSorter yields them first, ensuring dependencies are synced before dependents.
  • Return filtered_count from get_dependency_graph() so the Filtered: N log line remains accurate now that filtering happens at graph construction time
  • Remove redundant filter checks in _apply_resource_cb and _diffs_worker_cb (now dead code since filtered resources never enter the graph)
  • Fix _dependency_graph type annotation from List to Set and correct the assignment-vs-annotation bug on line 41
  • Fix pre-existing bug in Counter.reset_counter() not resetting the filtered field

Performance example

Scenario: 1000 dashboards in state depending on 500 SLOs, syncing with --resources=dashboards --filter matching 5 dashboards (which depend on 8 unique SLOs).

Before #518 After #518, before #524 After #524
Deepcopies 2,500 1,505 18
Resources synced 505 505 13
Sorter nodes ~1,500 ~1,500 13
API calls to dest 505 505 13

Before this change, all 500 SLOs were synced as phantom deps because every dashboard's SLO references entered the graph. After, only the 8 SLOs that the 5 filtered dashboards actually depend on become phantom nodes. The 492 unrelated SLOs are never touched.

Context

When syncing with filters (e.g., 5 dashboards out of 1000 in state), get_dependency_graph() was iterating ALL resources in source state. For each, it called _resource_connections() which deepcopies the resource. All 1000 then entered the topological sorter and worker queue, where 995 were immediately filtered out at _apply_resource_cb. This change moves the filter check earlier so filtered resources never enter the graph.

Builds on #518 which moved the filter check before deepcopy in _apply_resource_cb.

Test plan

  • 14 unit tests in test_dependency_graph.py (8 GREEN invariant tests + 6 RED behavioral tests)
  • Updated test_report_e2e.py::TestFilteredOutcome::test_filtered_resources_excluded_from_graph to reflect filtered resources no longer emitting "filtered" NDJSON events
  • Full unit suite passes (226 tests)
  • Integration tests pass (verified cross-type phantom deps are preserved for correct dependency ordering)

🤖 Generated with Claude Code

When syncing with filters (e.g., 5 dashboards out of 1000 in state),
get_dependency_graph() was iterating ALL resources and deepcopying each
via _resource_connections(). Now it checks the resource filter first and
skips non-matching resources, avoiding 995 unnecessary deepcopy calls.

Also strips phantom dependency references (nodes in dep sets but not
graph keys) to prevent TopologicalSorter from yielding implicit nodes
that waste worker cycles, and fixes the return type annotation from
List to Set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@michael-richey michael-richey marked this pull request as ready for review April 13, 2026 19:57
@michael-richey michael-richey requested a review from a team as a code owner April 13, 2026 19:57
michael-richey and others added 2 commits April 13, 2026 16:26
The blanket `dependency_graph[key] & graph_keys` stripped ALL deps not
explicitly in the graph, including cross-type phantom deps (e.g.,
users → roles when --resources=users). Those phantom nodes are essential
for TopologicalSorter to yield dependencies before dependents.

Replace with targeted `dependency_graph[key] - filtered_out` that only
removes deps pointing to resources explicitly excluded by --filter.

Also removes 2 redundant tests and moves test_cross_type_phantom_deps_preserved
to the GREEN section since it passes on main too.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Return filtered_count from get_dependency_graph() and set it on
  worker.counter so the "Filtered: N" log line is accurate for sync
- Fix reset_counter() to also zero the filtered field (pre-existing bug)
- Remove dead filter checks in _apply_resource_cb and _diffs_worker_cb
  (filtered resources are now excluded at the graph level)
- Rename test_filtered_resources_emitted to
  test_filtered_resources_excluded_from_graph
- Fix mock using both wraps= and return_value= (return_value wins,
  wraps was silently ignored)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
heyronhay
heyronhay previously approved these changes Apr 14, 2026
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@michael-richey michael-richey merged commit 7a80c66 into main Apr 14, 2026
11 checks passed
@michael-richey michael-richey deleted the michael.richey/perf-filter-dependency-graph branch April 14, 2026 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants