Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content

[dbsp] Count the runs of a nested circuit that get no input - #7743

Open
anandbraman wants to merge 2 commits into
mainfrom
worktree-recursive-idle-run-counter
Open

anandbraman wants to merge 2 commits into
mainfrom
worktree-recursive-idle-run-counter

Conversation

@anandbraman

@anandbraman anandbraman commented Oct 30, 2026 •

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 with 4 workers (debug build):

Transaction Root steps Runs Runs with input (max over workers) Inner steps per run
facts only 7 to 9 = root steps 0 about 3
empty 8 to 9 = root steps 0 about 3
edges only 9 9 1 about 9.4

With 1 worker, every commit takes 1 root step and 1 run.

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:

Describe Incompatible Changes

None. The profile metric catalog gets three new entries.

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 <anand.raman@feldera.com>
@mythical-fred-oss

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).

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 <anand.raman@feldera.com>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant