fix(#337): Replace blocking KEYS command with SCAN in Valkey client

- Use SCAN with cursor for non-blocking iteration
- Prevents Redis DoS under high key counts
- Same API, safer implementation

Refs #337

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-05 15:49:08 -06:00
parent 6d6ef1d151
commit 6a4f58dc1c
2 changed files with 117 additions and 15 deletions

View File

@@ -113,7 +113,7 @@ export class ValkeyClient {
async listTasks(): Promise<TaskState[]> {
const pattern = "orchestrator:task:*";
const keys = await this.client.keys(pattern);
const keys = await this.scanKeys(pattern);
const tasks: TaskState[] = [];
for (const key of keys) {
@@ -184,7 +184,7 @@ export class ValkeyClient {
async listAgents(): Promise<AgentState[]> {
const pattern = "orchestrator:agent:*";
const keys = await this.client.keys(pattern);
const keys = await this.scanKeys(pattern);
const agents: AgentState[] = [];
for (const key of keys) {
@@ -238,6 +238,23 @@ export class ValkeyClient {
* Private helper methods
*/
/**
* Scan keys using SCAN command (non-blocking alternative to KEYS)
* Uses cursor-based iteration to avoid blocking Redis
*/
private async scanKeys(pattern: string): Promise<string[]> {
const keys: string[] = [];
let cursor = "0";
do {
const [nextCursor, batch] = await this.client.scan(cursor, "MATCH", pattern, "COUNT", 100);
cursor = nextCursor;
keys.push(...batch);
} while (cursor !== "0");
return keys;
}
private getTaskKey(taskId: string): string {
return `orchestrator:task:${taskId}`;
}