ReviewOS

stacks/ts-cache

RedisDriver.keys() runs KEYS, which blocks the whole server for a scan of every key

#3094
Closed glennmichael123 opened this 4 days ago ·
4 days ago

RedisDriver.keys() runs KEYS, which walks the whole keyspace in one command and blocks the server while it does. Every other client of that Redis waits until it finishes, however few keys match.

Measurement

Redis 8.8.0 holding 1,000,000 keys. One client calls driver.keys('__stacks_tag__:<32 hex>:*'), which matches nothing, while another sends PING in a loop:

callworst PING
idle (control)-2 ms
keys(pattern), 3 runs76-80 ms75-79 ms

So the blocked time is the scan of the whole keyspace, not of the matches, and it grows with the size of the database. At 10M keys that is most of a second during which the server answers nothing else.

Where it is reached

  • keys(pattern) directly. @stacksjs/cache now flushes a tag by listing that tag's index entries (stacksjs/stacks#3073), so every tag flush on Redis is one of these.
  • flush() with a prefix: keys('*'), then a single DEL with every key as an argument.
  • getStats(): keys('*') on every call.

Fix

Walk the keyspace with SCAN cursor MATCH pattern COUNT n until the cursor comes back as 0, de-duplicating, since SCAN may return a key twice. The total work is the same, but each step is short and other clients are served between steps. flush() deletes in batches rather than with one DEL of everything.

Found while auditing @stacksjs/cache for 1.0 (stacksjs/stacks#1169).

Sign in to comment on this issue.