ActivityPub Delivery Queue#
Misskey uses BullMQ (backed by Redis) for all asynchronous job processing. ActivityPub federation delivery runs through two key independent queues:
deliverqueue — sends outbound ActivityPub activities (notes, deletes, follows, etc.) to remote instance inboxes via signed HTTP POST.dbqueue — handles database-heavy background work including account deletion, data exports/imports, and more.
These queues run concurrently with no ordering guarantees between them, which is the root of several known race conditions.
Queue Configuration#
Workers are initialized in QueueProcessorService.
| Queue | Default Concurrency | Default Rate Limit |
|---|---|---|
deliver | 128 (configurable via deliverJobConcurrency) | 128/sec (configurable via deliverJobPerSec) |
inbox | 16 (configurable via inboxJobConcurrency) | 32/sec (configurable via inboxJobPerSec) |
db | default BullMQ | — |
Both deliver and inbox workers use a custom exponential backoff strategy (httpRelatedBackoff) with a 1-minute base delay and an 8-hour maximum .
Deliver jobs are enqueued via QueueService.deliver() (single) or QueueService.deliverMany() (bulk). Each job is configured with up to 12 retry attempts (configurable via deliverJobMaxAttempts) and removed from the queue after 7 days or 30 completed / 100 failed jobs .
Deliver Job Processing#
DeliverProcessorService.process() handles each outbound delivery:
- Pre-flight checks — skips delivery if the target URL is federation-blocked or the host is suspended .
- HTTP POST — calls
ApRequestService.signedPost()with the actor's keypair . - Success path — updates
isNotRespondingstate, triggers metadata refresh, and updates charts . - Failure path (HTTP errors) — marks the host as
isNotResponding. After 7 consecutive days of failures, auto-suspends the instance. For 4xx non-retryable errors, throwsBull.UnrecoverableErrorto permanently discard the job; HTTP 410 on a shared inbox triggersgoneSuspendedstate . 5xx errors and network failures re-throw and trigger BullMQ's backoff retry .
Known Issue: Race Condition on Account Deletion#
Status: Open bug as of 2026.6.0
Root Cause#
When DeleteAccountService.deleteAccount() is called for a local user, it:
- Enqueues ActivityPub
Deletedelivery jobs for all known shared inboxes (into the deliver queue) . - Immediately after, enqueues a
deleteAccountjob (into the db queue) .
Because these are independent queues, the db queue's DeleteAccountProcessorService can physically delete the user row — cascade-deleting MiUserKeypair — before the deliver queue has finished sending all the Delete activities .
When any still-pending deliver job executes after the user row is gone, UserKeypairService.getUserKeypair() calls findOneByOrFail() , which throws EntityNotFoundError.
Why Jobs Accumulate#
The catch block in DeliverProcessorService only handles StatusError (HTTP errors) and network errors . An EntityNotFoundError from the database is not caught as unrecoverable — it falls through as a generic error, causing BullMQ to retry the job with the full exponential backoff (up to 8 hours), repeating until max attempts are exhausted. The result is a growing backlog of stuck jobs in the Delay state of the deliver queue.
Observed Symptom#
Could not find any entity of type "MiUserKeypair" matching: { "userId": "..." }
Jobs accumulate in the deliver queue's Delay section and do not drain .
Proposed Mitigations#
Three approaches are discussed in issue #17760:
| Approach | Description |
|---|---|
| Guard in DeliverProcessorService | Check user/keypair existence at job start; if missing, throw UnrecoverableError to immediately discard the job |
Catch EntityNotFoundError | Add a handler in the deliver processor catch block that discards rather than retries on DB lookup failures |
| Drain before deleting | Defer physical user deletion in DeleteAccountProcessorService until in-flight deliver jobs for that user have cleared |
Key Source Files#
| File | Role |
|---|---|
DeliverProcessorService.ts | Executes individual deliver jobs; contains error handling and skip logic |
QueueService.ts | Enqueues deliver/db/inbox/relationship jobs; manages queue admin operations |
QueueProcessorService.ts | Initializes all BullMQ workers; defines concurrency, rate limits, and backoff |
DeleteAccountService.ts | Triggers the deliver + db queue sequence on account deletion |
DeleteAccountProcessorService.ts | db-queue job that physically deletes the user row (cascade-deletes keypair) |
UserKeypairService.ts | Fetches/caches keypairs; findOneByOrFail is the throw site on missing users |