Problem
The claim-check payload is written to Redis first, then the job is added (apps/hocuspocus.server/src/lib/queue.ts:156-157). If StoreDocumentQueue.add throws, the payload stays behind with no job that will ever read it. Nothing in enqueueStoreDocument catches that throw.
This is not a permanent leak. The write sets a TTL from const STATE_KEY_TTL_SECONDS = 3600 (apps/hocuspocus.server/src/lib/queue.ts:142). But a multi-megabyte document state then sits in Redis for up to an hour, doing nothing. refreshPendingStateKeyTtls cannot shorten that. It re-arms TTLs from job ids read off the wait and paused lists (apps/hocuspocus.server/src/lib/queue.ts:369-372). An orphan has no job on either list.
The orphans share one Redis keyspace with the rate limiter, as the comment at apps/hocuspocus.server/src/middleware/index.ts:52-55 records. Production runs that Redis with --maxmemory-policy volatile-lru (docker-compose.prod.yml:110). Eviction under that policy only picks keys that carry a TTL, and the claim-check states carry one. So during a queue outage the orphans raise eviction pressure on the live claim-check keys next to them.
What to do
Wrap the add call. On failure, delete stateKey before the error propagates to the caller. The same delete already exists for the success and DLQ paths (apps/hocuspocus.server/src/lib/queue.ts:518 and apps/hocuspocus.server/src/lib/queue.ts:605), so match that shape.
Acceptance
Problem
The claim-check payload is written to Redis first, then the job is added (
apps/hocuspocus.server/src/lib/queue.ts:156-157). IfStoreDocumentQueue.addthrows, the payload stays behind with no job that will ever read it. Nothing inenqueueStoreDocumentcatches that throw.This is not a permanent leak. The write sets a TTL from
const STATE_KEY_TTL_SECONDS = 3600(apps/hocuspocus.server/src/lib/queue.ts:142). But a multi-megabyte document state then sits in Redis for up to an hour, doing nothing.refreshPendingStateKeyTtlscannot shorten that. It re-arms TTLs from job ids read off thewaitandpausedlists (apps/hocuspocus.server/src/lib/queue.ts:369-372). An orphan has no job on either list.The orphans share one Redis keyspace with the rate limiter, as the comment at
apps/hocuspocus.server/src/middleware/index.ts:52-55records. Production runs that Redis with--maxmemory-policy volatile-lru(docker-compose.prod.yml:110). Eviction under that policy only picks keys that carry a TTL, and the claim-check states carry one. So during a queue outage the orphans raise eviction pressure on the live claim-check keys next to them.What to do
Wrap the
addcall. On failure, deletestateKeybefore the error propagates to the caller. The same delete already exists for the success and DLQ paths (apps/hocuspocus.server/src/lib/queue.ts:518andapps/hocuspocus.server/src/lib/queue.ts:605), so match that shape.Acceptance
store-doc-state:prefix (apps/hocuspocus.server/src/lib/queue.ts:137) survives the failed enqueue.adderror, not a delete error.