Steady Queue: Porting Solid Queue to Django
by Elias Hernandis • Published Dec. 27, 2025 • Tagged django, news, python
A little more than a year ago I resumed using Ruby on Rails professionally after having been out of the ecosystem for several years. The last Rails version I used was Rails 4, and coming back to Rails 8 it was impressive to see how far things had come, especially in terms of the increasing number of tools included by default in the framework. While I was away from Rails, I mostly worked with Django in Python. Although Django is architecturally similar to Rails and also a joy to work with, it provides fewer tools out of the box.
One of the tools that most web apps need is asynchronous task processing. Rails has had a standard interface for defining asynchronous tasks for a few versions already with ActiveJob, but it hadn't shipped with a default backend. Last year, Rosa Gutierrez introduced Solid Queue, which became the default job processing backend in Rails 8. Following the omakase1 Rails philosophy, it leverages SELECT FOR UPDATE SKIP LOCKED to be able to use a relational database as the concurrency coordination mechanism, eliminating the need to deploy a separate Redis instance.
Django also has excellent options2 for async task processing, but, up until very recently, the framework didn't provide a standard interface for how tasks should be defined and the capabilities backends should have. This changed with the adoption of DEP0014 and its corresponding integration into Django 6.0 a few days ago. Jake Howard was the main contributor to both the DEP and django-tasks, the reference implementation. The database backend available in django-tasks uses the same core idea from Solid Queue: leveraging SELECT FOR UPDATE SKIP LOCKED to use a database as the coordinator for concurrency.
I had been wanting to dive deeper into the internals of Solid Queue for a while and having a task interface for Django on track to become part of the framework was the motivation I needed to attempt a port of Solid Queue to Django. Then, Steady Queue was born in mid-August 2025.
A quick tour of Steady Queue
Steady Queue is a database-backed task backend for Django (>= 6.0). Beyond the features already offered by the django-tasks database backend, it also supports:
- Recurring tasks (cron-like scheduling)
- Concurrency controls (limiting how many instances of a task can run at a time)
- Queue management (pausing queues and ordering queues by priority)
- Database isolation3, enhanced task argument serialization and a full Django admin interface.
After installing it (it has no dependencies), it can start processing tasks defined with the standard interface available in Django:
from django.tasks import task
@task(priority=10, queue_name='real_time')
def welcome_user(user: User):
send_email(to=user.email, subject='Welcome to Django!')
Steady Queue extends the decorator-style interface to support limiting how many instances of a task can run at the same time and recurring tasks:
from steady_queue.concurrency import limits_concurrency
from steady_queue.recurring_task import recurring
@limits_concurrency(key='email rate limiting', to=2)
@task()
def send_daily_digest(user: User):
send_email(to=user.email, subject='Your daily digest')
@recurring(schedule='0 12 * * *', key='send daily digest at noon')
@task()
def daily_digest_at_noon():
for user in User.objects.all():
send_daily_digest.enqueue(user)
Configuration is entirely optional, but it supports running multiple workers (and dispatchers and schedulers, depending on your load), prioritizing queues and designating workers to only run tasks from specific queues. Since no Redis is required, all that's needed to start processing tasks is running in your development or production machines
python manage.py steady_queue
We've been using Steady Queue in some production services with light load for a while now, and being able to ditch the Redis deployment was a joy. I'm very happy to take feedback and/or contributions from the community.