views:

415

answers:

3

I've got a Rails app that has stopped caching somewhere along the way, and I'm not sure which revision along the way might have stopped it from working.

I'm under the impression that page caching, when working properly, should never even hit Rails if it finds the cached file. However, when loading my page and monitoring production.log, it's hitting both Rails and the DB.

I have a sweeper set up that clears the cache on :create, :update, and :destroy. It works fine, as the /public/cache/index.html file is updated whenever one of those events occurs. I thought at first that it might be because I was using the OutputCompression plugin, but removing that had the same result, so I put it back in. The index.html is there, but .htaccess and Rails ignore it and rebuild the entire page, including rewriting the cached index.html.

Here are the relevant parts of the code (unless I'm missing something):

Controller:

class SecretsController < ApplicationController
  caches_page :index
  cache_sweeper :secret_sweeper, :only => [:create, :update, :destroy]

  # snipped
end

.htaccess:

RewriteEngine On

# Rewrite index to check for cached
RewriteRule ^/$ /cache/index.html [QSA]
RewriteRule ^$ /cache/index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]

Firebug response headers

Date: Tue, 02 Jun 2009 18:50:36 GMT
Server: Apache/1.3.41 (Unix) mod_fastcgi/2.4.2 PHP/5.2.9 mod_log_bytes/1.2 mod_bwlimited/1.4 mod_auth_passthrough/1.8 FrontPage/5.0.2.2635 mod_ssl/2.8.31 OpenSSL/0.9.8b
Vary: Accept-Encoding
X-Runtime: 0.05637
Etag: "4f3497a74141d1e92ae7a1fe4d5dc1d2"
Cache-Control: private, max-age=0, must-revalidate
Content-Encoding: gzip
Content-Length: 22356
Connection: close
Content-Type: text/html; charset=utf-8
default-style: tms

I'd love to be able to use mod_gzip, but ASmallOrange doesn't support it, while DreamHost did (before they tripled my price).

Anyway, can anyone shed any light on why Rails is ignoring the cached index.html? I'm assuming it's something in .htaccess, since it should never touch Rails if it's working right.

EDIT: The caching problem turned out to be the first slash on the RewriteRules. It wasn't finding the cached file until I changed them both to "cache/index.html", and now caching works perfectly.

However, now I do have to remove the OutputCompression calls, because it's returning the gzipped version of the file with the Content-Type set to "text/html". Any idea how to get it to send the correct content type for just that file? It's the only one cached in the entire app.

EDIT AGAIN: Changing the .htaccess to this didn't help with the gzip problem:

RewriteRule ^/$ cache/index.html [QSA,T=application/x-gzip]
RewriteRule ^$ cache/index.html [QSA,T=application/x-gzip]

It still shows up as the text representation of a zip file (i.e. gibberish), unless compression is disabled. Caching works perfectly, though.

A: 

I'd check on your ETag configuration. That can frequently prevent proper caching of files if you are using multiple web servers and it isn't configured to be independent of the machine that is serving the file.

_Kevin
It should just be a single web server on shared hosting, and I've heard of ETags, but have no idea what they are or how to set them up. Any pointers?
Chris Doggett
+1  A: 

Why use the OutputCompression plugin? Apache can do this for you. Check out mod_deflate.

Here's the rules I use:

# Deflate
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/x-java
script
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

This will compress all text output from your application, static and dynamic.

Just a guess, but I imagine this is faster, too, since it's an Apache module written in C.

Luke Francl
That would definitely work, if I weren't on ASmallOrange's shared hosting, which has disabled both mod_deflate and mod_gzip. OutputCompression does the same thing, and uses the same processor time, so I have no idea why they won't just enable it. Thanks for the suggestion, though. If I can convince them to enable mod_deflate for me, I'll use that.
Chris Doggett
Oh, bummer. It would reduce their bandwidth by quite a bit. New hosting? ;) You might want to try getting your app up on Heroku's free level. Even their lowest paid level is about the same cost as shared hosting.
Luke Francl
Thanks for the suggestion. Just re-upped for a year because I like ASO, and I'm rewriting my app in Django so I can brush up on my Python (and learn Django, of course). Know of any free hosting for that?
Chris Doggett
You can get Django running on Google App Engine with a bit of work. Their free level is very generous.
Luke Francl
A: 

Finally figured out the problem(s), and my site is live once again after 10 weeks of downtime. Once I got it to load the cache file, I found another problem in that the OutputCompression plugin was compressing the file to a .gz file, but Rails was saving it as .html, and Apache was serving that as text/html, which resulted in gibberish.

The fixes that solved my problem:

In .htaccess:

AddEncoding x-gzip .gz
AddType text/html .gz

RewriteRule ^/$ cache/index.gz [QSA]
RewriteRule ^$ cache/index.gz [QSA]

In config/environments.rb:

ActionController::Base.page_cache_extension = ".gz"

The Ruby code makes the "caches" directive save as "cache/index.gz" instead of "cache/index.html". AddEncoding tells it not to serve it as html, but by itself will just display the page source, since it defaults to a Content-Type of "text/plain". AddType changes things so that .gz files are served as "text/html", causing proper display.

This probably won't work for everyone, but since I don't serve .gz files anywhere on the site, and the front page is the only one cached, this works perfectly for me.

Thanks to everyone for the help.

Chris Doggett