2013
02.11

For some months I’m wondering how can I create a memcached base reverse proxy infront of my apache, that supports namespaces, for easy cache purging…

After some days of playing I came up with the solution :)

First I thought enhanced memcache module will work just like proxy_cache does… It was quite a big mistake. Actually, it doesn’t put any data into memcache database, unless, you put it there yourself.

So, I end up with a tiny script, that hooks on a WP Super Cache filter, catches the whole rendered page, and puts it right into memcache. The only tricky part was the namespace thing, because there’s no such native support for that, only some workaround to prefix.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
/*
Plugin Name: Add page buffer to memcached
Plugin URI: http://djzone.im
Description: Add page buffer to memcached
Version: 1.0
Author: DjZoNe
Author URI: http://djzone.im
*/


add_filter('wp_cache_ob_callback_filter','my_addto_memcached');

function my_addto_memcached($buffer)
{
    if(class_exists('Memcached'))
    {
        $m = new Memcached();
        $m->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
        $m->setOption(Memcached::OPT_COMPRESSION, false);
        $m->addServers(array(
            array('127.0.0.1', 11211, 20)
        ));

        $namespace = '__ns__'.$_SERVER['SERVER_NAME'];
        $key = $namespace.md5($_SERVER['REQUEST_METHOD'] . '%20'. $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] . 0);

        $data = "EXTRACT_HEADERS\r\nContent-Type: text/html; charset=UTF-8\r\n";
        $data .= "\r\n";
        $data .= $buffer."\r\n";
        $data .= '<!-- Memcached using key:'.$key.' @ '.date('Y-m-d H:i:s').' uri: '.$_SERVER['REQUEST_URI'].' //-->';

        $ret = $m->add($key,$buffer,7200);

        $buffer .= '<!-- Ret: '.$m->getResultCode().' //-->';
    }

    return $buffer;
}

The server side was simple, I used named location, and an upstream block.

So the upstream block looks like this:

1
2
3
4
    upstream memcached_upstream
    {
        server 127.0.0.1:11211;
    }

The memcache part:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location @fastcache
{
    if ($request_method = POST)
    {
        return 404;
    }

    access_log  /var/log/nginx/cache.log cache;
    error_log /var/log/nginx/memcached-add-debug.log debug;

    set $enhanced_memcached_key "$request_method $host$request_uri";
    set $enhanced_memcached_key_namespace "$host";

    enhanced_memcached_hash_keys_with_md5 on;
    enhanced_memcached_pass memcached_upstream;

    error_page  404 403 405 = @backend;
}

The first few lines makes sure, that POST requests never get cached, like wp-cron requests, and it doesn’t lower our hit rate ;)

There comes the backend part, that connects to Apache:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location @backend
{
    proxy_pass http://localhost:8080;
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_ignore_headers    X-Accel-Expires Expires Cache-Control;
    proxy_pass_header Set-Cookie;

    proxy_hide_header Vary;
    proxy_cache off;
    proxy_pass_header Location;

    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}

And finally the host config with the flush capability included

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
server {
    listen  80;
    server_name example.com;
    error_log /dev/null crit;

    root /home/www/someone/example.com/webroot;

    location /flush/
    {
        allow              127.0.0.1;
        deny               all;

        set $enhanced_memcached_key "$request_method $host$request_uri";
        set $enhanced_memcached_key_namespace "$host";
   
        enhanced_memcached_flush_namespace on;
        enhanced_memcached_hash_keys_with_md5 on;
        enhanced_memcached_pass memcached_upstream;
    }

    location ~* ^.+.(jpg|jpeg|gif|js|png|ico|css|zip|tgz|tar|gz|rar|bz2|doc|docx|xls|exe|pdf|ppt|txt|nfo|rtf|tif|mp3|mp4|mpg|mpeg|mov|avi|divx|wav|ogg|aac|flac|swf|fla|wmv|wma|xml)$
    {
        if (-f $request_filename)
        {
            access_log off;
            expires max;
            break;
        }

        if (!-e $request_filename)
        {
            rewrite ^.*/files/(.*) /wp-includes/ms-files.php?file=$1 last;
        }

        error_page 403 404 = @backend;
    }

    location /
    {
        try_files @fastcache @fastcache;
    }

    location /wp-admin/
    {
        try_files @backend @backend;
    }

    include "global/proxy.conf";
}

We shouldn’t cache the /wp-admin/ part of the site (In real life situations you’d probably need to add more uncached locations, just like login page, forgot password, etc.). In our case we setup a named location what goes straight to the Apache, without any more caching.

In the flush part, be sure to include your server’s public IP address as well, unless we can’t flush the namespace cache from WordPress.

And there’s only one thing needed to my complete happiness, the flusher thing on the WordPress side:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
/*
Plugin Name: My cache purger
Plugin URI: http://djzone.im
Description: My cache purger
Version: 1.0
Author: DjZoNe
Author URI: http://djzone.im
*/


function my_cache_prune()
{
    $purge_url = 'http://'. $_SERVER['SERVER_NAME'] . '/flush/';
    wp_remote_get($purge_url);
}

add_action('delete_post','my_cache_prune',30,3);
add_action('trash_post','my_cache_prune',30,3);
add_action('untrash_post','my_cache_prune',30,3);
add_action('save_post','my_cache_prune',30,3);
add_action('edit_post','my_cache_prune',30,3);

add_action('post_updated','my_cache_prune',30,3);

add_action('add_attachment','my_cache_prune',30,3);
add_action('edit_attachment','my_cache_prune',30,3);
add_action('delete_attachment','my_cache_prune',30,3);

This method can be applied to comments not just posts and media, but that’ll be your homework ;)

No Comment.

Add Your Comment