perf: replace Redis KEYS with SCAN for cache invalidation (#98) #101

Merged
cal merged 1 commits from ai/major-domo-v2-98 into next-release 2026-03-20 15:26:43 +00:00
Collaborator

Closes #98

Summary

Replace the blocking KEYS command with non-blocking SCAN iteration in CacheManager.clear_prefix().

Before:

keys = await client.keys(pattern)
if keys:
    await client.delete(*keys)

After:

keys_to_delete = []
async for key in client.scan_iter(match=pattern):
    keys_to_delete.append(key)
if keys_to_delete:
    await client.delete(*keys_to_delete)

KEYS blocks the Redis server for its full duration; scan_iter iterates incrementally and non-blocking.

Files Changed

  • utils/cache.pyCacheManager.clear_prefix() lines 190–194

Test Results

958 passed, 2 skipped

Closes #98 ## Summary Replace the blocking `KEYS` command with non-blocking `SCAN` iteration in `CacheManager.clear_prefix()`. **Before:** ```python keys = await client.keys(pattern) if keys: await client.delete(*keys) ``` **After:** ```python keys_to_delete = [] async for key in client.scan_iter(match=pattern): keys_to_delete.append(key) if keys_to_delete: await client.delete(*keys_to_delete) ``` `KEYS` blocks the Redis server for its full duration; `scan_iter` iterates incrementally and non-blocking. ## Files Changed - `utils/cache.py` — `CacheManager.clear_prefix()` lines 190–194 ## Test Results 958 passed, 2 skipped
Claude added 1 commit 2026-03-20 14:02:41 +00:00
perf: replace Redis KEYS with SCAN in clear_prefix (#98)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m22s
df9e9bedbe
Closes #98

Replace blocking `client.keys(pattern)` with non-blocking
`client.scan_iter(match=pattern)` to avoid full-keyspace scans
that block the Redis server during cache invalidation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude added the
ai-reviewing
label 2026-03-20 14:45:49 +00:00
Claude reviewed 2026-03-20 14:47:00 +00:00
Claude left a comment
Author
Collaborator

AI Code Review

Files Reviewed

  • utils/cache.py (modified)

Findings

Correctness

  • The scan_iter(match=pattern) call is the correct async API on redis.asyncio. It returns an AsyncIterator[bytes] and the match= keyword argument is the right parameter name — no issues there.
  • The accumulate-then-delete pattern is logically equivalent to the original two-step keys + delete flow. The deleted variable still receives the return value of client.delete(*keys_to_delete) and is passed to the logger — unchanged behavior on this path.
  • The empty-result path is correct: when no keys match, keys_to_delete stays [], the if keys_to_delete: block is skipped, and the function falls through to return 0 at line 199 — identical to the old if keys: guard.
  • No new imports are required; scan_iter is a method on the same redis.asyncio.Redis client already in use.

Security

  • No issues. The prefix parameter comes from internal call sites (decorator arguments), not from user input, so there is no pattern injection concern.

Style & Conventions

  • Variable name keys_to_delete is clear and unambiguous.
  • No lazy imports added. No convention violations against CLAUDE.md.
  • The change is minimal — only the 5 lines inside the try block are touched.

Suggestions

  • The accumulate-then-batch-delete approach loads all matching keys into memory before issuing the DELETE. For the typical key counts in this league bot this is fine, but if the keyspace ever grows very large a pipeline-based approach (delete in chunks of e.g. 100 during the scan) would reduce peak memory. Not a concern for the current use case.
  • There is no test_utils_cache.py covering CacheManager.clear_prefix() — this is a pre-existing gap not introduced by this PR. The PR body confirms 958 tests pass with no regressions. Adding unit tests for this method in a follow-up would be worthwhile.

Verdict: APPROVED

The change is correct, minimal, and accomplishes exactly what it claims: replacing the blocking KEYS command with non-blocking SCAN iteration. No logic, error handling, or return-value behavior changes. Safe to merge.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `utils/cache.py` (modified) ### Findings #### Correctness - The `scan_iter(match=pattern)` call is the correct async API on `redis.asyncio`. It returns an `AsyncIterator[bytes]` and the `match=` keyword argument is the right parameter name — no issues there. - The accumulate-then-delete pattern is logically equivalent to the original two-step `keys` + `delete` flow. The `deleted` variable still receives the return value of `client.delete(*keys_to_delete)` and is passed to the logger — unchanged behavior on this path. - The empty-result path is correct: when no keys match, `keys_to_delete` stays `[]`, the `if keys_to_delete:` block is skipped, and the function falls through to `return 0` at line 199 — identical to the old `if keys:` guard. - No new imports are required; `scan_iter` is a method on the same `redis.asyncio.Redis` client already in use. #### Security - No issues. The `prefix` parameter comes from internal call sites (decorator arguments), not from user input, so there is no pattern injection concern. #### Style & Conventions - Variable name `keys_to_delete` is clear and unambiguous. - No lazy imports added. No convention violations against CLAUDE.md. - The change is minimal — only the 5 lines inside the `try` block are touched. #### Suggestions - The accumulate-then-batch-delete approach loads all matching keys into memory before issuing the `DELETE`. For the typical key counts in this league bot this is fine, but if the keyspace ever grows very large a pipeline-based approach (delete in chunks of e.g. 100 during the scan) would reduce peak memory. Not a concern for the current use case. - There is no `test_utils_cache.py` covering `CacheManager.clear_prefix()` — this is a pre-existing gap not introduced by this PR. The PR body confirms 958 tests pass with no regressions. Adding unit tests for this method in a follow-up would be worthwhile. ### Verdict: APPROVED The change is correct, minimal, and accomplishes exactly what it claims: replacing the blocking `KEYS` command with non-blocking `SCAN` iteration. No logic, error handling, or return-value behavior changes. Safe to merge. --- *Automated review by Claude PR Reviewer*
cal changed target branch from main to next-release 2026-03-20 15:26:07 +00:00
cal merged commit 8d97e1dd17 into next-release 2026-03-20 15:26:43 +00:00
cal deleted branch ai/major-domo-v2-98 2026-03-20 15:26:44 +00:00
cal removed the
ai-reviewing
label 2026-03-23 15:32:40 +00:00
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: cal/major-domo-v2#101
No description provided.