Our ecommerce system has about a 300 - 500 ms TTFB, which I don't think is bad at all, but still isn't as good as a static file. This week I finally experimented with creating my own local cache of product / category pages, and telling our server to rewrite to those files if available, else rewrite to the ecommerce system.

It works great and our TTFBs are usually around 50 - 100 ms now. I also watched our server load average in top and, for the most part, the numbers went from around 0.5 down to 0.2ish (with some occasional jumps due to traffic). So in general it looks like this is making the server run smoother as well as well as speed up page load time, which is what I hoped for.

That being said I'm no expert on rewrites and server performance - can anyone do a quick review and let me know if there's room for improvement? These rewrites are located in the vhost file.

# product pages
RewriteCond %{REQUEST_URI} ^/product_([^.]+).*$
RewriteCond %{DOCUMENT_ROOT}\/\local_http_cache\/product_%1.php -f
RewriteRule (.*) /local_http_cache/product_%1.php?CacheFlag [PT,QSA,L]
RewriteRule ^/product_([^.]+).*$ /path/to/OurEcommerceSystem?ProductID=$1 [PT,QSA]

# category pages
RewriteCond %{REQUEST_URI} ^/category_([^.]+).*$
RewriteCond %{QUERY_STRING} !((^|&)(Sort_By|Per_Page)=(.*)+(&|$))
RewriteCond %{DOCUMENT_ROOT}\/\local_http_cache\/category_%1.php -f
RewriteRule (.*) /local_http_cache/category_%1.php?CacheFlag [PT,QSA,L]
RewriteRule ^/category_([^.]+).*$ /path/to/OurEcommerceSystem?CategoryID=$1 [PT,QSA]

Couple of other notes:

  • For both product and category pages the main goal is the same: check if a cache file exists for this product / category ID. If so, serve it, else do nothing and let the next rewrite send the request to our ecommerce system.

  • The ?CacheFlag doesn't do anything particularly functional - but a rule in /local_http_cache/'s .htaccess file tells it to forbid anything that doesn't have that flag, so no one can browse those files directly

  • the cache pages are purposely PHP due to some code that checks session IDs, but I'm going to look in to eliminating that, which would allow me to cache just static html code.

RewriteCond %{REQUEST_URI} ^/product_([^.]+).*$
RewriteCond %{DOCUMENT_ROOT}\/\local_http_cache\/product_%1.php -f
RewriteRule (.*) /local_http_cache/product_%1.php?CacheFlag [PT,QSA,L]

You don't need the first condition that checks the REQUEST_URI. This check should be performed in the RewriteRule pattern instead (just as you have done for the directive that follows) - instead of using a generalised match anything (.*) (which didn't need to be capturing) in the RewriteRule. (You will naturally need to change the backreference from %1 to $1 if you do this - see below)

The RewriteRule pattern is processed first. Only if this matches are the preceding conditions processed, so by having a match everything pattern here, you are forcing the RewriteCond directive(s) to be processed.


The trailing .*$ is superfluous. This simply forces the regex parser to gobble-up the rest of the URL-path, which you don't appear to be interested in anyway.

However, this also looks like it might be a bit too general? I assume you don't have any static resources (JS, CSS or images, etc) that have a URL-path that starts /product_? If you do then this pattern should be made more restrictive, otherwise it will end up "testing" many more requests than it needs to.

RewriteCond %{DOCUMENT_ROOT}\/\local_http_cache\/product_%1.php -f

You don't need to backslash escape the slashes or l(?) in the TestString. This isn't a regex, but even if it was these characters carry no special meaning.

/local_http_cache/product_%1.php?CacheFlag [PT,QSA,L]

Do you need the PT flag here (or on any of these directives)? This results in the substitution string being treated as a URL-path and the rewriting process starts over. If you are rewriting directly to a file (which is assumed in a vHost context), which requires no further processing, then the PT flag can be omitted.

In which case I'd also question the use of the CacheFlag query string. You could change this to an environment variable (eg. E=UniqueCacheFlag:1 in the RewriteRule flags) and check for this in the /local_http_cache directory instead (eg. Require env UniqueCacheFlag - assuming Apache 2.4). The QSA flag could also be omitted in this case. This would also make direct access "impossible".

The ?CacheFlag doesn't do anything particularly functional - but a rule in /local_http_cache/'s .htaccess file tells it to forbid anything that doesn't have that flag

Since efficiency is the question here... why are you using .htaccess? Especially since you have the other directives in the vHost. It's not just the fact that there is a .htaccess file - it's that they are enabled to start with (Apache will search for them). The directives in the /local_http_cache/.htaccess file should be moved to an appropriate <Directory> section in the vHost and AllowOverride None should be set for the document root, to disable .htaccess files.

RewriteRule ^/product_([^.]+).*$ /path/to/OurEcommerceSystem?ProductID=$1 [PT,QSA]

Shouldn't you have an L flag here to prevent further processing?

So, in summary, we have:

# product pages
RewriteCond %{DOCUMENT_ROOT}/local_http_cache/product_$1.php -f
RewriteRule ^/product_([^.]+) /local_http_cache/product_$1.php [E=UniqueCacheFlag:1,L]
RewriteRule ^/product_([^.]+) /path/to/OurEcommerceSystem?ProductID=$1 [QSA,L]

The same would then apply to the # category pages block.

  • 1
    Wow I had absolutely no idea that you could sort of "define" your %1 after referencing it! I didn't know the system looked at the RewriteRule first and then at RewriteCond. Really neat! I also took your advice and used the env var, and will move that .htaccess rule to vhost as well. For the PT flag, our ecommerce system actually does need it, but the cache I've built doesn't, so I removed it as you suggested. Thank you very much @MrWhite! – Mike Willis Mar 18 at 18:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.