PREF: optimise preloading application

We preload to ensure as much memory as possible is reused from unicorn master
to various workers using copy-on-write (sidekiq, unicorn)

This migrates the preloading code into the Discourse module for easier
reuse and adds 3 notable preloading changes

1. We attempt to localize a string on each site, ensuring we warmup
the i18n

2. We preload all our templates (compiling .erb to class)

3. We warm-up our search tokenizer which uses cppjieba which is a large
memory consumer, this will only cause a warmup on CJK sites or sites with
the special site setting enabled.
This commit is contained in:
Sam Saffron 2019-10-07 00:33:37 -04:00
parent 71ea4ad7fc
commit 8d5f47dded
4 changed files with 57 additions and 32 deletions

View File

@ -26,6 +26,10 @@ else
gem 'sprockets-rails' gem 'sprockets-rails'
end end
# this will eventually be added to rails,
# allows us to precompile all our templates in the unicorn master
gem 'actionview_precompiler', require: false
gem 'seed-fu' gem 'seed-fu'
gem 'mail', require: false gem 'mail', require: false

View File

@ -20,6 +20,8 @@ GEM
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
actionview_precompiler (0.2.1)
actionview (>= 6.0.a)
active_model_serializers (0.8.4) active_model_serializers (0.8.4)
activemodel (>= 3.0) activemodel (>= 3.0)
activejob (6.0.0) activejob (6.0.0)
@ -428,6 +430,7 @@ DEPENDENCIES
actionmailer (= 6.0.0) actionmailer (= 6.0.0)
actionpack (= 6.0.0) actionpack (= 6.0.0)
actionview (= 6.0.0) actionview (= 6.0.0)
actionview_precompiler
active_model_serializers (~> 0.8.3) active_model_serializers (~> 0.8.3)
activemodel (= 6.0.0) activemodel (= 6.0.0)
activerecord (= 6.0.0) activerecord (= 6.0.0)

View File

@ -53,43 +53,15 @@ initialized = false
before_fork do |server, worker| before_fork do |server, worker|
unless initialized unless initialized
# load up the yaml for the localization bits, in master process Discourse.preload_rails!
I18n.t(:posts)
# load up all models and schema
(ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table|
table.classify.constantize.first rescue nil
end
# ensure we have a full schema cache in case we missed something above
ActiveRecord::Base.connection.data_sources.each do |table|
ActiveRecord::Base.connection.schema_cache.add(table)
end
schema_cache = ActiveRecord::Base.connection.schema_cache
# load up schema cache for all multisite assuming all dbs have
# an identical schema
RailsMultisite::ConnectionManagement.each_connection do
dup_cache = schema_cache.dup
# this line is not really needed, but just in case the
# underlying implementation changes lets give it a shot
dup_cache.connection = nil
ActiveRecord::Base.connection.schema_cache = dup_cache
end
# router warm up
Rails.application.routes.recognize_path('abc') rescue nil
# preload discourse version
Discourse.git_version
Discourse.git_branch
Discourse.full_version
# V8 does not support forking, make sure all contexts are disposed # V8 does not support forking, make sure all contexts are disposed
ObjectSpace.each_object(MiniRacer::Context) { |c| c.dispose } ObjectSpace.each_object(MiniRacer::Context) { |c| c.dispose }
# get rid of rubbish so we don't share it # get rid of rubbish so we don't share it
# longer term we will use compact! here
GC.start
GC.start
GC.start GC.start
initialized = true initialized = true

View File

@ -764,4 +764,50 @@ module Discourse
def self.skip_post_deployment_migrations? def self.skip_post_deployment_migrations?
['1', 'true'].include?(ENV["SKIP_POST_DEPLOYMENT_MIGRATIONS"]&.to_s) ['1', 'true'].include?(ENV["SKIP_POST_DEPLOYMENT_MIGRATIONS"]&.to_s)
end end
# this is used to preload as much stuff as possible prior to forking
# in turn this can conserve large amounts of memory on forking servers
def self.preload_rails!
return if @preloaded_rails
# load up all models and schema
(ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table|
table.classify.constantize.first rescue nil
end
# ensure we have a full schema cache in case we missed something above
ActiveRecord::Base.connection.data_sources.each do |table|
ActiveRecord::Base.connection.schema_cache.add(table)
end
schema_cache = ActiveRecord::Base.connection.schema_cache
# load up schema cache for all multisite assuming all dbs have
# an identical schema
RailsMultisite::ConnectionManagement.each_connection do
dup_cache = schema_cache.dup
# this line is not really needed, but just in case the
# underlying implementation changes lets give it a shot
dup_cache.connection = nil
ActiveRecord::Base.connection.schema_cache = dup_cache
I18n.t(:posts)
# this will force Cppjieba to preload if any site has it
# enabled allowing it to be reused between all child processes
Search.prepare_data("test")
end
# router warm up
Rails.application.routes.recognize_path('abc') rescue nil
# preload discourse version
Discourse.git_version
Discourse.git_branch
Discourse.full_version
require 'actionview_precompiler'
ActionviewPrecompiler.precompile
ensure
@preloaded_rails = true
end
end end