Open stephanohainz opened 1 year ago
I am seeing very similar behavior in our APM traces. One PUMA request thread, connect via ROS Apartment, query for models and resulting in dozens to hundreds of SELECT DATABASE()
calls. Our configuration:
It amounts to adding dozens to hundreds of quick DB round trips to our app. Each statement on its own might only take milliseconds, but lots of them add up.
This heavily redacted APM trace shows it happening. All those purple slices at the bottom of the flame graph are the DB dips. This is one PUMA transaction through some Rails controllers. The whole operation took ~63 seconds (hence why we were looking at it). All those tiny DB dips are diminishing performance.
Update here for other parties reading along. We have found root cause inside our application and are taking steps to fix it. The root cause is:
Apartment::Tenant.current()
results in a MySQL SELECT DATABASE();
query.The Apartment gem is not doing any kind of thread-local, puma-worker-local, or sidekiq-worker local caching or memo-ization of this value. Every time .current
is invoked it's a network traversal and round trip to the DB engine.
Our application code assumed that was a cheap, local in-RAM call. Ooops on our part, and a bad assumption.
The solution we are deploying is application specific but amounts to replacing every call to Apartment::Tenant.current()
with a call to our own code that memoizes the current tenant in a thread local variable. We've replaced literally dozens of .current
calls with calls to our new utility class.
If you're having this issue then look at your app. How often are you invoking .current
? Do you need to do it that often? Can you memo-ize the returned value? Our solution is proprietary and intricate, but boils down to something like this:
module CustomApartmentUtils
class CurrentTenant
def current
Thread.current[:apartment_current_tenant] ||= Apartment::Tenant.current
end
end
end
Every time you switch!
your tenant you need to populate the thread local variable Thread.current[:apartment_current_tenant]
with your tenant details. And then replace all calls in your app with CustomApartmentUtils::CurrentTenant::current
.
Good luck!
We are seeing a lot of
SELECT database()
being shown at the MySQL logs.Steps to reproduce
Configuration
Using the "Switching Tenants per request" configuration proposed on documentation.
Run a HTTP request to a specific tenant. In our case, we are just doing a generic HTTP request to retrieve some data and doing some basic validations in the model.
Expected behavior
Make a single tenant change per request.
Actual behavior
I came to realize that there has been a very large consumption of the query database "SELECT database()" in a multi-tenant environment, where an HTTP request is made at a generic endpoint and this request triggers this large number of selects.
MySQL Logs:
Possible fix
Analyzing and comparing the PostgreSQL adapter with mysql2, it is seen that the PostgreSQL adapter contains a way to set the current tenant, in which it is not necessary multiple selects database()
When adapting the same function in the mysql2 adapter, is noticed a reduction in
SELECT database()
calls:System configuration
Ruby version