Memcached Functions for MySQL and Moxi: a great combination

Working at Northscale has been a lot of great fun lately! I have finally figured out how to get puppet to set up a stock Amazon instance with everything we need and have been impressed with how you can automate system setup with puppet. I remember when I worked at Grazr how much of a hassle it was for us to have to rebuild systems. Something like Puppet would have been a godsend.

Today I though I would post about how cool moxi is. If you don't know, moxi is a memcached proxy (http://labs.northscale.com/moxi/) which allows you to move any complexity of having to set up the list of memcached servers you are using. Also, moxi has some great features such as:

* De-duplication of requests for popular keys
* A front cache, L1 cache to avoid network hops
* Fire and forget SET (Like an async SET) this means "set a value, but don't wait to know if it was successful"
* Time-outs for setting the maximum time for operations

This post will show you how moxi can be used with the memcached UDFs for MySQL (https://launchpad.net/memcached-udfs). It's really quite amazingly easy. I'll even thrown in compilation of moxi and setup of moxi.

First, obtain the source for moxi which can be found at:

git clone git://github.com/northscale/moxi.git

To build moxi, you will need both libevent and libmemcached. I'll leave it to you to obtain both of those. For libevent, just use the package for either centos or ubuntu. For libmemcached, use the source because many packages available for it are somewhat stale, and libmemcached is actively developed by Trond et al!

Now enter the moxi source directory and do the following:

patrick-galbraiths-macbook-pro:moxi patg$ sh autogen.sh 
libtoolize...
aclocal...
autoheader...
automake...
autoconf...

./configure


make

sudo make install


At this point, moxi is ready to use. Now, depending where you have memcached running, this will determine which servers you proxy to. In this demonstration, I'm using 4 different memcached servers:

* Two locally within my network
* Two Northscale memcached Amazon EC2 images (http://labs.northscale.com/memcached-ami/, ami ami-bf4cadd6). These are great-- you just launch them and then *use* them!

So, to launch moxi (I changed the hostnames to protect the innocent)

moxi -z 33133=127.0.0.1:22122,192.168.1.118:11211,ec2-00-00-00-yy.compute1.amazonaws.com:11211,ec2-00-00-00-xx.compute-1.amazonaws.com:11211 -vvv


So, the syntax is quite simple:

moxi -z "port I want to answer to"="host:port server list I am proxying for"


In this example, just as you can also specify with memcached itself, I'm running with the -vvv flag for verbosity because I'm a geek and this is a great way to see what moxi is doing. Also, I'm running so I'm using port 33133 to answer to.

So, I installed my UDFs on the latest version of MariaDB, which I have installed in /usr/local/maria:

bzr branch lp:memcached-udfs
cd memcached-udfs
sh config/bootstrap
./configure --with-mysql=/usr/local/maria/bin/mysql_config --libdir=/usr/local/maria/lib/mysql/plugin/
make
sudo make install
mysql -u root test < sql/install_functions.sql


At this point, the UDFs are ready to use!

The convenient thing here is that moxi makes it so you don't have to specify a long server list in the memc_servers_set() UDF:

mysql> select memc_servers_set('127.0.0.1:33133');
+-------------------------------------+
| memc_servers_set('127.0.0.1:33133') |
+-------------------------------------+
|                                   0 | 
+-------------------------------------+



Now, I was able to start storing and retrieving data via moxi to my 4 memcached instances:

mysql> select memc_set('key1', 'test value');
+--------------------------------+
| memc_set('key1', 'test value') |
+--------------------------------+
|                              1 | 
+--------------------------------+
1 row in set (0.07 sec)

mysql> select memc_get('key1');
+------------------+
| memc_get('key1') |
+------------------+
| test value       | 
+------------------+
1 row in set (0.08 sec)



And of interest, observe the output of moxi:

cproxy_create on port 33133, downstream 127.0.0.1:22122,192.168.1.118:11211,ec2-72-44-62-67.compute-1.amazonaws.com:11211,ec2-67-202-38-195.compute-1.amazonaws.com:11211
cproxy_listen on port 33133, downstream 127.0.0.1:22122,192.168.1.118:11211,ec2-72-44-62-67.compute-1.amazonaws.com:11211,ec2-67-202-38-195.compute-1.amazonaws.com:11211
<40 server listening (proxy-upstream-ascii)
<40 initialized conn_funcs to default
<41 server listening (proxy-upstream-ascii)
<41 initialized conn_funcs to default
<41 cproxy listening on port 33133
<40 cproxy listening on port 33133
moxi listening on 33133 with 2 conns
<42 new proxy-upstream-ascii client connection
42: going from conn_new_cmd to conn_waiting
42: going from conn_waiting to conn_read
42: going from conn_read to conn_parse_cmd
<42 cproxy_process_upstream_ascii version
>42 VERSION 0.9.6
42: going from conn_parse_cmd to conn_write
42: going from conn_write to conn_new_cmd
42: going from conn_new_cmd to conn_waiting
42: going from conn_waiting to conn_read
<43 new proxy-upstream-ascii client connection
43: going from conn_new_cmd to conn_waiting
43: going from conn_waiting to conn_read
43: going from conn_read to conn_parse_cmd
<43 cproxy_process_upstream_ascii set key1 0 0 10
43: going from conn_parse_cmd to conn_nread
43 pause_upstream_for_downstream
43: going from conn_nread to conn_pause
assign_downstream
cproxy_add_downstream 0 4
cproxy_create_downstream: 127.0.0.1:22122,192.168.1.118:11211,ec2-72-44-62-67.compute-1.amazonaws.com:11211,ec2-67-202-38-195.compute-1.amazonaws.com:11211, 0, 7
release_downstream
check_downstream_config 1
check_downstream_config 1
assign_downstream, matched to upstream 43
<44 new proxy-downstream-ascii client connection
<45 new proxy-downstream-ascii client connection
<46 new proxy-downstream-ascii client connection
<47 new proxy-downstream-ascii client connection
46: going from conn_pause to conn_mwrite
assign_downstream, done
46: going from conn_mwrite to conn_new_cmd
46: going from conn_new_cmd to conn_waiting
46: going from conn_waiting to conn_read
46: going from conn_read to conn_parse_cmd
<46 cproxy_process_a2a_downstream STORED
46: going from conn_parse_cmd to conn_pause
>43 STORED
43: going from conn_pause to conn_write
<46 cproxy_on_pause_downstream_conn
46 release_downstream_conn, downstream_used 1 1
release_downstream
check_downstream_config 1
assign_downstream
assign_downstream, done
43: going from conn_write to conn_new_cmd
43: going from conn_new_cmd to conn_waiting
43: going from conn_waiting to conn_read
43: going from conn_read to conn_parse_cmd
<43 cproxy_process_upstream_ascii quit
43: going from conn_parse_cmd to conn_closing
<43 cproxy_on_close_upstream_conn
<43 connection closed.
<43 new proxy-upstream-ascii client connection
43: going from conn_new_cmd to conn_waiting
43: going from conn_waiting to conn_read
43: going from conn_read to conn_parse_cmd
<43 cproxy_process_upstream_ascii get key1 
43 pause_upstream_for_downstream
43: going from conn_parse_cmd to conn_pause
assign_downstream
cproxy_add_downstream 0 4
cproxy_create_downstream: 127.0.0.1:22122,192.168.1.118:11211,ec2-72-44-62-67.compute-1.amazonaws.com:11211,ec2-67-202-38-195.compute-1.amazonaws.com:11211, 0, 7
release_downstream
check_downstream_config 1
check_downstream_config 1
assign_downstream, matched to upstream 43
<48 new proxy-downstream-ascii client connection
<49 new proxy-downstream-ascii client connection
<50 new proxy-downstream-ascii client connection
<51 new proxy-downstream-ascii client connection
forward multiget get key1  (3 0)
50: going from conn_pause to conn_mwrite
forward multiget nwrite 1 out of 4
assign_downstream, done
50: going from conn_mwrite to conn_new_cmd
50: going from conn_new_cmd to conn_waiting
50: going from conn_waiting to conn_read
50: going from conn_read to conn_parse_cmd
<50 cproxy_process_a2a_downstream VALUE key1 0 10
50: going from conn_parse_cmd to conn_nread
<50 cproxy_process_a2a_downstream_nread 0 200
50: going from conn_nread to conn_new_cmd
<43 cproxy ascii item response success
50: going from conn_new_cmd to conn_parse_cmd
<50 cproxy_process_a2a_downstream END
50: going from conn_parse_cmd to conn_pause
<50 cproxy_on_pause_downstream_conn
50 release_downstream_conn, downstream_used 1 1
release_downstream
43: going from conn_pause to conn_mwrite
check_downstream_config 1
assign_downstream



You can see (yes, this a lot of output) how moxi is talking to the 4 different servers, a simple set and get of the key "key1".. Also you see this concept of "downstream" and "upstream" -- to where the data is actually being stored versus where it is passed to moxi from, for instance a PHP or Perl client, or even another moxi.

So, as you can see, using moxi with the memcached Functions for MySQL (memcached UDFs), you gain even more convenience and ability to store data to memcached. Also, you've seen mention of Northscale's Amazon EC2 Machine Images (AMIs) and how you can easily add more memcached instances for your application's needs.