I've done some stuff with routes, but I feel there are other wins we could get. The good news is we have `ObjectSpace.memsize_of_all`:
irb(main):001:0> require 'objspace'
=> true
irb(main):002:0> ObjectSpace.memsize_of_all(String)
=> 169318
irb(main):003:0> x = 'x' * 1024; nil
=> nil
irb(main):004:0> ObjectSpace.memsize_of_all(String)
=> 171817
irb(main):005:0>
What's cool is that in MRI we can get size of the instruction sequences too: irb(main):005:0> ObjectSpace.memsize_of_all(RubyVM::InstructionSequence)
=> 1600800
irb(main):006:0> 100.times { |i| eval("def x_#{i}; end") }
=> 100
irb(main):007:0> ObjectSpace.memsize_of_all(RubyVM::InstructionSequence)
=> 1711904
Also we can see the delta between methods defined with eval vs define_method: irb(main):011:0> ObjectSpace.memsize_of_all(RubyVM::InstructionSequence)
=> 1717984
irb(main):012:0> 100.times { |i| define_method("x_#{i}") { } }
=> 100
irb(main):013:0> ObjectSpace.memsize_of_all(RubyVM::InstructionSequence)
=> 1722416
:-)EDIT: stop saying "cool" so much. :-/
Thanks to copy on write, the amount of memory needed for 2 processes is strictly less than 2. Just make sure to preload as much data as you can before you fork. And in many use cases memory slowly comes unstuck (change anything, and a whole page of data comes unshared) so it can make sense to kill workers every so often and let them refork with everything properly shared.
Another issue with using the brightbox packages is that if you happen to run into a bug, you'll have to figure out if the bug was caused by something non-standard in your ruby installation or if it's actually a bug in ruby.
One last thing to note is that it looks like the latest brightbox release targets patchlevel 327, while ruby core is at 392 (not counting 2.0.0-p0), so you're missing a lot bug fixes until the brightbox team gets around to building against the latest release.
Your other two points are true though. We consider the trade-off worth it for the memory gains.
after_fork do |server, worker| size_in_bytes = 60010241024 # megabytes Process.setrlimit(Process::RLIMIT_AS, size_in_bytes)
www-data 21714 0.0 5.6 102928 87164 ? Sl Feb26 0:30 unicorn_rails master -c production.conf.rb -E production -D
www-data 21762 0.2 8.5 150672 131996 ? Sl Feb26 1:58 unicorn_rails worker[0] -c production.conf.rb -E production -D
www-data 21768 0.2 8.5 152036 132352 ? Sl Feb26 1:58 unicorn_rails worker[1] -c production.conf.rb -E production -D
www-data 21774 0.2 8.1 145248 125668 ? Sl Feb26 1:56 unicorn_rails worker[2] -c production.conf.rb -E production -D
www-data 21780 0.2 8.3 148792 129736 ? Sl Feb26 2:01 unicorn_rails worker[3] -c production.conf.rb -E production -D
www-data 21786 0.1 8.3 148384 129260 ? Sl Feb26 1:48 unicorn_rails worker[4] -c production.conf.rb -E production -D
www-data 21792 0.1 8.6 152156 133880 ? Sl Feb26 1:52 unicorn_rails worker[5] -c production.conf.rb -E production -DI can confirm that. In my previous company, we had a lot of H12 (request timeout) on heroku. We thought this should be our codebase fault, without being able to isolate why some usually fast requests were sometime so long to complete. As a workaround, we decided to temporary switch to unicorn to add more concurrency to our dynos.
H12 went down, but we were flooded by R14 (memory exceeded).
Even with two workers, it was too much. Granted, our app was eating too much memory and we were refactoring it to split it into several distinct apps.
We also had the problem that heroku router seemed to send requests to dyno while master unicorn process was starting (and thus, could not process it yet), which was a major problem since we used hirefire, which keep starting new dynos(and thus, new unicorn masters).
One interesting thing about the post is the mention of Puma[2] as the webserver for Rails app. Puma is thread based, so, its memory footprint is significantly less than unicorn. So, in theory, you should be able to handle more concurrent requests(more threads) per dyno with Puma.
Btw, don't just blindly follow the post. Make sure you test your app and see whether your app use up more memory each dyno allows. If your dyno runs out of memory, Heroku will just silently drop all your requests. So, you might end up having a worse problem.
[1]: In general, Rails uses up quite a lot of memory on cold start. [2]: http://puma.io/