Anyone that has worked with Rails for any significant amount of time probably knows that Rails performance tends to be tightly coupled to database query performance. For this reason, it’s extremely valuable to make friends with your local slow query log, where you can identify the queries that are taking the longest and in need of tuning. So, once you’ve identified your slow query(ies), it’s time to get tuning, right? Not so fast. First you need to find out where in the code this query is being generated. If you’re doing things the Rails way and using ActiveRecord to generate these queries, that can be pretty tough since you can’t just grep the code to find that query.
Enter Query Log Tags
Rails 7 added an amazing new feature, Query Log Tags. This feature, when turned on (it’s off by default), will add a comment to the end of every query indicating some context to help you track down where that query came from.
-- Before SELECT * FROM my_table_with_bajillions_of_records; -- After SELECT * FROM my_table_with_bajillions_of_records; /*application:MyApp,controller:my_controller,action:my_action*/
This can save SO much time when trying to figure out what is generating a particularly slow query. To turn it on, you just need to add the following to your
config.active_record.query_log_tags_enabled = true
As usual Rails comes with sane defaults out of the box. So you’ll get the following context on every query automatically:
- Application Name
- Controller Name
- Controller Action
- ActiveJob Class
For most apps, this is all you need. However, we noticed right away that some of our nastiest queries were including only the application name in the comment, like this:
SELECT * FROM my_table_with_bajillions_of_records; /*application:MyApp*/
Great, appreciate that Rails. Pretty sure I already knew the query was coming from my app, but thanks anyway.
So what was the problem? Why weren't we getting more context on these queries?
We’re big fans of Sidekiq here at BetterUp. In fact, we love it so much that we use a lot of the Enterprise and Pro features. Now, in order to use some of the more advanced features (like Bulk Queueing) you need to use the
Sidekiq::Job API directly, rather than through the Rails ActiveJob abstraction. This is all well and good, but you can probably see where this is going: since we’re not using ActiveJob, we don’t get the job name automatically in the query log tags.
So, how do we work around this?
ActiveSupport::ExecutionContext to the rescue! It turns out that we can put anything we want in the execution context and then configure query log tags to include that thing in our queries. So we whipped up a really simple Sidekiq Middleware that looks like this:
module SidekiqMiddleware class ActiveSupportExecutionContext def call(_worker_class, job, _queue, &) # If `wrapped` is set, this is an `ActiveJob` which is already in the execution context sidekiq_job_class = job['wrapped'].present? ? nil : job['class'].to_s ActiveSupport::ExecutionContext.set(sidekiq_job_class: sidekiq_job_class, &) end end end
Then we add the following to our
config.active_record.query_log_tags << :sidekiq_job_class
And tada! Now we have our Sidekiq Job Class Name in our queries! 🎉🎉🎉
SELECT * FROM my_table_with_bajillions_of_records; /*application:MyApp,sidekiq_job_class:MySidekiqJob*/
About the Author
Brian joined BetterUp in 2017 as a Senior Full Stack Engineer. He lives in the Minneapolis area with his spouse and two sons. Brian is a lover of family time, hiking, biking, video games, and serial optimization.