last-change-session-4now: single-pass streaming audit metrics; skip redundant persist on rate-limit rejects#420
Merged
Conversation
…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
627e351 to
7161a6a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
CyClaw-Optimize chunk 3/5 — two hot-path perf fixes, no behavior change:
metrics.py— single streaming pass.compute_metricsmade ~5 separate passes (Counter + 4 comprehensions) over a fully materializedaudit.jsonllist, andGET /audit/summaryloaded the entire append-only, unbounded file into memory on every call.compute_metricsnow aggregates in one pass over any iterable; newiter_events()/summarize_audit()stream the JSONL line-by-line so the endpoint andcyclaw-metricsCLI run at O(1) memory regardless of audit history.load_eventsis kept as the materialized form for existing callers/tests. Summary shape and semantics are identical (same keys, samemost_common()ordering, same escalation heuristics).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/summaryhandler switches from twoto_threadhops (load_events→compute_metrics) to onesummarize_auditcall.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.py→ 75 passed (includes the persistence-restart and audit-summary aggregate tests).Risk to monitor
test_counters_survive_restart).compute_metricsnow accepts a generator; all existing list callers unaffected.Note:
gate.pyis also touched by open PR #415 in disjoint regions (import block/audit_summaryhere 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