Full Page cache with nginx and memcache

  • Sharebar

Full Page cache with nginx and memcache

Since the cool kids at Google, Microsoft and Amazon researched how performance and scalability affect conversion rates, page load time has become the topic of every eCommerce store.

Magento was once a resource hog that consumated everything available to it and you had to be a magician to pull off some awesome benchmarks without using any reverse proxy or full page cache mechanism. Creating a full page cache with nginx and memcache is really simple (right after hours of research).

Words of warning first:

Don’t use this instead of varnish or Magento’s full page caching. This implemenation of full page cache is very simple, heck it will be even troublesome to clean the cache consistently because guess what, there is no holepunching but you could enhance the configuration file to read cookies and serve directly from the backend server instead.

Another problem is that you’ll need to ensure that a TwoLevel caching is used to be able to flush specific urls.

Now that is out of the way, let’s focus on the matter at hand.

I have tried this configuration file with both Magento enterprise and community and also with WordPress.

#memcache servers load balanced
upstream memcached {
        server     server_ip_1:11211 weight=5 max_fails=3  fail_timeout=30s;
        server     server_ip_2:11211 weight=3 max_fails=3  fail_timeout=30s;
        server    server_ip_3:11211;
	keepalive 1024 single;
}
#fastcgi - little load balancer
upstream phpbackend{
	server     server_ip_1:9000 weight=5 max_fails=5  fail_timeout=30s;
        server     server_ip_2:9000 weight=3 max_fails=3  fail_timeout=30s;
        server    server_ip_3:9000;
}
server {
    listen   80; ## listen for ipv4; this line is default and implied
    root /var/www/vhosts/kingletas.com/www;
    server_name kingletas.com;
    index index.php index.html index.htm;
    client_body_timeout  1460;
    client_header_timeout 1460;
    send_timeout 1460;
    client_max_body_size 10m;
    keepalive_timeout 1300;
    location /app/                { deny all; }
    location /includes/           { deny all; }
    location /lib/                { deny all; }
    location /media/downloadable/ { deny all; }
    location /pkginfo/            { deny all; }
    location /report/config.xml   { deny all; }
    location /var/                { deny all; }
   location ~* \.(jpg|png|gif|css|js|swf|flv|ico)$ {
	    expires max;
	    tcp_nodelay off;
	    tcp_nopush on;
    }
    location / {
        try_files $uri $uri/ @handler;
        expires 30d;
    }
   location @handler {
	rewrite / /index.php;
    }
    location ~ \.php$ {
        if (!-e $request_filename) {
            rewrite / /index.php last;
        }
        expires        off; ## Do not cache dynamic content
        default_type       text/html; charset utf-8;
        if ($request_method = GET) { # I know if statements are evil but don't know how else to do this
            set $memcached_key $request_uri; Catalog request modal
            memcached_pass     memcached;
            error_page         404 502 = @cache_miss;
            add_header x-header-memcached true;
		}
		if ($request_method != GET) {
			fastcgi_pass phpbackend;
		}
    }
    location @cache_miss {
        # are we using a reverse proxy?
        proxy_set_header  X-Real-IP  $remote_addr;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_max_temp_file_size 0;
        #configure fastcgi
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_send_timeout  5m;
        fastcgi_read_timeout 5m;
        fastcgi_connect_timeout 5m;
        fastcgi_buffer_size	256k;
        fastcgi_buffers	4	512k;
        fastcgi_busy_buffers_size	768k;
        fastcgi_param GEOIP_COUNTRY_CODE $geoip_country_code;
        fastcgi_param GEOIP_COUNTRY_NAME $geoip_country_name;
        fastcgi_param  PHP_VALUE "memory_limit = 32M";
        fastcgi_param  PHP_VALUE "max_execution_time = 18000";
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    location ~ /\. {
		deny all;
	}
}
#if you want to make it even better your own cdn
#server {
#      listen 80;
#      server_name media.kingletas.com;
#      root /var/www/vhosts/kingletas.com/www;
#}
#server {
#      listen 80;
#      server_name css.kingletas.com;
#      root /var/www/vhosts/kingletas.com/www;
#}
#server {
#      listen 80;
#      server_name js.kingletas.com;
#      root /var/www/vhosts/kingletas.com/www;
#}

One major topic to remember is that nginx will try to read from memory not write to it. In other words you still need to write the contents to memcache. For WordPress this is what I did in the index.php

/**
* Front to the WordPress application. This file doesn't do anything, but loads
* wp-blog-header.php which does and tells WordPress to load the theme.
 *
* @package WordPress
 */
/**
* Tells WordPress to load the WordPress theme and output it.
 *
* @var bool
 */
ini_set("memcache.compress_threshold",4294967296); //2^32
ob_start();
define('WP_USE_THEMES', true);
/** Loads the WordPress Environment and Template */
require('./wp-blog-header.php');
$buffer = ob_get_contents();
ob_end_clean();
$memcache_obj = memcache_connect("localhost", 11211);
memcache_add($memcache_obj,$_SERVER['REQUEST_URI'],$buffer,0);
echo $buffer;

Notice that I had to change the memcache.compress_threshold setting to HUGE number, that is because memcache will ignore the no compress setting when this threshold is exceeded and compress the content, while this is good and dandy the results in the browser are not.

So there you have it an easy way to implement full page caching with nginx and memcache for WordPress or Magento and the rest of the framework world.

VN:F [1.9.22_1171]
Rating: 10.0/10 (7 votes cast)
VN:F [1.9.22_1171]
Rating: +3 (from 3 votes)
Full Page cache with nginx and memcache, 10.0 out of 10 based on 7 ratings

Author: Luis Tineo

Husband, Father, performance improvement junkie, biker and video gamer, Linux user and in my day job I'm a Systems Architect at Blue Acorn.

Share This Post On
  • Pingback: PHP offloading: Speeding up magento with Vala

  • http://www.facebook.com/profile.php?id=100004102547825 Embroidery Chinese

    how to config magento index.php?

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
    • http://www.kingletas.com letas

      I’d not change the index.php in magento – rather I’d use the predispatch, response and postdispacth events to check, save or clear the cache as needed. But if you still want to go for the index.php (simpler approach just not recommended) you could do the same we are doing in wordpress just capture the output with ob_start and then output it. Let me know if this helps

      VN:F [1.9.22_1171]
      Rating: 0.0/5 (0 votes cast)
      VN:F [1.9.22_1171]
      Rating: 0 (from 0 votes)
    • letas

      I’d recommend that you don’t change the index.php in Magento but use the event driven architecture already in place to add, remove and update the cache. Magento is awesome for implementing CRUD with memcache.

      VA:F [1.9.22_1171]
      Rating: 0.0/5 (0 votes cast)
      VA:F [1.9.22_1171]
      Rating: 0 (from 0 votes)
  • Pingback: the "uh,i'll wait forever." blog — Full Page cache with nginx and memcache

  • http://www.emuparadise.me/ MasJ

    Great post! I have a full page nginx-memcached setup running pretty well (cache invalidation/etc. is all working). I’ve been wondering if using varnish might be better than my current combo. I’m not sure if it’ll work better than nginx-memcached .. would it be faster ?

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
    • Luis Tineo

      Hey MasJ – thanks for stopping by…

      My answer may lead to more questions but this is what I have seen:

      Varnish can handle volumes faster and more efficient but Nginx + memcache can serve the content faster in small portions, especially static content. At first you might even see the difference between them (except locally where varnish can serve a page in ~2ms or less)

      So what I’ve done is to set up varnish in “switch mode” and siege it with enough users to get an idea of the overall response, turn it off and then do the same with nginx and memcache.

      That benchmark usually tells me if Varnish is required or not.

      Hope I answered your question correctly

      VA:F [1.9.22_1171]
      Rating: 0.0/5 (0 votes cast)
      VA:F [1.9.22_1171]
      Rating: 0 (from 0 votes)
      • http://www.emuparadise.me/ MasJ

        I think this is where the real difference lies. I’m using nginx+memcache to actually cache DYNAMIC content. So here’s how it’s currently working:

        The site runs a PHP backend and the pages setup full caches for themselves pretty similar to how this post explains it. (at the end the output buffer gets set into memcache with a key that matches the request URI)

        Then, nginx will serve this cached version *unless* the user takes some action to invalidate it (rates an item, or does something like that..) but once the invalidation happens the cache gets filled with the new page on the next page reload. Also, the other time the cache gets invalidated is when it expires.

        The main reason I did this was to shave off the PHP rendering time on the pages. The volume of traffic is manageable and my intention with this wasn’t to reduce server load BUT only to speed up the time that the page gets served in.

        So coming back to my initial question – with a varnish cache, how many ms am I likely to save over and above nginx+memcached ? My research indicates that the amount will only be a few ms. Furthermore, I think cache invalidation through PHP might be a bit tricky to integrate with varnish (as memcache is very well integrated with both nginx and PHP..).

        Just wondering if it’s worth the effort. Obviously setting up a test might be a good idea but on a local setup nginx+memcache returns pages in 0ms :O

        As you said nginx+memcache can serve content better whereas varnish can handle volume better – I guess for now nginx+memcache will continue to be my solution of choice. Though once I do pick up VCL I might give Varnish a shot (just dread the testing that comes with a new addition to the tech stack!)

        VA:F [1.9.22_1171]
        Rating: 0.0/5 (0 votes cast)
        VA:F [1.9.22_1171]
        Rating: 0 (from 0 votes)
        • Luis Tineo

          This is not going to be a very impartial answer, because I really like Nginx and Varnish.

          The business side answer:

          Varnish is worth the effort IMO only if you are going to get the traffic and conversions required to pay up for the implementation.

          Nginx configured to be in a high availability maybe better and quicker to set up.

          The main problem is what you mentioned. Cache invalidation and reloading it will be a pain especially when you start looking at what content logged in users vs non logged users will have and how you are going to handle the frontend side.

          My answer

          Varnish is one of the coolest technology and everyone should be playing with it right now. It works out of the box and the setup time is minimum, if you start getting fancy then things get complicated. Nginx is a bit a magic box to me that is more advanced and therefore a bit more complicated to fully master. Varnish supports memcache so you could have varnish + nginx + memcache talking to each other and do it some awesome things…

          VA:F [1.9.22_1171]
          Rating: 0.0/5 (0 votes cast)
          VA:F [1.9.22_1171]
          Rating: 0 (from 0 votes)
  • Resende

    Luis, what’s your opinion on this Nginx + PHP-FPM + FastCGI config suggested by Magento on a recent white paper?: http://www.magentocommerce.com/boards/viewthread/296381. It looks a bit messy and confusing…

    Would be awesome if you could prepare a post with an improved Nginx logic for a FastCGI cache backend to be used with Magento.

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
    • Luis Tineo

      Resende,

      Thanks for the suggestion and please accept my apologies that I didn’t respond before. I didn’t get notified of this comment. Please contact me directly when you get a chance :-)

      VA:F [1.9.22_1171]
      Rating: 0.0/5 (0 votes cast)
      VA:F [1.9.22_1171]
      Rating: 0 (from 0 votes)
  • Alex

    But what about the shopping cart ?how do you render without cache it ?

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)
    • http://www.kingletas.com Luis Tineo

      Hey Alex,

      It is actually pretty simple, if you are using an event driven approach or if you are building a key right up in the index.php, anything that is a-) https or b-) checkout related is banned.

      I hope it makes sense!

      Thanks,

      Luis

      VN:F [1.9.22_1171]
      Rating: 0.0/5 (0 votes cast)
      VN:F [1.9.22_1171]
      Rating: 0 (from 0 votes)
    • Luis Tineo

      Alex sorry for the late reply.

      What I have done with the checkout in general can be summarize as follows:

      I cache the areas that can be cached using Magento’s builtin cache functionality and bypass the checkout frontname in nginx by having a location /checkout… be aware that having a location that way doesn’t account for the onestepcheckout extension but you can use regex in the location.

      The question is how much time do you want to invest speeding up the cart through cache. Is it worth the time?

      Let me know if you have any other questions

      VA:F [1.9.22_1171]
      Rating: 0.0/5 (0 votes cast)
      VA:F [1.9.22_1171]
      Rating: 0 (from 0 votes)
  • Adrian

    Hi,

    I am quite new to nginx and mecached so be patient.

    Why this approach vs say standard php use of memcached?

    VA:F [1.9.22_1171]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.22_1171]
    Rating: 0 (from 0 votes)