diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e645edf7..196124535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## v205 (unreleased) +* Default `MALLOC_ARENA_MAX=2` for new applications (https://github.com/heroku/heroku-buildpack-ruby/pull/752) + ## v204 (9/12/2019) * Default Ruby version for new apps is now 2.5.6 (https://github.com/heroku/heroku-buildpack-ruby/pull/919) diff --git a/changelogs/v205/malloc.md b/changelogs/v205/malloc.md new file mode 100644 index 000000000..b8cd03e8e --- /dev/null +++ b/changelogs/v205/malloc.md @@ -0,0 +1,32 @@ +## Default `MALLOC_ARENA_MAX=2` for new Ruby applications + +The environment variable `MALLOC_ARENA_MAX` will now default to `2` for Ruby applications. This environment variable was +previously unset. This change will only affect new applications on the platform, to update an existing application please +run: + +``` +$ heroku config:set MALLOC_ARENA_MAX=2 +``` + +The goal of setting this value is to decrease memory usage for the majority of Ruby applications that are using threads +such as apps that use Sidekiq or Puma. To understand more about the relationship between this value and memory please see +the following external resources: + +- https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html +- https://www.mikeperham.com/2018/04/25/taming-rails-memory-bloat/ + +We also maintain our own [documentation on tuning the memory behavior of glibc by setting this environment variable](https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior). + +If a your application is not memory bound and would prefer slightly faster execution over the decreased memory use, +you 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. + +## Jemalloc + +Another popular alternative memory allocator is jemalloc. At this time Heroku does not maintain a supported version of this memory allocator, +but you can use it in your application with a 3rd party [jemalloc buildpack](https://elements.heroku.com/buildpacks/mojodna/heroku-buildpack-jemalloc). + +If you are using jemalloc, setting `MALLOC_ARENA_MAX` will have no impact on memory or performance. For more information on +how jemalloc interacts with Ruby applications see this external post: + +- https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html#fix-2-use-jemalloc diff --git a/lib/language_pack/metadata.rb b/lib/language_pack/metadata.rb index f6979883c..4cc1d33e1 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) @@ -27,6 +27,12 @@ def write(key, value, isave = true) full_key = "#{FOLDER}/#{key}" File.open(full_key, 'w') {|f| f.puts value } save if isave + + return true + end + + def touch(key) + write(key, "true") end def save diff --git a/lib/language_pack/ruby.rb b/lib/language_pack/ruby.rb index ee72e3c50..8de8513cc 100644 --- a/lib/language_pack/ruby.rb +++ b/lib/language_pack/ruby.rb @@ -121,6 +121,22 @@ def config_detect private + def default_malloc_arena_max? + return true if @metadata.exists?("default_malloc_arena_max") + return @metadata.touch("default_malloc_arena_max") if new_app? + + 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") @@ -352,6 +368,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? @@ -568,11 +585,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/rubies_spec.rb b/spec/hatchet/rubies_spec.rb index 283d97138..0cfa68755 100644 --- a/spec/hatchet/rubies_spec.rb +++ b/spec/hatchet/rubies_spec.rb @@ -85,6 +85,8 @@ app = Hatchet::Runner.new("default_ruby", stack: DEFAULT_STACK) app.setup! app.deploy do |app| + expect(app.run('echo "MALLOC_ARENA_MAX_is=$MALLOC_ARENA_MAX"')).to match("MALLOC_ARENA_MAX_is=2") + run!(%Q{echo "ruby '2.5.1'" >> Gemfile}) run!("git add -A; git commit -m update-ruby") app.push! @@ -93,4 +95,4 @@ expect(app.output).to match("Ruby version change detected") end end -end \ No newline at end of file +end