Rails 7’s load_async Doesn’t Play Well with Multi-Tenant Apps

Disclaimer: I can’t speak for all multi-tenant apps but I suspect the vast majority of those which use separate DB schemas will run into the same issue I ran into.

I was excited to try out the new Relation#load_async feature in Rails 7 to speed up some pages on SwingTradeBot. I upgraded the app to Rails 7 a few days ago and added some load_async calls to a few spots where I thought it would help.

All seemed well until earlier today when one of my users told me that he was getting some strange behavior one of my pages. After a quick investigation I discovered that the load_async calls were the culprit.

SwingTradeBot is a multi-tenant app. It has separate PostgreSQL schemas for different stock markets around the world. I use the Apartment gem to manage switching the schema per server request based on the subdomain of the requested URL. So the Australian version of SwingTradeBot at asx.swingtradebot.com switches to the PostgreSQL ‘asx’ schema. The person who contacted me was using the London (LSE) version of the site, which points to the ‘lse’ schema. The problem he was seeing was that stocks from the USA markets (NYSE & NASDAQ) were appearing on the LSE page.

The USA markets is the default tenant / schema and that’s what the load_async calls were using. So the load_async calls aren’t retaining the database schema context set by the Apartment gem.

How to work around this?

My first thought of how to make load_async work with Apartment was to somehow pass the current schema into load_async. Then load_async could switch to the proper schema. (There’s a gem to something similar in order to get Sidekiq to play well with Apartment) But I don’t think such a change would be acceptable to the Rails core team.

My next thought was to do explicitly specify the schema via ActiveRecord’s ‘from’ and/or ‘joins’ clauses. So something like:

Stock.select(:id).from( “#{target_schema}.stocks”)…

I think that could work in certain situations but it would be very messy for some of the queries on which I was using load_async. So for now I’m just going to avoid using load_async.

P.S.

I use quite a bit of raw SQL (gasp!) in my app and was a bit disappointed to discover that load_async does not work with ‘find_by_sql’.