Skip to content

Commit 110230d

Browse files
jensensclaude
andauthored
feat(warmer): herd mitigation for rolling deploys (#59) (#69)
* docs(spec): cache warmer herd mitigation design (#59) A1 (jitter) + A2 (baseline delay) + A3 (paced batched warmup) + B2b (N-slot PG advisory-lock semaphore with wait-on-miss) for the cluster- wide inter-pod warmer stampede observed on rolling deploys. Six new config knobs (cache-warm-delay/-jitter/-concurrency/-wait-max/ -batch-size/-batch-pause) with conservative defaults. Reuses the session-level advisory lock pattern from startup_locks.py for slot allocation. Last-pod latency analysis included. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(plan): implementation plan for #59 herd mitigation Bite-sized TDD-ordered tasks (14 total) covering: kwarg extension, A2+A1 delay/jitter, A3 batched warmup, B2b advisory-lock semaphore helpers (try-once, retry-loop, release), warm() integration, storage wiring, ZConfig keys, three DB integration tests, CHANGES entry, full test + lint pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(warmer): add six herd-mitigation kwargs (off defaults) (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(warmer): A2+A1 — startup delay + jitter sleep (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(warmer): A3 — paced batched warmup (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(warmer): B2b — _try_acquire_slot_once helper (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(warmer): B2b — _acquire_slot with retry loop and wait cap (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(warmer): B2b — _release_slot helper (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(warmer): B2b — integrate slot lifecycle into warm() (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(storage): wire herd-mitigation knobs into CacheWarmer (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(config): six ZConfig keys for cache-warm herd mitigation (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(warmer): DB integration tests for B2b semaphore (#59) - concurrency=1 serializes two warmers - concurrency=2 allows two parallel, third waits - crash-safe slot release on connection close Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: changelog entry for cache warmer herd mitigation (#59) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: ruff format + lint after herd-mitigation changes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(warmer): code review fixes (#59) - close lock connection on slot acquisition timeout (was leaking) - continue scanning remaining slots when single-slot try_lock raises (was halting prematurely, deviating from spec) - use fresh psycopg connection per warmer thread in DB integration tests (psycopg connections are not thread-safe) - assert lock_conn.close() called on timeout path in unit test Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 51ed12b commit 110230d

9 files changed

Lines changed: 3210 additions & 29 deletions

File tree

CHANGES.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,43 @@
11
# Changelog
22

3+
## 1.13.0 (unreleased)
4+
5+
### Features
6+
7+
- **Cache warmer herd mitigation** (#59). Rolling Kubernetes deploys
8+
used to multiply DB primary CPU by the replica count for the warmer
9+
startup window, because every pod independently fired its full set
10+
of warmup `SELECT`s simultaneously. Four composable behaviors fix
11+
this, all wired through six new ZConfig keys:
12+
13+
- `cache-warm-delay` (default 15s): baseline sleep before warmer
14+
starts. Moves the warmer out of the pod's own cold-start window
15+
(Plone import, plone.pgcatalog schema check, ANALYZE chatter).
16+
- `cache-warm-jitter` (default 30s): additional `random(0, jitter)`
17+
sleep on top. Spreads arrival times across pods.
18+
- `cache-warm-concurrency` (default 2): maximum number of pods
19+
warming in parallel, cluster-wide. Enforced via a session-level
20+
PostgreSQL advisory lock semaphore (same pattern as the existing
21+
startup-DDL lock). Pods that miss a slot retry with jittered
22+
backoff until `cache-warm-wait-max` expires.
23+
- `cache-warm-wait-max` (default 300s): retry cap before giving up
24+
and skipping warmup (logged as WARNING).
25+
- `cache-warm-batch-size` (default 500): zoids per `SELECT` batch.
26+
- `cache-warm-batch-pause` (default 0.5s): sleep between batches.
27+
Lowers per-pod peak qps.
28+
29+
**Observable behavior change:** warmer queries are now delayed by
30+
~15–45s post-startup (delay + jitter), not immediate. To restore
31+
pre-1.13 behavior, set `cache-warm-delay=0`, `cache-warm-jitter=0`,
32+
`cache-warm-batch-pause=0`, `cache-warm-concurrency=9999`.
33+
34+
**Last-pod latency:** for a 6-pod fleet with default settings, the
35+
last pod has a warm L2 cache approximately 45s after the deploy
36+
window. All pods serve traffic from T+0; this is the time-to-warm,
37+
not the time-to-ready.
38+
39+
Design: [docs/superpowers/specs/2026-05-29-cache-warmer-herd-mitigation-design.md](docs/superpowers/specs/2026-05-29-cache-warmer-herd-mitigation-design.md).
40+
341
## 1.12.0
442

543
### Features

0 commit comments

Comments
 (0)