memcached - a distributed memory object caching system

Introduction: Memcached Proxy - Dormando (August 26, 2022)

What is this and why?

A generic cache pool management proxy, built into the memcached daemon. It supports all of the text protocol. It is designed to abstract clients away from pools of memcached servers, for ease of management, scale-out, and so on. It uses Lua to create a highly flexible configuration language, and comes with libraries to make basic usage easy.

The proxy can manage pools of servers locally, across availability zones, replicate set and delete traffic, send keys to different pools based on flexible rules, and so on. It is fast (hundreds of thousands of rps at least) and will only get faster.

The proxy is built into memcached to simplify deployment and enable code re-use; all of memcached’s client handling code is reused for the proxy, including TLS management. The proxy adds no new external dependencies so it is easy and fast to compile and deploy. Being embedded in the cache daemon allows flexible topology designs, which we will talk about in future posts.

Why make this when other proxies exist? There are a couple popular memcached specific proxies, but they are either abandoned or hard to build. This proxy is fully supported by the community. Existing proxies are designed for the company they were built at; with our flexible configuration system we can avoid restricting people to designs and compromises taken at these larger companies.

We are early in the process of getting this proxy to end users. If you feel something is missing or undocumented, please reach out and let us know!

More FAQ’s can be answered by the full documentation

Quickstart Guide

This guide needs version 1.6.17 or newer. Enabling the proxy is as easy as adding an extra configure argument:

./configure --enable-proxy
make
make test # optional

For this quickstart guide we will fetch one extra file. This ‘simple’ library is a Lua wrapper to create easy quickstart configurations. The proxy has nearly limitless potential in structuring pools and routes. Because of this the raw configuration system can be pretty confusing, so we ship a library for common use cases.

These libraries live here: https://github.com/memcached/memcached-proxylibs - issues and pull requests are welcome!

For now, lets fetch ‘simple’:

wget https://raw.githubusercontent.com/memcached/memcached-proxylibs/main/lib/simple.lua

Full documentation is here - Several configuration examples live in the repository as well.

We’ll walk through a few examples. First, lets use the proxy to manage a single pool of servers.


package.loaded["simple"] = nil
local s = require("simple")

verbose(true)

router{
    router_type = "flat",
}

pool{
    name = "default",
    backends = {"127.0.0.1:11212 server1", "127.0.0.1:11213 server2"},
}

Save this as config.lua. The first two lines load our convenience library, which handles all of the API to expand our simple configuration into real configuration.

Then we start the server:

LUA_PATH="/path/to/simple/?.lua" memcached -o proxy_config=/path/to/config.lua

Sending a SIGHUP to the daemon will reload the configuration seamlessly. That’s it!

Lets try out a replicated example for our next example:


package.loaded["simple"] = nil
local s = require("simple")

verbose(true)
-- You can pull this zone externally from another lua file, OS env, etc
my_zone(os.getenv("MEMCACHE_MYZONE"))

router{
    router_type = "keyprefix",
    match_prefix = "/(%a+)/",
    default_pool = nil
}

pool{
    name = "foo",
    zones = {
      z1 = {
        "127.0.0.1:11212 foo1",
        "127.0.0.1:11213 foo2",
      },
      z2 = {
        "127.0.0.1:11214",
        "127.0.0.1:11215",
      },
    }
}

This configuration will replicate all sets and deletes across zones “z1” and “z2”. get requests will first try the proxy’s “local zone” and fall back to the remote zone. This is a simple best effort replication; if you have a network partition or crash the caches will not stay in sync! Keep TTL’s reasonably low. More reliable methods are possible and we will discuss them in future posts.

Keys that start with “/foo/” will be trimmed of the “/foo/” prefix and routed to the listed backend servers. If you wish for non-prefixed keys to have a backup pool, create another pool named “default” and remove “default_pool = nil”

We changed a few more things:

my_zone(os.getenv("MEMCACHE_MYZONE"))

This line lets you set the “local zone” (say “z1”) via an environment variable. If running multiple proxies this lets you keep the configuration files identical on all servers. You can also fetch the zone from a file, or any other method Lua allows for you.

router_type = "keyprefix"

In this configuration we examine the keys themselves to decide which pool to route to. If you wish to use multiple zones but not route by key, setting “router_type” to “flat” will work exactly the same as the first example.

What's next?

This blog post demonstrates the basic capability of the new proxy, and future posts will go deeper into more examples and how to build your own route library.

We’re hoping to find more early adopters. We’re working on polish, iterating on core features, performance, example configurations and libraries, and so on.

There is more complete documentation on the wiki

You can find open issues and worklogs related to the proxy in the issue tracker as well