[BUG] Bulk Inbox/Account deletion starves live WhatsApp/inbound webhooks due to Sidekiq queue priority inversion
Description
When an administrator deletes an Inbox (or Account) with a large number of conversations (e.g., 5,000–10,000+), incoming live WhatsApp customer messages (Webhooks::WhatsappEventsJob) and other webhooks assigned to :low are completely starved and blocked from processing for 30–60+ minutes.
Live customer communication is halted because background deletion jobs monopolize worker threads.
Root Cause Analysis
Queue Assignment Inconsistency:
- In
app/jobs/webhooks/whatsapp_events_job.rb, inbound webhooks are queued as:low:class Webhooks::WhatsappEventsJob < MutexApplicationJob queue_as :low - In contrast, other channel webhook handlers (e.g.,
facebook_events_job.rb,instagram_events_job.rb,telegram_events_job.rb) usequeue_as :default. - The deletion initiator
DeleteObjectJobis also queued asqueue_as :low.
- In
Async Cascade Fan-Out into Higher-Priority Queues:
- When
DeleteObjectJobruns,purge_heavy_associationsinvokesbatch.each(&:destroy!)on all conversations in the inbox. - Each
Conversation#destroy!triggers Rails ActiveRecord asynchronous associations:has_many :messages, dependent: :destroy_async-> enqueuesActiveRecord::DestroyAssociationAsyncJobinto:default.- Associated attachments -> enqueue
ActiveStorage::PurgeJobinto:default. EventDispatcherJob(conversation.deleted) -> enqueues into:critical.
- An inbox with ~7,500 conversations generates 10,000+ jobs in
:defaultand hundreds in:critical.
- When
Strict Sidekiq Queue Priority Ordering:
- In
config/sidekiq.yml, queues are defined without weights::queues: - critical - high - medium - default - mailers - action_mailbox_routing - low - In Sidekiq, unweighted queues enforce strict top-to-bottom priority.
- Sidekiq worker threads will never pull a single job from
:lowas long as:defaulthas jobs waiting. - As a result, all incoming WhatsApp webhooks arriving in
:lowsit backlogged for 30–60+ minutes until every single deleted message and attachment is cleaned up.
- In
Steps to Reproduce
- Have an inbox with ~5,000 to 10,000 conversations.
- In Chatwoot UI, delete the inbox.
- Check Redis queue lengths (
redis-cli llen queue:default) — notice it immediately jumps to 10,000+ jobs. - Send an incoming WhatsApp message to another active inbox.
- The message webhook is accepted by the Rails web server (
200 OK) and enqueued toqueue:low. - Inspect Sidekiq worker: all worker threads are 100% occupied processing
queue:default(ActiveRecord::DestroyAssociationAsyncJob). - The inbound WhatsApp message does not appear in the dashboard until all deletion jobs in
queue:defaulthave finished.
Environment
- Chatwoot version: v4.x / latest (
develop) - Sidekiq: 7.3.x
- OS: Linux (Ubuntu 22.04 LTS)
Proposed Solutions
Prioritize Real-Time Inbound Webhooks: Inbound customer communication (
Webhooks::WhatsappEventsJob,Webhooks::TwilioEventsJob) is real-time and business-critical. It should be assigned to:high(matchingSendReplyJob) or at least:defaultto be consistent withFacebookEventsJobandTelegramEventsJob.Route Bulk Deletion Cascades to Lower/Housekeeping Queues:
config/sidekiq.ymlalready defines queues such aspurgableandhousekeepingat the bottom. Bulk deletion jobs (ActiveRecord::DestroyAssociationAsyncJobandActiveStorage::PurgeJobtriggered during bulk object deletion) should not pollute:default.Consider Queue Weights in Default Sidekiq Config: Adding weights in
config/sidekiq.yml(e.g.,['high', 5], ['default', 3], ['low', 2]) prevents total starvation of lower queues during high-volume spikes in higher queues.
Source: chatwoot/chatwoot