GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — [dbsp] Count the runs of a nested circuit that get no input by anandbraman · Pull Request #7743 · feldera/feldera

Conversation

@anandbraman

@anandbraman anandbraman commented Oct 30, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Linked Issue

No issue yet. This adds the measurements for two planned issues: skip a run of a recursive sub-circuit that has no input, and let a sharded accumulator receiver finish its flush in the step in which its peers flush.

What this changes

A nested circuit runs to a fixed point once in every step of its parent, also when none of its imports has data. In transaction mode one commit takes several root steps, so a recursive view can run many times per commit with nothing to do. The circuit profile shows the steps and the runtime of a nested circuit, but not how many of its runs had input.

This PR adds three counters to each nested circuit node. They appear in the circuit profile, and so in support bundles:

Metric Meaning (per worker)
subcircuit_runs_count Runs of the nested circuit, one per parent step
subcircuit_runs_with_input_count Runs in which at least one import was not empty
subcircuit_runs_input_unknown_count Runs in which an import could not tell if its value was empty

How:

  • ImportOperator::imported_empty() reports if the last imported value was empty. The default is None.
  • Delta0 records val.is_zero(), and ImportAccumulator (the import of non_incremental circuits) records val.is_empty(), on each import. Delta0NonIterative keeps the default: it has no callers, and its D: Clone bound has no emptiness test.
  • ChildNode::eval classifies each run after it runs the import nodes, and ChildNode::metadata reports the counts.
  • profiler-lib takes the largest reading of the three counts (AggregationMode.Max), like steps_count, instead of adding them up over the workers.

The counters do not change what the circuit computes or when it runs.

A non_incremental circuit is also run once per parent step, but it only collects its input until the step in which the input is complete. The description of subcircuit_runs_count says so.

Describe Manual Test Plan

New tests in operator::dynamic::recursive::test::run_stats. Each builds a nested circuit over edges and, next to it, a chain of 8 joins over facts, so that a commit takes several root steps. Each measures root steps, inner steps (from scheduler events) and the new counters for each commit, with 1 and 4 workers. Run them with --nocapture to see the tables.

Test Nested circuit Checks
recursion_runs_at_every_root_step Transitive closure, imported with delta0 runs = root steps; runs with input only when edges changed; no unknown runs
opaque_import_counts_runs_as_unknown The same closure, imported with an operator that keeps the default imported_empty every run counts as unknown, none as with input
non_incremental_runs_at_every_root_step non_incremental circuit runs = root steps; runs with input only when edges changed; no unknown runs

Results for recursion_runs_at_every_root_step with 4 workers (debug build), before and after merging main with 2fbe60d (#7745, a receiver finishes its flush in the step of the last flush):

Transaction Root steps, before → after Runs Runs with input (max over workers) Inner steps per run, before → after
facts only 7 to 9 → 1 = root steps 0 about 3 → 2
empty 8 to 9 → 1 = root steps 0 about 3 → 2
edges only 9 → 1 = root steps 1 about 9.4 → 23 to 25 (one run does all the work)

With 1 worker, every commit took 1 root step and 1 run before and after. After the merge, a commit that does not touch the recursion's input still runs it once, with no input on any worker (#7744).

The test asserts today's behavior: the nested circuit runs once per root step. A change that skips runs without input must update the test.

Checks:

  • I broke the counters on purpose in three ways (Delta0 always reports "empty"; the run count counts only runs with input; ImportAccumulator keeps the default). The tests failed each time.
  • cargo test -p dbsp --lib -- recursive nonlinear_recursion profile delta0 non_incremental: 67 passed.
  • profiler-lib: profile.test.ts passes (179 tests) under the Bun runtime. With the old profile.ts, the new mode(...) assertions fail. Under Node 20 the file does not load (entries().map needs iterator helpers); regionSize.test.ts and viewport.test.ts fail the same way with and without this change.
  • cargo clippy -p dbsp --lib --tests -- -D warnings and cargo fmt: clean. The pre-commit hooks passed.

Checklist

  • Unit tests added/updated
  • Integration tests added/updated
  • Documentation updated
  • Changelog updated

Breaking Changes?

Mark if you think the answer is yes for any of these components:

  • OpenAPI / REST HTTP API / feldera-types / manager (What is a breaking change?)
  • Feldera SQL (Syntax, Semantics)
  • feldera-sqllib (incl. dependencies fxp, etc.) (What is a breaking change?)
  • Python SDK (What is a breaking change?)
  • fda (CLI arguments)
  • Adapters (including configuration)
  • Storage Format / Checkpoints
  • Others (specify)

Describe Incompatible Changes

None. The profile metric catalog gets three new entries.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@anandbraman
[dbsp] Count the runs of a nested circuit that get no input.
8006067
A nested circuit runs to a fixed point once in every step of its parent,
also when none of its imports has data.  In transaction mode one commit
takes several root steps, so a recursive view can run many times per
commit with nothing to do.  The circuit profile shows the steps and the
runtime of the nested circuit, but not how many of its runs had input.

Add three counters to each nested circuit node, reported in the circuit
profile:

- subcircuit_runs_count: runs, one per parent step.
- subcircuit_runs_with_input_count: runs in which at least one import
  was not empty on this worker.
- subcircuit_runs_input_unknown_count: runs in which an import could not
  tell if its value was empty.

An import operator now reports if its last imported value was empty
(`ImportOperator::imported_empty`).  `Delta0` implements it; other
import operators report that they cannot tell.

Add a test that measures runs, runs with input, and root and inner steps
per commit for a transitive closure next to a chain of joins, with 1 and
4 workers.

Signed-off-by: Anand Raman <[email protected]>
@anandbraman anandbraman mentioned this pull request Oct 30, 2026
[dbsp] Skip a recursive sub-circuit at a step where its inputs did not change #7744
Open
mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Oct 30, 2026
View reviewed changes
Comment thread crates/dbsp/src/circuit/circuit_builder.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

mythical-fred-oss[bot]
mythical-fred-oss Bot reviewed Oct 30, 2026
View reviewed changes
Comment thread crates/dbsp/src/circuit/operator_traits.rs Resolved

Uh oh!

There was an error while loading. Please reload this page.

@mythical-fred-oss

mythical-fred-oss Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

What I ran:

Command Result
cargo test --locked -p dbsp --lib run_stats -- --nocapture (run 4 times) pass every time, runs ~1.3 s
cargo test --locked -p dbsp --lib -- recursive nonlinear_recursion profile delta0 64 passed

The table I got matches the PR description: runs = root steps, and 0 runs with input for "facts only" and "empty".

  • observation (flakiness, medium): root steps per commit vary between runs (one 4-worker "facts only" commit took 1 step and the next took 9). The assertions only compare relative values (runs == root_steps, inner >= runs), so the test stays deterministic. Please keep it that way and don't assert exact step counts later.
  • missing (medium): no test covers a nested circuit whose import reports None (the unknown path), or a non-recursive child circuit. See the inline comment.
  • Gates: unit test added ✅, no unsafe ✅, no new deps ✅, no breaking change ✅. The counters are only visible as advanced profile metrics, so no docs are needed beyond the catalog descriptions.

Otherwise LGTM once the profiler aggregation is fixed (see the inline comment).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@anandbraman anandbraman mentioned this pull request Oct 30, 2026
[dbsp] Let a sharded accumulator receiver finish its flush in the step in which its peers flush #7745
Closed
anandbraman added 3 commits October 30, 2026 11:50
@anandbraman
[dbsp] Classify non-incremental imports; do not add up run counts.
7522810
Review fixes for the nested circuit run counters:

- `ImportAccumulator`, the import of `non_incremental` circuits, now
  reports if its imported batch was empty.  Before, every run of such a
  circuit counted as a run with unknown input.
- profiler-lib takes the largest reading of the three run counts, like
  `steps_count`, instead of adding them up over the workers.
- The description of `subcircuit_runs_count` says that a
  non-incremental circuit only collects its input until it is complete.
- New tests: an import that cannot tell if its input was empty, and a
  non-incremental circuit.

Signed-off-by: Anand Raman <[email protected]>
@anandbraman
Merge remote-tracking branch 'origin/main' into worktree-recursive-id…
6e17ad8
…le-run-counter

# Conflicts:
#	crates/dbsp/src/circuit/metadata.rs
@anandbraman
Merge remote-tracking branch 'origin/main' into worktree-recursive-id…
8cf7f86
…le-run-counter

# Conflicts:
#	js-packages/profiler-lib/src/profile.ts

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@mythical-fred-oss mythical-fred-oss[bot] mythical-fred-oss[bot] left review comments

At least 1 approving review is required to merge this pull request.

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@anandbraman
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.