From d6a8796928af7026d6bcb520eae8b649e2a7294e Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 25 Apr 2018 21:23:46 -0500 Subject: [PATCH] [close #751] Default MALLOC_ARENA_MAX new apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR will set MALLOC_ARENA_MAX=2 by default for new Ruby apps and for any apps running on the heroku-18 stack (when released). While we currently have [documentation on tuning the memory behavior of glibc by setting this environment variable](https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior) the default does not produce good results for Ruby applications that are using threads: - https://www.mikeperham.com/2018/04/25/taming-rails-memory-bloat/ - https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html In general most Ruby applications are memory bound and by decreasing the memory footprint of the application we can enable scaling out via more workers. Less memory might also mean a cheaper to run application, as it consumes fewer resources. Setting this value is not entirely free. It does come with a performance trade off. For more information, see how we originally benchmarked this setting: - https://devcenter.heroku.com/articles/testing-cedar-14-memory-use If a customer’s application is not memory bound and would prefer slightly faster execution over the decreased memory use, they can set their MALLOC_ARENA_MAX to a higher value. The default as specified by the [linux man page](http://man7.org/linux/man-pages/man3/mallopt.3.html) is 8 times the number of cores on the system. Or they can use the 3rd party [jemalloc buildpack](https://elements.heroku.com/buildpacks/mojodna/heroku-buildpack-jemalloc). Our documentation will be updated to reflect this change once the PR is merged and deployed. --- lib/language_pack/metadata.rb | 2 +- lib/language_pack/ruby.rb | 28 +++++++++++++++++++++++----- spec/hatchet/ruby_spec.rb | 4 ++-- spec/hatchet/upgrade_ruby_spec.rb | 9 +++++++-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/lib/language_pack/metadata.rb b/lib/language_pack/metadata.rb index f6979883c..ea4e91a55 100644 --- a/lib/language_pack/metadata.rb +++ b/lib/language_pack/metadata.rb @@ -13,7 +13,7 @@ def initialize(cache) def read(key) full_key = "#{FOLDER}/#{key}" - File.read(full_key) if exists?(key) + File.read(full_key).chomp if exists?(key) end def exists?(key) diff --git a/lib/language_pack/ruby.rb b/lib/language_pack/ruby.rb index 43d5c9799..42e323e48 100644 --- a/lib/language_pack/ruby.rb +++ b/lib/language_pack/ruby.rb @@ -115,6 +115,27 @@ def compile private + def default_malloc_arena_max? + return true if stack_not_14_not_16? + return true if "true" == @metadata.read("default_malloc_arena_max") + + if new_app? + @metadata.write("default_malloc_arena_max", "true") + return true + end + + return false + end + + def stack_not_14_not_16? + case stack + when "cedar-14", "heroku-16" + return false + else + return true + end + end + def warn_bundler_upgrade old_bundler_version = @metadata.read("bundler_version").chomp if @metadata.exists?("bundler_version") @@ -337,6 +358,7 @@ def setup_profiled set_env_override "GEM_PATH", "$HOME/#{slug_vendor_base}:$GEM_PATH" set_env_override "PATH", profiled_path.join(":") + set_env_default "MALLOC_ARENA_MAX", "2" if default_malloc_arena_max? add_to_profiled set_default_web_concurrency if env("SENSIBLE_DEFAULTS") if ruby_version.jruby? @@ -518,11 +540,7 @@ def load_default_cache # install libyaml into the LP to be referenced for psych compilation # @param [String] tmpdir to store the libyaml files def install_libyaml(dir) - case stack - when "cedar-14", "heroku-16" - else - return - end + return false if stack_not_14_not_16? instrument 'ruby.install_libyaml' do FileUtils.mkdir_p dir diff --git a/spec/hatchet/ruby_spec.rb b/spec/hatchet/ruby_spec.rb index 23fac7fd4..c9d7e2dc8 100644 --- a/spec/hatchet/ruby_spec.rb +++ b/spec/hatchet/ruby_spec.rb @@ -27,8 +27,8 @@ describe "2.5.0" do it "works" do - Hatchet::Runner.new("ruby_25").deploy do - # works + Hatchet::Runner.new("ruby_25").deploy do |app| + # end end end diff --git a/spec/hatchet/upgrade_ruby_spec.rb b/spec/hatchet/upgrade_ruby_spec.rb index c3c117f02..a5d1d48c9 100644 --- a/spec/hatchet/upgrade_ruby_spec.rb +++ b/spec/hatchet/upgrade_ruby_spec.rb @@ -1,10 +1,12 @@ require 'spec_helper' describe "Upgrading ruby apps" do - it "upgrades from 2.0.0 to 2.1.0", stack: :cedar do - app = Hatchet::Runner.new("default_ruby") + it "upgrades from 2.0.0 to 2.1.0" do + app = Hatchet::Runner.new("default_ruby", stack: "heroku-16") app.setup! app.deploy do |app| + # MALLOC_ARENA_MAX is persisted + expect(app.run('echo "MALLOC_ARENA_MAX_is=$MALLOC_ARENA_MAX"')).to match("MALLOC_ARENA_MAX_is=2") `echo "" > Gemfile; echo "" > Gemfile.lock` puts `env BUNDLE_GEMFILE=./Gemfile bundle install`.inspect @@ -14,6 +16,9 @@ expect(app.output).to match("2.4.1") expect(app.run("ruby -v")).to match("2.4.1") expect(app.output).to match("Ruby version change detected") + + # MALLOC_ARENA_MAX is persisted + expect(app.run('echo "MALLOC_ARENA_MAX_is=$MALLOC_ARENA_MAX"')).to match("MALLOC_ARENA_MAX_is=2") end end end