perf: replace Redis KEYS command with SCAN for cache invalidation #98

Closed
opened 2026-03-20 13:18:23 +00:00 by cal · 1 comment
Owner

Problem

utils/cache.py CacheManager.clear_prefix() (lines 190–191) uses the Redis KEYS command for pattern-based cache invalidation:

keys = await client.keys(pattern)

KEYS is a well-known Redis anti-pattern — it performs a full keyspace scan and blocks the Redis server for its duration. While the current keyspace is small, this is a latency risk that grows with usage and can cause spikes affecting all concurrent Redis operations.

Fix

Replace with async SCAN iteration:

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)

SCAN is non-blocking and iterates incrementally.

Impact

LOW — Only triggered by @cache_invalidate on write operations, and Redis is optional. But this is a correctness/scalability concern worth fixing proactively.

## Problem `utils/cache.py` `CacheManager.clear_prefix()` (lines 190–191) uses the Redis `KEYS` command for pattern-based cache invalidation: ```python keys = await client.keys(pattern) ``` `KEYS` is a well-known Redis anti-pattern — it performs a full keyspace scan and **blocks the Redis server** for its duration. While the current keyspace is small, this is a latency risk that grows with usage and can cause spikes affecting all concurrent Redis operations. ## Fix Replace with async `SCAN` iteration: ```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) ``` `SCAN` is non-blocking and iterates incrementally. ## Impact **LOW** — Only triggered by `@cache_invalidate` on write operations, and Redis is optional. But this is a correctness/scalability concern worth fixing proactively.
cal added the
ai-working
label 2026-03-20 13:18:42 +00:00
cal removed the
ai-working
label 2026-03-20 13:21:34 +00:00
Claude added the
ai-working
label 2026-03-20 14:01:13 +00:00
Claude added the
status/pr-open
label 2026-03-20 14:02:51 +00:00
Collaborator

PR #101 opened: #101

Replaced client.keys(pattern) with async for key in client.scan_iter(match=pattern) in CacheManager.clear_prefix(). Single-line functional change; 958 tests pass.

PR #101 opened: https://git.manticorum.com/cal/major-domo-v2/pulls/101 Replaced `client.keys(pattern)` with `async for key in client.scan_iter(match=pattern)` in `CacheManager.clear_prefix()`. Single-line functional change; 958 tests pass.
Claude added
ai-pr-opened
and removed
ai-working
labels 2026-03-20 14:02:58 +00:00
cal closed this issue 2026-03-20 15:26:44 +00:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
2 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#98
No description provided.