At Drivy, we use a lot of background jobs, called from service objects, API calls, cron, etc.
A time came when we needed to add some context data across several of these code layers.
For instance, we have some context data we need to keep for auditing reasons. This data can originate from several points in the application: maybe from some part of the web application, from the mobile app, or from a service object.
We tried to find a way to keep this new context data through all code layers and jobs without having to resort to adding context data arguments everywhere.
We decided to use Thread.current
objects to host this data for the current process.
CAVEAT: Using this kind of global data in this way is usually considered to be bad practice. I will not discuss it here, but you can look at this discussion for more detail.
We use global data with caution, in a limited scope and only after having really thought about it. All interactions with the global data is tightly contained in service objects to limit the risk of using the data outside of its intended scope.
It works well, up to the point where we delegate some of this processing to background jobs. The jobs run on a different thread (even on a different machine).
We use Sidekiq to manage our jobs. Sidekiq works in the following way (a simplified version):
Conveniently, Sidekiq provides a way to add some code around job processing, on the client side, the server side or both. So we used these middlewares to propagate the context information from the client (our Rails application) to the Sidekiq server.
The Sidekiq middleware client API is:
And you add it to Sidekiq configuration in this way:
Note: You may want to add this client middleware to the server middleware pipe, see below
In our case, we want to enrich the job with some metadata. Sidekiq allows the adding of information to the job that will be available on the server side:
We only need the job argument here. It’s basically a regular Hash. We just add here our own information (be careful to store only data that will be serialised in JSON).
The Sidekiq middleware server API is:
And you add it to Sidekiq configuration in this way:
In our usage, we need to retrieve the metadata from the job and set it in the current process:
We simply restore the data from the serialised version.
Each middleware is executed in the same thread as the main job process, so we know the context data will be available to the Ruby job.
Sidekiq will reuse threads for different jobs in some cases, so we must be very careful to cleanup our ProcessContext to ensure we do not pollute the context of other jobs.
Sometimes, jobs running on the server can enqueue jobs, and act as a client. In this case, you’ll want to add the client middleware to the server configuration as well:
Middlewares are a useful tool, we use them for logging, and monitoring mainly. You can find some interesting plugins using middleware on the Sidekiq Wiki.
And again, do not use global states if you can avoid it.