Skip to content

last-change-session-4now: single-pass streaming audit metrics; skip redundant persist on rate-limit rejects#420

Merged
cgfixit merged 1 commit into
mainfrom
claude/cyclaw-optimize-hotpath-perf
Jul 4, 2026
Merged

last-change-session-4now: single-pass streaming audit metrics; skip redundant persist on rate-limit rejects#420
cgfixit merged 1 commit into
mainfrom
claude/cyclaw-optimize-hotpath-perf

Conversation

@cgfixit

@cgfixit cgfixit commented Jul 4, 2026

Copy link
Copy Markdown
Owner

What

CyClaw-Optimize chunk 3/5 — two hot-path perf fixes, no behavior change:

  1. metrics.py — single streaming pass. compute_metrics made ~5 separate passes (Counter + 4 comprehensions) over a fully materialized audit.jsonl list, and GET /audit/summary loaded the entire append-only, unbounded file into memory on every call. compute_metrics now aggregates in one pass over any iterable; new iter_events() / summarize_audit() stream the JSONL line-by-line so the endpoint and cyclaw-metrics CLI run at O(1) memory regardless of audit history. load_events is kept as the materialized form for existing callers/tests. Summary shape and semantics are identical (same keys, same most_common() ordering, same escalation heuristics).
  2. utils/ratelimit.py — no redundant upsert on rejects. allow() fired a sqlite/Postgres write on every rejected request even when the stored window was unchanged (rejects append no timestamp). An IP hammering the endpoint triggered a DB write per rejection — write amplification exactly under the abuse condition the limiter exists to make cheap. The reject path now persists only when expiry pruning actually changed the stored state.

gate.py's /audit/summary handler switches from two to_thread hops (load_eventscompute_metrics) to one summarize_audit call.

Why / benefit

Performance and memory-boundedness on two per-request paths; the audit-summary cost no longer grows with deployment age.

Verification

GROK_API_KEY=dummy pytest tests/test_metrics.py tests/test_rate_limit.py tests/test_gate.py tests/test_audit.py75 passed (includes the persistence-restart and audit-summary aggregate tests).

Risk to monitor

  • Persistence semantics on rejects: the backend already held the identical window state in the skipped case, so restart-recovery behavior is unchanged (covered by test_counters_survive_restart).
  • compute_metrics now accepts a generator; all existing list callers unaffected.

Note: gate.py is also touched by open PR #415 in disjoint regions (import block/audit_summary here vs. retriever init/confirm-message there) — a trial 3-way merge shows no conflict.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MLvAGGjgyVbJVT7tXcp9UA


Generated by Claude Code

@cgfixit cgfixit changed the title perf: single-pass streaming audit metrics; skip redundant persist on rate-limit rejects last-change-session-4now: single-pass streaming audit metrics; skip redundant persist on rate-limit rejects Jul 4, 2026
…rate-limit rejects

metrics.compute_metrics made ~5 separate passes over a fully materialized
audit.jsonl (an append-only, unbounded file), and GET /audit/summary loaded
the whole file into memory on every call. compute_metrics now aggregates in
one pass over any iterable; the new iter_events()/summarize_audit() stream
the file line-by-line, so the endpoint and the cyclaw-metrics CLI run at
O(1) memory regardless of audit history. load_events stays as the
materialized form for existing callers. Summary shape and semantics are
byte-identical.

RateLimiter.allow() fired a sqlite/Postgres upsert on every REJECTED request
even when the stored window was unchanged (no timestamp appended, nothing
pruned) — write amplification precisely under the hammering condition the
limiter exists to cheapen. The reject path now persists only when expiry
pruning actually changed the stored state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MLvAGGjgyVbJVT7tXcp9UA
@cgfixit cgfixit force-pushed the claude/cyclaw-optimize-hotpath-perf branch from 627e351 to 7161a6a Compare July 4, 2026 05:19
@cgfixit cgfixit marked this pull request as ready for review July 4, 2026 05:21
@cgfixit cgfixit merged commit 10007bf into main Jul 4, 2026
25 checks passed
@cgfixit cgfixit deleted the claude/cyclaw-optimize-hotpath-perf branch July 4, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants