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) <noreply@anthropic.com>
This commit is contained in:
Rob Ballantyne
2026-05-11 18:26:31 +01:00
parent 8df562e243
commit 2aada7b210
2 changed files with 36 additions and 21 deletions
+7 -5
View File
@@ -151,11 +151,13 @@ Staggered demo:
python -m workers.null.client --endpoint <ENDPOINT_NAME> --demo python -m workers.null.client --endpoint <ENDPOINT_NAME> --demo
``` ```
Starts three reservations 30s apart (all held concurrently) with a 90s Starts three reservations 30s apart (all held concurrently), holds the
duration each. They scale down one at a time, also 30s apart, then the 3-worker plateau for 5 minutes so the autoscaler has time to actually
client exits — a clean trapezoidal load curve for watching scale-up and provision the third worker before any scale-down starts, then scales
scale-down in the autoscaler dashboard. Each reservation ends via its down one worker at a time, also 30s apart, and exits.
duration cap (a 200 success in metrics).
Each reservation ends via its duration cap (a 200 success in metrics).
Tune the timing with `--interval` and `--plateau`.
## Notes and caveats ## Notes and caveats
+29 -16
View File
@@ -51,23 +51,24 @@ async def run_demo(
*, *,
endpoint_name: str, endpoint_name: str,
interval: float, interval: float,
plateau: float,
) -> None: ) -> 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 Start three reservations spaced `interval` seconds apart. Pick the
duration equal to 3 * interval. The staggered starts and identical duration so that the first release fires `plateau` seconds *after the
durations mean they end one at a time, also `interval` apart, so the last reservation started*, giving the autoscaler time to actually have
load curve ramps up over 2*interval, plateaus at 3 for `interval`, and all three workers running before any of them begin to scale down.
ramps down over 2*interval. Each reservation ends via its duration cap Releases then fire `interval` seconds apart, matching the ramp-up.
(a 200 success, not a 499 cancellation).
Each reservation ends via its duration cap (a 200 success).
""" """
hold = interval * 3 n = 3
hold = (n - 1) * interval + plateau
tasks: list[asyncio.Task] = [] tasks: list[asyncio.Task] = []
for i in range(1, 4): for i in range(1, n + 1):
label = f"res-{i}" label = f"res-{i}"
log.info( log.info("[%s] starting (auto-release after %.0fs)", label, hold)
"[%s] starting (auto-release after %.0fs)", label, hold
)
task = asyncio.create_task( task = asyncio.create_task(
reserve( reserve(
client, client,
@@ -78,15 +79,16 @@ async def run_demo(
name=label, name=label,
) )
tasks.append(task) tasks.append(task)
if i < 3: if i < n:
log.info("Waiting %.0fs before next reservation...", interval) log.info("Waiting %.0fs before next reservation...", interval)
await asyncio.sleep(interval) await asyncio.sleep(interval)
log.info( log.info(
"All 3 reservations in flight; they will scale down %.0fs apart, " "All %d reservations in flight; holding plateau for %.0fs, "
"starting in %.0fs", "then scaling down %.0fs apart",
n,
plateau,
interval, interval,
hold - 2 * interval,
) )
results = await asyncio.gather(*tasks, return_exceptions=True) results = await asyncio.gather(*tasks, return_exceptions=True)
for task, result in zip(tasks, results): for task, result in zip(tasks, results):
@@ -125,6 +127,16 @@ def build_arg_parser() -> argparse.ArgumentParser:
default=30.0, default=30.0,
help="Demo mode: seconds between reservation steps (default: 30)", 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 return p
@@ -142,6 +154,7 @@ async def main_async():
client, client,
endpoint_name=args.endpoint, endpoint_name=args.endpoint,
interval=args.interval, interval=args.interval,
plateau=args.plateau,
) )
else: else:
response = await reserve( response = await reserve(