Skip to content

benchmarking: add a burst benchmark that resumes and suspends many actors at once - #2399

Draft
Sairaj Pokale (sairajp-rewind) wants to merge 2 commits into
agent-substrate:mainfrom
sairajp-rewind:benchmarks/burst-benchmark
Draft

Sairaj Pokale (sairajp-rewind) wants to merge 2 commits into
agent-substrate:mainfrom
sairajp-rewind:benchmarks/burst-benchmark

Conversation

@sairajp-rewind

Copy link
Copy Markdown
Collaborator

Adds BurstUser, a benchmark that measures how fast Substrate resumes and
suspends many actors at the same moment, and how many fail, up to node density
ceilings. SpawnUser creates a batch once and never suspends it; GluttonUser
cycles each actor on its own timer, so actors never burst together.

How it works

  • Setup creates --total-actors actors from the glutton template, all suspended.
  • Each batch resumes every actor at once, keeps them running for one live window,
    then suspends every actor at once. A wait window separates the batches, which
    repeat until the run is stopped.
  • Batch 1 resumes from the golden snapshot and books its own First rows. Later
    batches resume from each actor's own snapshot.
  • Each burst books per-actor times, the 10% to 100% marks, and the time until the
    last success. Locust merges later batches into one row, so the worker also logs
    one burst: batch done line per batch with p50, p90 and max.
  • With --mem-target (and the CPU flags) set, batch 1 fills RAM and starts the CPU
    load; later batches read the RAM the snapshot kept, then churn part of it.
  • An actor that fails a phase is deleted at once and left out of later batches.
    Failures never stop the run: at the density ceiling they are the result.
  • Like spawn, a worker runs one burst, driven by one user (-u 1). Stop cancels it
    and deletes the cohort.

Commits

  1. benchmarking: extract spawn's retry loop: moves spawn's retry loop into
    retryWithBackoff and createLanded so burst can reuse them. Spawn's behavior
    does not change.
  2. benchmarking: add the burst suspend/resume benchmark: burst.go and its tests,
    burst.py, a tests.yaml entry, and a README section.

For review

  • Burst changes no shared code beyond commit 1: it reuses gluttonActor as it is.
    The trade is that rows booked by the reused methods (CreateActor,
    ResumeActor, SuspendActor, DeleteActor, GluttonPing, the RAM and CPU
    rows, CrashCount) keep user_class="GluttonUser" in Prometheus and are not
    dropped after a stop. Burst's own rows (ActorTimeTo*, *_<k>pct,
    TimeToAll*, GluttonReadyPing, Retired*) carry BurstUser and are dropped
    after a stop. stats.csv does not split rows by user class, so it is
    unaffected.

Follow-ups (not in this PR)

  • A shared refactor across the user classes: a user-class field and a stop gate on
    gluttonActor. Burst's reused rows could then carry BurstUser.
  • The durdir workload in burst.

On both pools, no burst-* actors remained after Stop, and a second Start did
nothing until the worker restarted.

  • Tests pass
  • Appropriate changes to documentation are included in the PR

SpawnUser's CreateActor and ResumeActor each had their own copy of the
retry-with-backoff loop. Move it into retryWithBackoff, and the rule
that AlreadyExists on a retry means an earlier attempt landed into
createLanded, so the burst benchmark can reuse both. Spawn's behavior
does not change: the same errors are terminal, with the same backoff.
Add BurstUser, which measures how fast Substrate resumes and suspends
many actors at the same moment. Setup creates --total-actors actors from
the glutton template. Each batch then resumes every actor at once, runs
the workloads for one live window, and suspends every actor at once,
with a wait window between batches. Batch 1 resumes from the golden
snapshot, so it books its own First rows. Each burst books per-actor
times, the 10% to 100% marks, and the time until the last success.

An actor that fails a phase is deleted right away and left out of later
batches, so later batches measure the actors that still work. Failures
never stop the run: at the density ceiling they are the result.

Like SpawnUser, a worker runs one burst, driven by its first user, and a
stop from the web UI cancels it and deletes the cohort in one pass. The
actors reuse gluttonActor unchanged; only the readiness ping, which
needs an error return, is local. Rows booked by the reused code keep
GluttonUser's Prometheus label and are not dropped after a stop, while
burst's own rows are.
)

func init() {
userclass.Add(userclass.Entry{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Heads-up rather than a defect: #2160 (approved, about to land) replaces the shared dynconfig.Config with a per-class knobs struct registered here as Entry.Config, and removes Holder.Load() returning Config, NewHolder(Config) and uniformWait. Burst reads TotalActors, ActorDeadline, the wait and live windows, mem and CPU knobs and MaxPingsPerWake through those, so whichever of the two merges second has to adapt. A trial merge against today's main is clean; against #2160's head this file will not compile. Burst will want its own knobs struct (WaitTime, Lifecycle, live window, mem, CPU, pings, total_actors, actor_deadline) and dynconfig.Get[knobs] in place of cfg.Dyn.Load(), and the frozen copy becomes dynconfig.Static(knobs).


// resumeOne resumes a and waits until it answers through the router.
func (b *burstRun) resumeOne(ctx context.Context, a *burstActor) error {
if err := retryWithBackoff(ctx, func(int) error { return a.resume(ctx) }); err != nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

At the density ceiling the failure this benchmark exists to find is ResourceExhausted ("no free workers available"), and isSpawnTerminalError does not treat it as terminal, so every actor past the ceiling retries ResumeActor with backoff for the whole actor deadline. The ceiling then reads as 120 s latencies and late ActorTimeToResume failures, and the TimeTo*_<k>pct marks are taken over a cohort that is partly spinning in backoff rather than failing. Spawn is a creation benchmark where riding out capacity makes sense; for a burst I would make ResourceExhausted terminal in the resume phase (or make the policy a flag) so the ceiling shows up as failures at the moment they happen, and keep the retries for Unavailable and Aborted.

}
boomerutil.LogSampledTrace(span, "GluttonReadyPing", latency, boomerutil.SourceClient, err)
if err != nil {
b.recordFailure("http", "GluttonReadyPing", latency, err.Error())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Each 100 ms poll until the actor answers books a GluttonReadyPing failure, so the row's failure count is ten per second of restore time per actor, a function of how long the wake took rather than of anything failing. On a 1,000 actor burst that is tens of thousands of failures on a row operators will read as errors. Book only the final outcome on this row (plus an attempts count or the time-to-ready, which ActorTimeTo*Resume already carries), or send the polls to a row named for what they are.

// Gap between readiness pings once ResumeActor has returned.
burstReadyPingGap = 100 * time.Millisecond
// Budget for one round of DeleteActor calls.
burstDeleteTimeout = 60 * time.Second

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

60 s for a whole round at concurrency 32 does not scale with the cohort: an any_state delete of a RUNNING actor tears the sandbox down, about a second each, so a 2,000 actor cohort needs over a minute and a 5,000 one about two and a half, after which teardown logs the leak warning and stops. Since a leaked cohort keeps holding workers for the next run, size the budget from the cohort (for example actorDeadline + N/concurrency × per-delete) or loop rounds until nothing is left or deletes stop making progress.

@bowei Bowei Du (bowei) added area/benchmarking kind/feature An enhancement / feature request or implementation labels Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/benchmarking kind/feature An enhancement / feature request or implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants