From 2aada7b210595b183507f27f52fe18dd5e219077 Mon Sep 17 00:00:00 2001 From: Rob Ballantyne Date: Mon, 11 May 2026 18:26:31 +0100 Subject: [PATCH] Add --plateau to null pyworker demo (default 5min) Previously the first release fired only 30s after the third reservation started, so the autoscaler often hadn't even finished provisioning the third worker yet. Default plateau to 300s so all three workers are visibly running before scale-down begins; configurable via --plateau. Co-Authored-By: Claude Opus 4.7 (1M context) --- workers/null/README.md | 12 ++++++----- workers/null/client.py | 45 +++++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/workers/null/README.md b/workers/null/README.md index 4b2848f..86fdba2 100644 --- a/workers/null/README.md +++ b/workers/null/README.md @@ -151,11 +151,13 @@ Staggered demo: python -m workers.null.client --endpoint --demo ``` -Starts three reservations 30s apart (all held concurrently) with a 90s -duration each. They scale down one at a time, also 30s apart, then the -client exits — a clean trapezoidal load curve for watching scale-up and -scale-down in the autoscaler dashboard. Each reservation ends via its -duration cap (a 200 success in metrics). +Starts three reservations 30s apart (all held concurrently), holds the +3-worker plateau for 5 minutes so the autoscaler has time to actually +provision the third worker before any scale-down starts, then scales +down one worker at a time, also 30s apart, and exits. + +Each reservation ends via its duration cap (a 200 success in metrics). +Tune the timing with `--interval` and `--plateau`. ## Notes and caveats diff --git a/workers/null/client.py b/workers/null/client.py index eacce49..86bd61f 100644 --- a/workers/null/client.py +++ b/workers/null/client.py @@ -51,23 +51,24 @@ async def run_demo( *, endpoint_name: str, interval: float, + plateau: float, ) -> None: - """Trapezoidal load: ramp up three reservations, then let them scale down. + """Trapezoidal load: ramp up three reservations, plateau, then scale down. - Start three reservations spaced `interval` seconds apart, each with a - duration equal to 3 * interval. The staggered starts and identical - durations mean they end one at a time, also `interval` apart, so the - load curve ramps up over 2*interval, plateaus at 3 for `interval`, and - ramps down over 2*interval. Each reservation ends via its duration cap - (a 200 success, not a 499 cancellation). + Start three reservations spaced `interval` seconds apart. Pick the + duration so that the first release fires `plateau` seconds *after the + last reservation started*, giving the autoscaler time to actually have + all three workers running before any of them begin to scale down. + Releases then fire `interval` seconds apart, matching the ramp-up. + + Each reservation ends via its duration cap (a 200 success). """ - hold = interval * 3 + n = 3 + hold = (n - 1) * interval + plateau tasks: list[asyncio.Task] = [] - for i in range(1, 4): + for i in range(1, n + 1): label = f"res-{i}" - log.info( - "[%s] starting (auto-release after %.0fs)", label, hold - ) + log.info("[%s] starting (auto-release after %.0fs)", label, hold) task = asyncio.create_task( reserve( client, @@ -78,15 +79,16 @@ async def run_demo( name=label, ) tasks.append(task) - if i < 3: + if i < n: log.info("Waiting %.0fs before next reservation...", interval) await asyncio.sleep(interval) log.info( - "All 3 reservations in flight; they will scale down %.0fs apart, " - "starting in %.0fs", + "All %d reservations in flight; holding plateau for %.0fs, " + "then scaling down %.0fs apart", + n, + plateau, interval, - hold - 2 * interval, ) results = await asyncio.gather(*tasks, return_exceptions=True) for task, result in zip(tasks, results): @@ -125,6 +127,16 @@ def build_arg_parser() -> argparse.ArgumentParser: default=30.0, help="Demo mode: seconds between reservation steps (default: 30)", ) + p.add_argument( + "--plateau", + type=float, + default=300.0, + help=( + "Demo mode: seconds to hold all 3 reservations active before " + "scale-down starts. Gives the autoscaler time to fully spin " + "up the third worker (default: 300)" + ), + ) return p @@ -142,6 +154,7 @@ async def main_async(): client, endpoint_name=args.endpoint, interval=args.interval, + plateau=args.plateau, ) else: response = await reserve(