feat(summary): revision-gated cache with stale-while-revalidate (STATE-WP-0066)

Replace the fixed 15s TTL on GET /state/summary with per-table revision
watermarks, stale-while-revalidate background refresh, and a progress-tail
section split. SQLAlchemy write hooks invalidate core or progress sections
on mutation. Adds tests, benchmark script, and operator docs.
This commit is contained in:
2026-06-22 16:27:32 +02:00
parent f88e74288d
commit 94c7817339
10 changed files with 614 additions and 35 deletions

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""Quick benchmark for /state/summary revision cache (STATE-WP-0066)."""
from __future__ import annotations
import argparse
import statistics
import sys
import time
import urllib.request
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-url", default="http://127.0.0.1:8000")
parser.add_argument("--requests", type=int, default=10)
args = parser.parse_args()
url = f"{args.base_url.rstrip('/')}/state/summary"
timings: list[float] = []
last_cache = ""
# Prime cache
with urllib.request.urlopen(url, timeout=30) as resp:
resp.read()
last_cache = resp.headers.get("X-StateHub-Cache", "")
for _ in range(args.requests):
started = time.perf_counter()
with urllib.request.urlopen(url, timeout=30) as resp:
resp.read()
last_cache = resp.headers.get("X-StateHub-Cache", "")
timings.append(time.perf_counter() - started)
p95 = statistics.quantiles(timings, n=20)[18] if len(timings) >= 2 else timings[0]
print(f"requests={args.requests} p50={statistics.median(timings):.3f}s p95={p95:.3f}s last_cache={last_cache}")
return 0 if last_cache == "hit-revision" else 1
if __name__ == "__main__":
sys.exit(main())