Rails caches_action_with_params

One of the big problems with caching in Rails is the way that Rails’s caching systems handles query parameters. Page caching completely screws this up–the page cache will turn /articles/read?id=100 into /articles/read.html, and Apache will then hand all future hits on /articles/read off to that static HTML file, even if the user was looking for /articles/read?id=99. You can mostly get around this by making sure that you always use named parameters via Rails’s routes, but even then a malicious user can do weird things to your cache by feeding query parameters via ?.

The action cache is slightly better, but it’ll still misbehave with the examples above. What we really need is a caching system that pays attention to all parameters, not one that ignores all of them that aren’t part of a route.

Towards that end, I’ve created caches_action_with_params. It’s a minor derivative of caches_action with a different fragment cache key; instead of using the URL (as generated by url_for), it ignores URLs completely and uses ACTION_PARAM/<host>/<controller>/<<action>/<params>. This way caching isn’t dependent on routing, which will help with some of the stranger problems that Typo has seen. On the downside, if your actions explicitly check the URL that the user used, then caches_action_with_params won’t work for you.

Once I’m off the train and sitting somewhere with usable IP, I’ll post some sample code to the Typo bug tracker and generate a couple benchmarks. I expect this to about about 10% as fast as the page cache, but it should still be faster then 100 hits/second, which is my personal definition of “fast enough” this week. Then, if no one has any big complaints, I’ll commit this and switch off the page cache.

Once that is done, it’ll be fairly easy to add a lifespan to cached pages, so we can say “this page is only good for 2 hours” and have it regenerate automatically after that.

Posted by Scott Laird Tue, 04 Oct 2005 15:54:24 GMT


Comments

  1. Peter krantz about 21 hours later:

    Thank you for an interesting post. Rails would definitely need something like you describe. I am thniking of something similar to what ASP.NET uses. For pages you can specify how the should be cached with a directive:

    <%@OutputCache Duration=”60” VaryByParam=”none” %>

    The value of VaryByParam can be “none” (don’t care about query params), a comma separated list of uqery param identifiers (e.g. “id,node,statusid”) or “*” (=vary by all params available effectively creating a cache instance with the complete URL as key).

    An overview is available here: http://aspnet.4guysfromrolla.com/articles/022802-1.aspx

  2. Piers Cawley 1 day later:

    I’ve taken a look at the patch, and I still think we’re at the wrong level of granularity. Rather than caching pages we should be caching certain divs as fragments, so sidebar plugins would be responsible for caching their content etc.

    To make this work there needs to be a good mapping between fragment paths and content objects, and I think I’ve worked out how to build the mapping and keep it up to date. Expect a patch soon.