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:
@@ -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
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user