rails: ActiveRecord schema info is not preloaded in production on first request

I know this isn’t stack overflow (great way to open an issue, eh?)… but I’ve been bashing my head at this one for awhile and think it might be an issue in rails.

I’m seeing these kind of queries being issued for each running process’ first request:

screen shot 2016-03-10 at 12 09 42 am

I’m doing vanilla ActiveRecord::Base.find stuff. Both of these options are set:

  config.cache_classes = true
  config.eager_load = true

On production.

Puma configuration:

workers ENV.fetch('WEB_CONCURRENCY') { 2 }.to_i
threads_count = ENV.fetch('MAX_THREADS') { 5 }.to_i
threads threads_count, threads_count

# DB_POOL should be equal to max threads

preload_app!

rackup      DefaultRackup
port        ENV.fetch('PORT') { 3000 }
environment ENV.fetch('RACK_ENV') { 'development' }

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end
after_worker_boot do

end
before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

Steps to reproduce

  1. boot server
  2. Issue first request
  3. Inspect rack-mini-profiler stats

Expected behavior

The 50+ sql queries are not executed. The app preloads all that information on boot.

Actual behavior

Every process running (for me its 16) executes 50+ sql schema queries on its first request. So for me, I have 5 servers with 16 processes each, it takes 80 requests before my response times drop from 1 second + to 100-200ms.

System configuration

Rails version: 4.2.6

Ruby version: 2.3.0

Puma version: 3.1.0

pg version: 0.18.4

Heroku

Potentially relevant gems: identity-cache

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 15 (7 by maintainers)

Most upvoted comments

Just an FYI to people out there looking for the same solution, I never did end up getting this to work.

So to clarify, the production environment (or any environment for that matter) does not build this by default on boot if its not present?

No it doesn’t, and yeah it only cache for each model you check.

I never tried this workaround but you could use a after_initialize block with this code:

ActiveSupport.on_load(:active_record) do
  con = ActiveRecord::Base.connection
  con.schema_cache.clear!
  con.tables.each { |table| con.schema_cache.add(table) }
end

In Rails 4 you would also need to backport https://github.com/rails/rails/pull/17632 and https://github.com/rails/rails/pull/20175 to all connections use the cache.

I’d love to help but I’m nowhere near competent enough to contribute to rails core 😃

You would be surprised that there is no such thing like not being competent to contribute to Rails.

So to clarify, the production environment (or any environment for that matter) does not build this by default on boot if its not present? I definitely think it should, it’s adding 1s+ plus my requests. And from what I’m seeing, its not even fully loaded on the first request - it only caches the models that get hit. So if I go to a different page (2nd request) that has other models, I see these queries as well. (I should have mentioned that)

Is there some workaround I could do in the meantime, given my constraints with Heroku? Can I trigger this rake task somewhere in code?

I’d love to help but I’m nowhere near competent enough to contribute to rails core 😃