Asynchronous Jobs Backward Compatibility

It is a very common practice to use asynchronous jobs for executing long and time consuming tasks, such as sending emails, batch imports and image resizing. These jobs run in the background and are key to building scalable web apps.

Although background jobs are easily implemented and configured, specially in Rails, there is one aspect I believe it is not very well discussed in the community and people only realize it too late in the development lifecycle. This aspect is backward compatibility.

In the context of background jobs, by backward compatibility I mean: deploying a new version of your application should not break the execution of the existing jobs. Let’s look at a concrete example.

Imagine a simple online store application. Every time a new order is placed, the application needs to process it using an external API (billing service). As mentioned before, this is a good candidate to be implemented as a background job, as seen below.

A new job can be enqueued using the following command:


Now, imagine that the billing service needs to change to accept the order shipping details. This is exemplified in the following code:

The changes are simple and new jobs are enqueued using the command below:

details = { address: '994 5th Avenue', city: 'NY', zipcode: '10021' }
ProcessOrderJob.perform_later(order_id, details)

This works fine! However, after deploying the changes some jobs start failing with the following error:

wrong number of arguments (given 1, expected 2)
./app/jobs/process_order_job.rb:6:in `perform'

This is probably unexpected because this scenario is not easily reproduced during development.

Basically, the code deployed is not backward compatible. In other words, the changes we’ve made broke the execution of existing jobs in the queue. While the jobs created after the deploy respect the new ProcessOrderJob#process signature, the already existing ones don’t, since they do not include the shipping details.

There are some options to avoid this error:

  1. Stop the application, wait until all jobs are processes and deploy the new changes;
  2. Implement backward compatible changes, so old jobs don’t break;

When possible, the second option is recommended since it does not require application downtime. The code below shows the same changes to the billing service in a backward compatible way.

Now, both jobs (old and new) are processed correctly:

details = { address: '994 5th Avenue', city: 'NY', zipcode: '10021' }
ProcessOrderJob.perform_later(order_id, details)

To conclude, background jobs are a very common technique to build scalable web applications. Although its implementation and configuration are fairly easy, maintaining them can be challenging. This blog post showed that changes to the job’s code can potentially break the execution of already existing jobs. To avoid this issue, remember to always introduce backward compatible changes, as shown in the examples above.

Leave a Reply

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s