A queue that grows faster than it drains is a queue that's failing at its job, quietly. If you're enqueuing 10,000 items a day but your worker only clears 5,000, the backlog compounds every single day until something breaks — a full disk, an ancient item processed so late it's no longer relevant. Throughput tuning is what keeps a queue actually caught up.
What you'll learn in this lesson
- How to measure whether a queue is actually keeping up
- The real levers for increasing throughput, and their trade-offs
- Why some queue items are inherently slower and need separate handling
Measuring backlog first
# Real, current backlog size for a specific queue:
drush php:eval "echo \Drupal::queue('mymodule_email_queue')->numberOfItems();"
Track this number over time (even a simple cron-logged value works) before tuning anything. A queue with a stable or shrinking count is keeping up; one that climbs steadily is the actual problem to solve, and the number tells you roughly how much extra throughput you need, not just that "it feels slow."
Lever 1: raise the per-run time budget
function mymodule_cron() {
$queue = \Drupal::queue('mymodule_email_queue');
$end = time() + 45; // Was 15 in the previous lesson — more time per run.
while (time() < $end && $item = $queue->claimItem()) {
// ... process ...
}
}
hook_cron() queued to run afterward in the same request. This is the same interaction you saw in the previous lesson — raise it too far and you've just moved the bottleneck to "cron itself now takes too long," rather than actually fixing throughput.Lever 2: run the queue outside cron entirely
# A dedicated, more frequent schedule just for this one queue.
*/2 * * * * cd /var/www/example.com && /usr/local/bin/drush queue:run mymodule_email_queue --time-limit=90
Decoupling a specific queue's processing from the general cron cycle lets you give it its own schedule and time budget without affecting every other module's cron work — the standard fix once a single queue's needs genuinely diverge from "whatever cron happens to allow."
Lever 3: separate fast items from slow ones
If one queue mixes fast items (a simple database write) with slow ones (an external API call with real network latency), the slow items dominate the time budget and starve the fast ones behind them in the same run. Splitting into two separate queues — mymodule_fast_queue and mymodule_slow_queue, each with its own worker and schedule — lets fast items drain quickly regardless of how backed up the slow queue gets.
Quick check: a queue mixes quick database writes with slow third-party API calls, and the fast items are getting delayed behind the slow ones. What's the most targeted fix? If you said split them into two separate queues rather than just raising the time budget, you've picked the fix that actually addresses the root cause instead of papering over it with more time.
Key takeaways
- Measure actual backlog size (
numberOfItems()) over time before assuming a queue needs tuning at all. - Raising the per-run time budget helps throughput but delays every other module's cron work in the same run — a real trade-off, not a free lever.
- A dedicated
drush queue:runschedule decouples one queue's processing from the general cron cycle entirely. - Split mixed fast/slow workloads into separate queues so slow items never block fast ones behind them.
Coming up next
You've now covered the full cron-and-queue performance toolkit. Last in this section: scheduling genuinely lightweight cron tasks that don't need the full queue machinery at all.