Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Puma in Production

Puma in Production

Thread safety and configuring Puma to run in Production on Heroku.

Nathan Youngman

April 15, 2014
Tweet

More Decks by Nathan Youngman

Other Decks in Programming

Transcript

  1. Unicorn 256 MB 256 MB 256 MB 256 MB 1

    GB Unicorn Worker Killer
  2. 333 MB 333 MB 333 MB 1 GB Ruby 2.1

    Merry Christmas • Faster Garbage Collector • Higher Memory Footprint Yes No Return to Ruby 2.0?
  3. Rails on Threads • Each request creates its own instance

    of the controller stack • Your Rails app is probably already thread-safe • If not, it’s very, very bad request a thread 1 request b thread 2 request c thread 3
  4. Unsafe class Time
 def self.date_format=(new_format)
 @date_format = new_format
 Thread.current[:date_format] =

    new_format
 end
 # etc.
 end def file_for_paperclip(filename, content, content_type)
 file = StringIO.new(content)
 metaclass = class << file; self; end
 metaclass.class_eval do
 define_method(:original_filename) { filename }
 define_method(:content_type) { content_type }
 end
 file
 end
  5. Dependencies # This method is not thread-safe.
 def quietly
 #

    ...
 end
 
 # This method is not thread-safe.
 def silence_stream(stream)
 # ...
 end • Some code in Rails is not thread-safe • Search all your gems: > ack "\.quietly" `bundle show --paths`
  6. Puma Config web: bundle exec puma -p $PORT -e ${RACK_ENV:-development}

    -C config/puma.rb min_threads = Integer(ENV['PUMA_MIN_THREADS'] || 6)
 max_threads = Integer(ENV['PUMA_MAX_THREADS'] || 6)
 
 threads min_threads, max_threads
 ! # only if using workers:
 preload_app!
 workers Integer(ENV['PUMA_WORKERS'] || 0)
 
 on_worker_boot do
 # database connection
 end Procfile puma.rb
  7. Error Handler A really lowlevel plumbing error occured. Please contact

    your local Maytag(tm) repair man. lowlevel_error_handler do
 [302, {'Content-Type' => 'text', 'Location' => 'http:// jobber.s3.amazonaws.com/error_pages/error.html'}, ['302 found']]
 end
 puma.rb
  8. Database Connection Rails.application.config.after_initialize do
 ActiveRecord::Base.connection_pool.disconnect!
 
 ActiveSupport.on_load(:active_record) do
 config =

    Rails.application.config.database_configuration[Rails.env]
 if $rails_rake_task
 config['pool'] = 1
 else
 config['pool'] = ENV['DB_POOL'] || 6 config['reaping_frequency'] = ENV['DB_REAP_FREQ'] || 10
 end
 ActiveRecord::Base.establish_connection(config)
 end
 end

  9. PG::OutOfMemory • Postgres uses 10MB per connection • Dynos *

    DB_POOL + Workers + Cron Jobs • Solution was to upgrade our DB
  10. The Benefits • 6 threads vs. 3 processes: roughly half

    the Dynos • less Dynos reduces cost and queuing • memory headroom (1GB) • we can visualize & track down memory bloat • not constantly restarting processes
  11. Future • MRI threads+workers with puma_worker_killer • or Rubinius/JRuby for

    real threads • PX Dynos (6GB / 8-core) • Threaded background workers (Sidekiq) Sounds interesting? We’re hiring: http://getjobber.com/jobs