Posted 11 May 2006
Next week, there’s a Rails conference in the Netherlands, Ruby en Rails 2006, were I will present my wisdom about Rails performance tuning. If you live nearby, and haven’t registered yet, now’s your last chance!
I’m sure it will prove to be a very interesting event. I’m especially looking forward to hear about a Ruby.net implementation project, presented by Wilco Bauwer.
And, of course I anticipate the after conference event, where we should have plenty of time to relax and dream up new ideas to save the world from joyless programming!
Update: I must say this was a very well organized event at a beautyful location, were I’ve met very nice people. It was a real pleasure to be there.
My slides are now available for download.
View Comments
Posted 03 Apr 2006
In addition to being packed with new features and also some forgotten ones, the recently released Rails 1.1 contains a number of performance related changes. For some cases, you will see significant improvements.
The components implementation received a complete refactoring, making it both faster and simpler to use.
-
inside a controller you can easily determine whether your current action is being run on behalf of an embedding controller by calling component_request?
-
session and flash information will now be passed on from the embedding controller to the component. As a consequence, it is no longer necessary to save session information to the session container before a component action is invoked by the embedding controller. For pages embedding n components, this saves n session queries and updates, reducing DB load significantly if you use ActiveRecordStore or MysqlSessionStore.
- It is also possible to specify the controller as a class constant, bypassing the inflector code to compute the controller class at runtime. So
render_component :controller => "greeter",
:action => "hello_world"
becomes
render_component :controller => GreeterController,
:action => "hello_world"
Due to the component refactoring, it is no longer necessary to save newly created sessions twice. This has the biggest impact for people using ActiveRecodStore.
We have changed the default for ActiveRecord::Base.allow_concurrency from true to false, since almost all apps run in a single threaded request container. In addition, you can now safely set ActiveRecord::Base.allow_concurrency in environment.rb or any other config file.
Connection cache management got a serious overhaul. Previously, the connection cache was emptied after each request, in order to solve problems with dropped database connections. You can now use lazy connection verification by setting ActiveRecord::Base.verification_timeout to a number of seconds timeout interval. It defaults to 0, to be compatible with the previous release. However, I strongly recommend setting it to a higher value, for a simple reason:
Dropped database connections occur mostly on sites not seeing traffic for the database internal connection timeout period. High traffic sites will see them rarely. However, for high traffic sites, reducing the number of database accesses by setting verification_timeout to a value slightly below the database internal value will result in a significant reduction of DB requests. For example, if you use memcached for session management, the DB won't see any hit for action cached pages.
Additionally, the connection cache size was reduced from the number of ActiveRecord classes involved in a query, to the number of active database connections. So for most applications this means that instead of retrieving and verifying n connections, at most 1 retrieval and verification will occur, and only when the timeout interval has passed.
Update: the correct way of determining whether you're in a component request is calling component_request?. In a view you can call controller.component_request?
View Comments
Posted 30 Mar 2006
As you probably know already, Rails 1.1 was released a few days ago.
We have worked hard to ensure that 1.1 performs on the same level as 1.0. For some cases, it performs significantly better than 1.0 (explanation will follow in a future post).
The following performance data table shows the speed difference for the fastest available configuration for my application.
/empty/index |
7.86139 | 7.10545 |
636.0 | 703.7 |
1.57 | 1.42 |
1.11 |
/welcome/index |
8.38743 | 8.32744 |
596.1 | 600.4 |
1.68 | 1.67 |
1.01 |
/rezept/index |
8.87038 | 8.75717 |
563.7 | 571.0 |
1.77 | 1.75 |
1.01 |
/rezept/myknzlpzl |
8.86172 | 8.76325 |
564.2 | 570.6 |
1.77 | 1.75 |
1.01 |
/rezept/show/713 |
22.22530 | 20.12046 |
225.0 | 248.5 |
4.45 | 4.02 |
1.10 |
/rezept/cat/Hauptspeise |
25.29051 | 24.70123 |
197.7 | 202.4 |
5.06 | 4.94 |
1.02 |
/rezept/cat/Hauptspeise?page=5 |
25.92528 | 25.40904 |
192.9 | 196.8 |
5.19 | 5.08 |
1.02 |
/rezept/letter/G |
25.06242 | 24.96315 |
199.5 | 200.3 |
5.01 | 4.99 |
1.00 |
You can find additional information and lots of performance data in the full report .
View Comments
Posted 28 Mar 2006
I’m proud to announce that I will be giving a talk at RailsConf2006 on Rails Application Optimization.
If you have specific topics or questions you’d like to have addressed besides the ones mentioned in the talk proposal, or some general suggestion, I invite you to add an entry to the comment section of this post.
Looking forward to meet you in Chicago!
View Comments
Posted 24 Jan 2006
A few days ago Eric Hodel announced the availability of a new pure Ruby memcached client implementation (memcache-client-1.0.3) with performance improvements over the older Ruby-Memcache-0.4 implementation.
I had measured the old version previously, for use as a Ruby on Rails fragment cache storage container, but found its performance to be abysmal and completely unusable for my purposes.
memcache-client-1.0.3 provides much better performance: much faster than either the old implementation, pstore or ActiveRecordStore, but also faster than my optimized SQLSessionStore using MysqlSession (see Roll your own SQL session store).
In order to determine the relative performance I ran my usual benchmarks using railsbench against memcache-client-1.0.3 and SQLSessionStore using MyslSession.
All components resided on a single machine. Before running the tests, both DB tables and memcached were populated with 10000 sessions.
The first test was run with Mysql query caching disabled:
/empty/index |
1.23318 | 0.96322 |
810.9 | 1038.2 |
1.23 | 0.96 |
1.28 |
/welcome/index |
1.44314 | 1.15051 |
692.9 | 869.2 |
1.44 | 1.15 |
1.25 |
/rezept/index |
1.52869 | 1.19616 |
654.2 | 836.0 |
1.53 | 1.20 |
1.28 |
/rezept/myknzlpzl |
1.52159 | 1.19022 |
657.2 | 840.2 |
1.52 | 1.19 |
1.28 |
/rezept/show/713 |
4.14906 | 3.95939 |
241.0 | 252.6 |
4.15 | 3.96 |
1.05 |
/rezept/cat/Hauptspeise |
9.48101 | 9.29202 |
105.5 | 107.6 |
9.48 | 9.29 |
1.02 |
/rezept/cat/Hauptspeise?page=5 |
9.84404 | 9.65393 |
101.6 | 103.6 |
9.84 | 9.65 |
1.02 |
/rezept/letter/G |
5.42370 | 5.20227 |
184.4 | 192.2 |
5.42 | 5.20 |
1.04 |
c1: DB sessions, c2: memcached sessions, r/s: requests per second, ms/r: milliseconds per request |
On the second run, Mysql query caching was enabled:
/empty/index |
1.26804 | 0.95403 |
788.6 | 1048.2 |
1.27 | 0.95 |
1.33 |
/welcome/index |
1.47070 | 1.13807 |
679.9 | 878.7 |
1.47 | 1.14 |
1.29 |
/rezept/index |
1.55976 | 1.18066 |
641.1 | 847.0 |
1.56 | 1.18 |
1.32 |
/rezept/myknzlpzl |
1.55402 | 1.17650 |
643.5 | 850.0 |
1.55 | 1.18 |
1.32 |
/rezept/show/713 |
3.50876 | 3.20310 |
285.0 | 312.2 |
3.51 | 3.20 |
1.10 |
/rezept/cat/Hauptspeise |
4.31720 | 4.00799 |
231.6 | 249.5 |
4.32 | 4.01 |
1.08 |
/rezept/cat/Hauptspeise?page=5 |
4.41150 | 4.11811 |
226.7 | 242.8 |
4.41 | 4.12 |
1.07 |
/rezept/letter/G |
4.31190 | 4.02345 |
231.9 | 248.5 |
4.31 | 4.02 |
1.07 |
c1: DB sessions, c2: memcached sessions, r/s: requests per second, ms/r: milliseconds per request |
What can we learn from this data?
- Pages which force the creation of new sessions (empty, welcome) and action cached pages (rezept/index and rezept/knzlpzl) experience a speedup between 25% and 33%.
- Pages involving DB queries other than session retrieval don’t see as much speedup.
- If query caching is disabled and the query is really DB expensive (/rezept/cat issues a LIKE %Hauptspeise%), the speed improvement is negligible.
- Enabling the Mysql query cache will result in slightly slower creation of new sessions, but can speed up complex queries tremendously (table below).
/empty/index |
1.23318 | 1.26804 |
810.9 | 788.6 |
1.23 | 1.27 |
0.97 |
/welcome/index |
1.44314 | 1.47070 |
692.9 | 679.9 |
1.44 | 1.47 |
0.98 |
/rezept/index |
1.52869 | 1.55976 |
654.2 | 641.1 |
1.53 | 1.56 |
0.98 |
/rezept/myknzlpzl |
1.52159 | 1.55402 |
657.2 | 643.5 |
1.52 | 1.55 |
0.98 |
/rezept/show/713 |
4.14906 | 3.50876 |
241.0 | 285.0 |
4.15 | 3.51 |
1.18 |
/rezept/cat/Hauptspeise |
9.48101 | 4.31720 |
105.5 | 231.6 |
9.48 | 4.32 |
2.20 |
/rezept/cat/Hauptspeise?page=5 |
9.84404 | 4.41150 |
101.6 | 226.7 |
9.84 | 4.41 |
2.23 |
/rezept/letter/G |
5.42370 | 4.31190 |
184.4 | 231.9 |
5.42 | 4.31 |
1.26 |
c1: DB sessions without query cache, c2: DB sessions with query cache |
Like all benchmarks, these results have to be taken with a grain of salt. Choice of either option involves more than just looking at the above numbers.
Database and memcached have quite different scaling properties: adding more memcached daemons is easy, whereas DB based session storage scales mostly by buying a bigger DB machine or running the session DB on a separate machine using a session database.
On the other hand, DB session storage makes it easy to get application usage statistics using SQL queries, e.g. displaying the number of active sessions or checking whether a particular user is currently logged in. I don’t know an easy way to do this using the memcachd API.
It seems to me that using memcached for session storage is a good choice if your DB server experiences a very high load and your scaling options are already exhausted. I wouldn’t recommend to use it per default.
Using memcachd for Ruby on Rails fragment cache storage is a completely different story, on which I hope to report in the future.
View Comments