Wednesday, July 28, 2010

libmemcached on win32

It's a fact that people love their development platform and want to stick with it. I'm a die hard Solaris fan, and would never dream of switching to something else. I've heard that there is a crowd out there that likes to work on other systems like Windows, MacOSX, BSD and Linux. That the developer use a platform during development doesn't necessary mean that the target product will run on the platform, but the developer is more productive on that platform.

People can argue as much as they want, but there is a large crowd of developers using Windows. There is also a large number of systems running some version of Windows, so enabling them to use the projects I'm working on is a good thing. Earlier today I pushed a branch that adds support for building libmemcached into a dll on Windows.

90% of the source code in libmemcached is just "logic" that applies for all platforms, but there is a small part of the code that interacts tightly with the operating system. "Everything" is a file descriptor on Unix systems, but Windows got their own subsystem for sockets called WinSock. In order to avoid getting tonns of #ifdefs all over the code, I defined memcached_socket_t to represent a socket object, and used "the WinSock way" to implement the code. It is pretty easy to map the WinSock code to work on Unix systems with a couple of macros.

Well, enough talk. If you're interested in the details you can check out the branch on Launchpad.

The easiest way for you to test out the code is to install the fullinstall of msysgit. Unfortunately it doesn't come with all of the tools needed to build libmemcached (you can't generate a configure script and generate the documentation). This means that you cannot build the development branch unless you got another machine where you can generate the configure script. I am exporting parts of my ZFS filesystem via CIFS, so I generated the configure script on Solaris.

Building libmemcached with mingw is just as easy as on your favorite platform:

$ ./configure --with-memcached= --without-docs
$ make all install

I haven't fixed the test suite yet, so you have to wait a bit longer before you can run make test ;)

Tuesday, March 30, 2010

Building Memcached on Windows

I like to be able to compile my software on multiple platforms with different compilers, because it force me to write that complies to standards (if not you'll most likely have a spaghetti of #ifdefs all over your code). One of the bonuses you get from using multiple compilers is that you'll get "more eyes" looking at your code, and they may warn you on different things. Supporting multiple platforms will also make it easier to ensure that you don't have 'hidden bombs' in your code (byte order, alignment, assumptions that you can dereference a NIL pointer etc)...

Building software that runs on different flavors of Unix/Linux isn't that hard, but adding support for Microsoft Windows offers some challenges. Among the challenges are:

  1. Microsoft Windows doesn't provide the headerfiles found on most Unix-like systems (unistd.h etc).
  2. Sockets is implemented in a separate "subsystem" on windows.
  3. Win32 provides another threads/mutex implementation than what most software written for Unix-like systems use (pthreads).
  4. Microsoft doesn't supply a C99 compiler. One would think that it would be safe to assume that one could use a C standard that is more than 10 years old....

I am really glad to announce that we just pushed a number of changesets to memcached to allow you to build memcached on windows "just as easy" as you would on your favorite Unix/Linux system. This means that as of today users using Microsoft Windows is no longer stuck with an ancient version!

`The first thing you need to do in order to build memcached on Windows is to install the "fullversion" of msysgit. In addition to installing git, it also installs a compiler capable of building a 32 bit version of memcached.

So let's go ahead and build the dependencies we need to get our 32bit memcached version up and running!

The first thing up is libevent. You should download the latest 2.x release available (there is a bug in all versions up to (and including) 2.0.4, so unless there is a new one out there you can grab a development version I pushed to libevent-2.0.4-alpha-dev.tar.gz), and install it with the following commands (from within your msysgit-shell). Please note that I'm using /tmp because I've had problems using my "home directory" because of spaces in the directory name (or at least I guess that's the reason ;-) ):

$ cd /tmp
$ tar xfz libevent-2.0.4-alpha-dev.tar.gz
$ cd libevent-2.0.4-alpha-dev
$ ./configure --prefix=/usr/local
$ make all                    (this will fail with an error, but don't care about that.. it's is in the example code)
$ make install             (this will fail with an error, but don't care about that.. it's just an example)

It's time to start build memcached!!! Let's check out the source code and build it!

$ git clone git://github.com/trondn/memcached.git
$ git checkout -t origin/engine
$ make -f win32/Makefile.mingw

You should now be able to start memcached with the following command:

$ ./memcached.exe -E ./libs/default_engine.so

Go ahead and telnet to port 11211 and issue the "stats" command to verify that it works!

You don't need to install msysgit on the systems where you want to run memcached, but you do need to include pthreadGC2.dll from the msysgit distribution. That means that if you want to run memcached on another machine you need to copy the following files: memcached.exe .libs/default_engine.so and /mingw/bin/pthreadGC2.dll (Just place them in the same directory on the machine you want to run memcached on :-)

But wait, the world is 64 bit by now. Lets build a 64 bit binary instead

The msysgit we installed previously isn't capable of building a 64-bit binary, so we need to install a new compiler. Download the a bundle from http://sourceforge.net/projects/mingw-w64/files/ (I used mingw-w64-bin_i686-mingw_20100129.zip ). You can "install" it by running the following commands in our msysgit shell:

$ mkdir /mingw64
$ cd /mingw64
$ unzip /mingw-w64-bin_i686-minw_20100129.zip
$ export PATH=/mingw64/bin:/usr/local/bin:$PATH

So let's start compiling libevent:

$ cd /tmp
$ tar xfz libevent-2.0.4-alpha-dev.tar.gz
$ cd libevent-2.0.4-alpha-dev
$ ./configure --prefix=/usr/local --host=x86_64-w64-mingw32 --build=i686-pc-mingw32
$ make all
$ make install

The compiler we just downloaded didn't come with a 64-bit version of pthreads, so we have to download and build that ourself. I've pushed a package to my machine at: pthreads-w64-2-8-0-release.tar.gz

$ cd /tmp
$ tar xfz pthreads-w64-2-8-0-release.tar.gz
$ cd pthreads-w64-2-8-0-release
$ make clean GC CC=x86_64-w64-mingw32-gcc
$ cp pthread.h semaphore.h sched.h /usr/local/include
$ cp libpthreadGC2.a /usr/local/lib/libpthread.a
$ cp pthreadGC2.dll /usr/local/lib
$ cp pthreadGC2.dll /usr/local/bin

And finally memcached with:

$ git clone git://github.com/trondn/memcached.git
$ git checkout -t origin/engine
$ make -f win32/Makefile.mingw CC=x86_64-w64-mingw32-gcc

So how does it perform?

I'm pretty sure a lot of the "Linux fanboys" are ready to jump in and tell how much faster the Linux version is. I have to admit that I was a bit skeptical in the beginning if the layers we added to get "Unix compatibility" had any performance impact. The only way to know for sure is to run a benchmark to measure the performance.

I ran a small benchmark where I had my client create 512 connections to the server, and then randomly choosing a connection and perform a get or a set operation on one of the 100 000 items I had stored in the server. I ran 500 000 operation (33% of the operations are set operations), and calculated the average time. I can dual-boot one of my machines into Windows 7 and Red Hat Enterprise Linux, so my results shows the "out of the box"-numbers between Windows 7 and Red Hat Enterprise Linux running on the same hardware. I used my OpenSolaris box to drive the test (connected to the same switch). Posting numbers is always "dangerous", because people put too much into the absolute numbers. The intention with my graphs is to show that the version running on Microsoft Windows is pretty much "on par" with the Linux version.

256 byte userdata

512 byte userdata

1024 byte userdata

Wednesday, March 3, 2010

Memcached with SASL on OpenSolaris - part 2

It turns out that some systems doesn't support shadow.h and fgetspent, so I just updated the patch to no longer require them.. To create the file, simply do: echo "myuser:mypass" >> my_sasl_pwdb Happy hacking

Thursday, February 25, 2010

Memcached with SASL on OpenSolaris

You may have tried to build memcached with SASL support on OpenSolaris with the following result:

trond@opensolaris< ./configure --enable-sasl
[ ... cut ... ]
checking sasl/sasl.h usability... yes
checking sasl/sasl.h presence... yes
checking for sasl/sasl.h... yes
checking for library containing sasl_server_init... no
configure: error: Failed to locate the library containing sasl_server_init

This is because configure only tries to look for sasl_server_init in libsasl2, and OpenSolaris use libsasl instead. Yesterday I pushed a fix to my github repository that search for the symbol in libsasl as well.

Configuring SASL may also be a challenge (I want to spend my time writing code, not be a system administrator), so I decided to add support for plaintext passwords as well. You probably don't want to use this on your production servers, but it comes in really handy if you just want to test your favorite client.

You enable support for plaintext password by passing --enable-sasl-pwdb to configure. I didn't want to spend any time to writing a new parser or come up with a new file format, so I decided to use fgetspent_r to read the password file. This means that as long as you follow the format for a shadow file, you're good to go :-) You have to set the name of the file to use as the password file in the environment variable MEMCACHED_SASL_PWDB:

trond@opensolaris> echo "myname:mypass:::::::" > /tmp/memcached-sasl-db
trond@opensolaris> export MEMCACHED_SASL_PWDB=/tmp/memcached-sasl-db

With a password file in place, you have to create a config file for SASL to instruct it to use plain text password authentication:

trond@opensolaris> echo "mech_list: plain" > memcached.conf

If you don't want to install this as the global configuration for memcached, you should specify the location of the file in SASL_CONF_PATH:

trond@opensolaris> export SASL_CONF_PATH=`pwd`/memcached.conf

You then start the memcached deamon with "-S" to enable SASL authentication:

trond@opensolaris> ./memcached -S -d

So let's run some commands to the server and see how this works. I'm using the SASL support I implemented in libmemcached (not integrated yet, but you may download it from https://code.launchpad.net/~trond-norbye/libmemcached/sasl):

trond@opensolaris> ./memcp --servers=localhost:11211 --binary \
                                                    --username=myname --password=inncorrect \
                                                    memcp.c
memcp: memcp.c: memcache error AUTHENTICATION FAILURE
trond@opensolaris> ./memcp --servers=localhost:11211 --binary \
                                                    --username=myname --password=mypass \
                                                    memcp.c
trond@opensolaris> ./memcat --servers=localhost:11211 --binary \
                                                     --username=myname --password=mypass \
                                                     memcp.c
[ ... output of memcp.c ... ]
That's all for now. Happy hacking :-)

Thursday, February 11, 2010

New opportunities

I've been really quiet lately, so I guess I should come with a short update on what I'm up to. As of 2010 I am no longer a Sun Microsystems employee, so I spent January in Mountain View in the offices of my new employer: NorthScale. I'm currently working on getting up to speed on my new tasks, so I'll be back with more information later.

Thursday, December 24, 2009

2009

2009 has been a really interesting year for me. We didn't have much snow during the holiday season, but during the first weeks in January we got a lot causing a complete chaos in the traffic. I spent two hours driving to work and two hours driving home one day (I normally only use 20-30 minutes), so I decided to work from home until things got back to normal.
Eric Lambert visited from California in February to start planning our next sprint on our project. "Unfortunately" he didn't get to see any extreme winter weather, but it was nice to be able to work face to face with my team-mate instead of only discussing on IRC.
Unfortunately for me I can't spend as much time as I want with my brother and his family, because they live in California. I have however been so lucky the last few years that I have been able to stay at his place for a month and work from there. Last year was no exception, but this time I was also giving a presentation on MySQL Users Conference. The users conference is a highlight of the year for me, because I meet so many good friends from all over the world there.
I've seen a lot of movies and heard a lot about Alcatraz, but I have never found the time to go there. It was therefore a really pleasant surprise when Matt Ingenthron asked me if we should go there a weekend. Everything there was much smaller than I had imagined (the cells are really tiny). The weather was really nice that day, but it was kind of cold there anyway.. I can really imagine myself how it must have been to stay there on a winters night...
During the Users Conference I also left Suns Database Group, and joined a fantastic team lead by Lee Bieber (aka the Drizzle team).
We spent the summer vacation on Oslo this year, going to amusement parks and museums. One of the highlights this years was my cousins wedding in Geiranger. We went on a 3 hour long boat trip in the fjords before we arrived at the hotel, so I got to see "syv søstre" and "friaren" up close. Thats a memory for life. The view out my hotel window was really awesome!
In September I went to Seattle for a team meeting, and it was really great to see the rest of the team again (You can see the team at http://www.flickr.com/photos/brianaker/3834956962/ ). Working from Norway I spend a lot of time talking with them on telephone / IRC, so it is really great to meet them face to face once in a while. Luckily for me I got to meet other friends during my stay there. Dustin Sallings, Steve Yen and Patrick Galbraith from NorthScale joined in on the open events to discuss the community work on Drizzle / Gearman and Memcached.
Back in Trondheim I continued the renovation om my house. I am going to build two rooms in the garage attached to my house, so I started by tearing down the wall and constructing a new wall. I'm 99% done with everything on the outside now, but I need to find time to finish up inside.
This was a quick summary of 2009, and I am pretty sure that 2010 will become even more exciting!!

Sunday, December 20, 2009

Persistent storage engine

A lot of people keep asking about persistent storage engines for memcached, so I thought that I should create an example to show you how easy it is to create a storage engine that stores the items on disk for persistence. Please note that this is an example on how to do it, not a highly tuned version for performance (that would be up to you to implement ;-)) You wouldn't want to access the filesystem every time you want to access an item, so I'm going to beef the example up a bit by creating a two-level cache. All items will eventually be stored down to disk, but I will serve all of my items from memory. You might think that this sounds like a lot of work to implement, but you couldn't be more wrong about that. The way the default storage engine in memcached is implemented makes it a perfect starting point for us. I've pushed the source code I'm going to discuss to git://github.com/trondn/memcached-engines.git, and you should look at the source code in src/persistent. So how does this thing work? The short answer there is that whenever you store an item into the cache, I will also store the item in my persistent layer. We don't want to create a dead slow cache, so I am going to do the actual storing to the persistent media asynchronously. To speed up things more, I'm not going to let the application have to wait for the data to be written to the persistent media. The drawback for this is that the application will never know if anything failed while I tried to write the object to a persistent media. Whenever the user tries to get an object from the cache, I'll search the memory table first and if it isn't there, I'll read it from the persistent media and return it to the caller. I've decided to use SQLite for my persistence layer, so I created two extra threads in my engine:
  • SQLiteReader to read items from the database and store them in memory
  • SQLiteWriter to write items from memory to the database
Now let's look at the details.. When you try to store (add, set, replace etc) an item in memcached, you will eventually end up in do_store_item in items.c. This is the first place we are going to make some modifications. To keep the example simple I am not going to implement a proper add, append, prepend and replace function (to be specific, I am not going to check if they are in the persistent media if they aren't located in memory when you call the specific command. It should be pretty obvious how to implement that if you want it, so let's rather keep the example easy to understand. At the end of do_store_item we know if the item was successfully inserted into memcached, and this is where I ship the item to my persistent layer.:
if (stored == ENGINE_SUCCESS) {
   *cas = item_get_cas(&it->item);
   if (notify) {
       sqlite_io_store_item(engine, it);
   }
}
You will find the implementation for sqlite_io_store_item in sqlite.cc, and if you look at the code there you will see that all it does is to bump the reference counter for the item (to ensure that the object isn't evicted from the cache), and put the item into the work queue for the writer thread. With this simple modification to the default engine, I was able to always store the items in the SQLite database when I was storing items to the cache. To verify that this worked, I created the SQLiteCacheWarmup class that does a simple "select * from kv;" and inserts the content into the database during startup. The above code works perfectly if your entire cache set fits in memory, but if you start to evict items you would probably want to be able to asynchronously get items back into the cache again. What I did here was to add the following code snippet to my implementation of the get function in the engine API:
hash_item *it = item_get(engine, key, nkey);
if (it != NULL) {
   *item = &it->item;
   return ENGINE_SUCCESS;
} else {
    sqlite_io_get_item(engine, cookie, key, nkey);
    return ENGINE_EWOULDBLOCK;
}
The real magic here is the sqlite_io_get_item() function. What it does is to put a request in the SQLiteReader threads queue to load the item identified by the key. The SQLiteReader thread will try to read the item from the database and insert it into memory before it will call the function notify_io_complete() from the engine interface when it is done (with either ENGINE_SUCCESS, or ENGINE_KEY_ENOENT if the key isn't in the database either). Please note that the asynchronous interface in the core memcached server isn't fully implemented yet, so you need to pull my engine branch for the memcached server in order to try it out. Happy hacking :-)