How to save 100m of RAM per mongrel
UPDATE: See Part 2 of this for a better solution
Note: This monkey-patch only works on Rails 2.2
We recently noticed our mongrels, upon startup, were 244M. Eek.
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 53749 some_user 15 0 337m 244m 2912 S 0 2.0 0:17.49 mongrel_rails
Running Bleakhouse on our rails app, i was amazed at the number of objects created. over 2M!
$ BLEAK_HOUSE=1 ruby-bleak-house ./script/runner 'puts 1' ** Bleakhouse: installed 1 ** BleakHouse: working... ** BleakHouse: complete ** Bleakhouse: Run 'bleak /tmp/bleak.53749.0.dump' to analyze. $ bleak /tmp/bleak.53749.0.dump Displaying top 20 most common line/class pairs 2,047,025 total objects 2,047,025 filled heap slots 2,403,764 free heap slots 1779063 __null__:__null__:__node__ 197423 __null__:__null__:String 16519 __null__:__null__:Array 12728 __null__:__null__:ActionController::Routing::DividerSegment 10495 __null__:__null__:Hash 5676 __null__:__null__:ActionController::Routing::StaticSegment 5436 __null__:__null__:Class 5132 __null__:__null__:Regexp 4525 __null__:__null__:ActionController::Routing::DynamicSegment 2524 __null__:__null__:ActionController::Routing::Route 1307 __null__:__null__:Gem::Version
Whats up with all those routing objects! So i tried a blank routes.rb file….
519,307 total objects 519,307 filled heap slots 233,503 free heap slots
Arghh…. 1.5M objects just because of routes.rb. I know our application is large, lots of resources and nested resources, but thats crazy.
$ rake routes | wc -l 2516
Ok, so taking a closer look, I realized something. Each resource route has a corresponding formatted_* route, lots of which we dont use. So now for some nasty monkey-patching.
ActionController::Routing::RouteSet::Mapper.class_eval do
protected
def map_unnamed_routes(map, path_without_format, options)
map.connect(path_without_format, options)
#map.connect("#{path_without_format}.:format", options)
end
def map_named_routes(map, name, path_without_format, options)
map.named_route(name, path_without_format, options)
#map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
end
end
I then manually added back the formatted routes we needed, careful to use named routes as “map.formatted_foo_bar”
$ rake routes | wc -l 1490
$ bleak /tmp/bleak.56554.0.dump 1,242,796 total objects 1,242,796 filled heap slots 1,224,309 free heap slots
Awesome. I no longer create almost 800k objects.
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 56554 someuser 15 0 249m 156m 2912 S 0 1.3 0:09.92 mongrel_rails
244m –> 156m
This is not a very elegant solution… but I saved almost 100m per mongrel. Ohh.. and it reduced startup time by 50%.
How big are your mongrels?
How does Merb’s router differ from Rails routing? Worth investigating?

Wait. Why do we need to workaround this? Why is Rails creating 100mb of extraneous objects *on startup*? Surely there is a problem with the default behavior in that case.
This is really really interesting. I will give the patch a try.
Aaron,
Routes double total RSS use for large applications.We had similar downsizing in our environment.
http://github.com/methodmissing/routing_with_optional_formats/tree/master
There’s yet another optimization pattern – only mapping routes for actions that exist iow. if a controller only responds to 1 REST action, only map that, not all 7.
http://gist.github.com/23253
The current Rails initializer sequence complicates clean optimizations and to get the Gist snippet to fire, one has to do stick it in config/initializers/ and
RouteFilter.prune! if Rails.env.production?
in environment.rb, after the config block.This however still doesn’t save on memory as downsizing an Enumerable doesn’t yield memory back to the OS, but yields noticeable differences in recognition overhead.
The Anonymous module that installs named routes in ActionController, ActionView etc. is also a candidate for similar optimization ( undef_method ?, but see below ).
It would be most excellent if Rails::Initializer could be tweaked to setup routing AFTER preloading application klasses ( current default for Prod. env ) to allow for a more granular routing table, but this may break existing plugins, assumptions etc.
Getting this right has huge ramifications for the Rails hosting space.
Thoughts ?
- Lourens
@Lourens, feels like a nice good addition for Rails Core. Have you tried opening up a new Ticket with your implementation so Routes become more flexible and save more memory?
Interesting. Have you also experimented with Phusion Passenger + Ruby Enterprise Edition? If you use those two together, then the memory occupied by the compiled routes will be shared between all Rails processes, which should also reduce your total memory usage significantly.
I’m very interested in trying this, but I can’t seem to get it to work properly. What version of Rails are you using? Where are you placing the monkey-patch?
Thanks
-jeff
Rails lighthouse already has a patch for this at http://rails.lighthouseapp.com/projects/8994/tickets/1215-add-actions-and-formatted-options-to-mapresources
I imagine that should solve this problem.
Yes, please see my patch on Lighthouse, and add your voice there if you’d like to see it included in Rails. Your comments and benchmarks would really help convince the core team.
Introspecting the controllers feels like a bit too much magic for me — loading order problems aside, I’m a fan of documenting the desired routes explicitly in routes.rb as you can see from the ticket — but anything that reduces the astonishing memory overhead of the default routing is great news!
It’s really nice. I will give it a try.
Sounds cool, but I don’t really get it. Which version of rails did you monkey patch? All versions I checked (originally I wanted to apply this to one of our apps using rails 2.0.5) didn’t had the methods you changed but had a lot of duplicated code in actionpack/lib/action_controller/resources.rb in the form:
show_action_options = action_options_for("show", resource)
map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
update_action_options = action_options_for("update", resource)
map.connect(resource.member_path, update_action_options)
map.connect("#{resource.member_path}.:format", update_action_options)
destroy_action_options = action_options_for("destroy", resource)
map.connect(resource.member_path, destroy_action_options)
map.connect("#{resource.member_path}.:format", destroy_action_options)
I tried to comment out the .format which worked ok, but that’s of course much worse than monkey patching and only worth the experiment.
It was things like this that ultimately lead me to build <a href=’http://rubywaves.com’>Waves</a>. No routes, just Resource classes with method "functors" to match requests. You can only monkey-patch for so long before you are really writing your own framework on top of Rails. Our 0.8.x release is pretty solid – we are using it within AT&T Interactive R&D to provide a services tier for some of our prototype apps.