A quick introduction to Redis Queue (RQ) to process long-running jobs
When building an application, there are times when you need to submit a long-running job to the backend and compute the results before returning it back to the front end. Some examples include image processing tasks, video uploads, and sending bulk emails to the recipients. From the client's perspective, these are very annoying when you have to look at the endless spinning spinners to wait for the requests to be completed, and eventually, see the requests timed out in the end.
There are multiple ways of approaching this problem, including my previous post on using GCP Cloud Tasks to manage and schedule jobs. Celery is another popular library that can be used to manage background tasks in Python applications. If you are looking at simpler and more intuitive APIs, Redis Queue (RQ) will be a good choice to perform the task.
Redis Queue (RQ)
RQ is built on top of Redis, a popular in-memory data store that provides fast, reliable storage for your data. RQ provides a messaging queue that allows you to store and retrieve messages in a timely manner, making it ideal for processing background jobs. By using a separate queue to store and manage the background tasks, this helps in separating the areas of concern, i.e. the main application and the functions that process the background jobs.
In the remaining of this article, I will show a quick and simple example of how we could enqueue a job using RQ, process the job with another function, and retrieve the status of the job.
Main components of RQ
- Queue — A system that is used to accept jobs or Python functions to be invoked asynchronously by the workers
- Worker — A worker is a Python process that typically runs in the background and exists solely as a workhorse to perform lengthy or blocking tasks that you don’t want to perform inside web processes.
- Jobs — A job is a Python object, representing a function that is invoked asynchronously in a worker (background) process.
Prerequisites
To get started with RQ, you need to have a Redis server (≥3.0.0) running. You can either install Redis locally or use a cloud-based Redis instance. If you prefer to use Docker like me, you can run the following command:
docker run -d -p 6379:6379 redis
This will pull the latest Redis docker image and runs Redis in daemon mode with port forwarding on the default port (6379).
Once you have Redis running, you can install the RQ library using pip:
pip install rq
Create a job/task function
This can be any Python function that you would like to execute in the background. In this example, we use time.sleep to simulate a long-running job and use the random module to create arbitrary wait times and results.
# jobs.py
import time
import random
def long_running_jobs():
jobs_running_time = random.randint(5, 10)
time.sleep(jobs_running_time)
return f'Jobs finished. Total run time: {jobs_running_time}'Initializing a queue
# main.py
from rq import Queue
from redis import Redis
redis_conn = Redis()
q = Queue(connection=redis_conn)This uses the default Redis configuration (eg: port 6379, localhost) to establish the connection to the Redis server, and create a default queue. We can also create multiple queues and name them differently, this is particularly useful when you have different types of jobs or priorities that you want to separate the logic with, eg:
high_priority_queue = Queue('high', connection=redis_conn)
low_priority_queue = Queue('low', connection=redis_conn)Enqueue job in the queue
from jobs import long_running_jobs
job = q.enqueue(long_running_jobs) # enqueue a long running job into the default queueStart a worker
You can start the worker via CLI from the root of the project or a Python worker script, eg:
rq worker
You can also specify one or multiple queues in the command so that the workers can read jobs from different queues, for example in order to listen to the high and default queue:
rq worker high default
Each worker will process a single job at a time. If you want to perform jobs concurrently, we can simply start more workers from the CLI. This is where process managers like Supervisor will be very useful to manage multiple worker processes and help to restart the processes automatically in the case where errors or crashes happened.
Get status and results of the job
Once a worker is started, it will read from the the queue and start processing the jobs. You can access the job’s status via .get_status() or check if the job has finished via .is_finished from the Python job object.
while not job.is_finished:
print('Job not finished yet, wait for 1s')
time.sleep(1)
print(job.result)
# Output:
# Job not finished yet, wait for 1s
# Job not finished yet, wait for 1s
# Job not finished yet, wait for 1s
# Job not finished yet, wait for 1s
# Job not finished yet, wait for 1s
# Job not finished yet, wait for 1s
# Jobs finished. Total run time: 6Conclusion
This article is meant to give a very high-level overview of Python RQ, which is a handy library for long-running processing tasks in your Python applications. It provides a simple and efficient way to queue and manage jobs and allows you to run tasks in the background while your application continues to work.
I will post a follow-up article on how we could take this concept into a dockerized web app where we can submit and get the status of the job via an API call. Thanks for reading!






