Ayende @ Rahien

Unnatural acts on source code

Scratching an itch: NMemcached

Last night I found myself up at 1 AM, feeling restless. I decided that I need a new project, something that is very simple and will allow me to channel some energy. I decided to pursue something that I have long been curious about, build a Memcached server in .Net. The Memcached server is a distributed cache server that is very popular in the Ruby, PHP & Perl worlds, with some following on both Java & .Net side.

It is written in C, and it is has some fairly interesting characteristics. Chief among them, it is a straightforward technical challenge. I spend about a day on this, but I have a working Memcached server, which can work with the existing clients.

Currently I support all the standard commands:

  • add
  • append
  • cas
  • delete
  • prepend
  • replace
  • set
  • get
  • gets
  • incr
  • decr
  • flush_all
  • quit
  • version

The only thing that I do not support is the stats command. The project has over a hundred unit tests and 34 integration tests and currently stand at 94% test coverage.

All of that said, what will probably interest most people is the performance comparison. I do hope that some people will stop to actually look at the design of the code, but here are the numbers, for reading / writing of 10,000 (small) values (over 10 iterations, using 20 connections):

Native Memcached finish in: 1709.6 ms

NMemcached completes in: 5768.5 ms

Update:

Based on profiling, I decided to make a tiny change to the part of the application that handles reading the commands from the user. Now it is reading them in continuous fashion, instead of one byte at a time. This brought the speed of the NMemcached version to 3144.5 ms, which is a huge benefit for such a small change.

Which put a project that was written in a day at just over 3 times slower than a heavily optimized piece of mature C code. Not bad, if I say so myself. I took a look with dot Trace, to see what was taking most of the time, and it looks like a significant amount goes in the networking section (specifically the LineReader class). There are a lot of tests, so it is not a problem to go and fix this if someone wants to.

You can access the code here: https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/experiments/NMemcached

Comments

cristian
06/06/2008 04:08 PM by
cristian

Good work, checking out the code, btw, why not CacheMan (http://www.sriramkrishnan.com/blog/2008/02/cacheman-fast-distributed-hashtable-for.html) where you bored enough?

Sriram Krishnan
06/06/2008 04:41 PM by
Sriram Krishnan

Interesating - I need to benchmark this against Cacheman on my machine . On my 2.4Ghz dual core dev box with 1gb RAM, I get around 16K read/writes per second.

Ayende Rahien
06/06/2008 05:02 PM by
Ayende Rahien

Cristian,

A/ yes.

B/ code is not available

Vijay Santhanam
06/06/2008 05:38 PM by
Vijay Santhanam

Looks like fun. I'll check this out this weekend.

Why not use MS' Velocity?

Leverett
06/06/2008 06:08 PM by
Leverett

This is very clean. I like its simplicity. It essentially wraps System.Web.Caching.

Marco
06/06/2008 06:12 PM by
Marco

If you're still bored, sometime ago you had some posts about a enterprise system, see http://ayende.com/Blog/archive/2007/11/17/A-vision-of-enterprise-platform.aspx

I love to see what you ideas are about an extensible UI like you describe in that blog post:

Extensible in an easy manner - note that this holds for business analysts and for developers, both are groups that are likely to do work on the system. Ideally we can have some sort of a common interface that would make both people happy.

* New entities

* User Interface:

      o Forms

      o UI elements

      o Editing existing forms

* Replacing core services
Ryan Heath
06/06/2008 06:29 PM by
Ryan Heath

You beat me up and down! :)

Great job!

I am implementing a memcache in C# in my sparse sparetime too.

But I didnt know I could use HttpCache in a service too, oh boy, that alone would have spared me a lot of hours of work.

Perhaps you should take a look at the .net 3.5 socket API which have greater performance compare to the IAsync pattern. And there are also a few SocketOptions which you could set, to get even more performance on the network stack.

// Ryan

Ayende Rahien
06/06/2008 07:09 PM by
Ayende Rahien

Marco,

That takes about a week, and compose a fairly complex architecture.

Ayende Rahien
06/06/2008 07:11 PM by
Ayende Rahien

Sriram,

What is the benchmark that you are running?

I am running a load test, basically open connections and try to happen the server using overlapped IO for getting things out of the client ASAP.

That said, I am assuming that your network code is significant more mature than mine. I paid no attention to perf during this stage

Ayende Rahien
06/06/2008 07:13 PM by
Ayende Rahien

Ryan,

Can you give me a url for that, I would love to see this.

Even better, a patch :-)

pete w
06/06/2008 07:38 PM by
pete w

Lately I've been working less with .NET and I've picked up a project with ruby.

We are working with GemStone which doesnt rely on a traditional relational database, its more like a distributed cache of objects. You have objects on the disk, objects in distributed cache, and objects in worker processes and gemstone handles all of the concurrency.

The big difference is that there is no object mapping/transformations between the disk/cache/memory, they all have the same structure, it is simple byte copies, which makes it screaming fast.

Its written in smalltalk and I would love to interface that in .NET sometime.

This is a decent overview of gemstone:

http://www.avibryant.com/2008/03/index.html

Rob
06/06/2008 08:35 PM by
Rob

Ayende...you're a sick, sick man...

Robert
06/06/2008 08:57 PM by
Robert

Hi Ayende did you have a look at

http://www.codeplex.com/SharedCache/Release/ProjectReleases.aspx?ReleaseId=10755

From my perspective it looks promising.

Robert
06/06/2008 08:57 PM by
Robert

Hi Ayende did you have a look at

http://www.codeplex.com/SharedCache/Release/ProjectReleases.aspx?ReleaseId=10755

From my perspective it looks promising.

Daniel
06/06/2008 11:58 PM by
Daniel

Hi Ayende,

I would second Macro. One week for a powerful and extensible enterprise framework would be a good investment, that will benefit a lot of people. Even a small profiling project would serve.

Jeffrey McManus
06/07/2008 05:52 AM by
Jeffrey McManus

This is spectacular. Thanks for doing it. I know that the native win32 version of memcached has been without a maintainer for some time (a few years, actually, I think) and I'd theorized that if someone did a .NET port it might attract more contributors.

To answer a few people's questions about why use this instead of Velocity or CacheMan -- memcache has been around for years and there is already a large body of knowledge about how to use it to scale up a large dynamic site. There are also memcache client libraries written in .NET (and lots of other languages) already so the support is already there.

It's also worth mentioning that memcache is cross platform so if your application knows how to use memcache it can store cache data on any machine running any OS in your data center, as long as that machine has spare RAM. This is something that Velocity will not likely ever provide.

Ryan Heath
06/07/2008 09:40 AM by
Ryan Heath

Look at http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx

MSDN has some examples too how to use it, and there are a lot of examples availble on the net.

// Ryan

Ayende Rahien
06/07/2008 10:20 AM by
Ayende Rahien

Nice, really nice. I'll have to try that

Steve
06/07/2008 01:58 PM by
Steve

Next time you get restless, setup a asp.net mvc preview 3 project that uses just Boo for controllers, views, etc... uses NHibernate and DI (with Boo and no configuration files)

:)

After that I promise I will switch to boo :)

Federico
06/07/2008 10:53 PM by
Federico

Hi Ayende,

Can you tell me which version of mbUnit are you using? BTW thanks for sharing, it's a nice way to learn.

Mark Hildreth
06/08/2008 03:50 AM by
Mark Hildreth

You know, when you said...

"It is written in C,", for a minute I thought you were talking about your NMemcached project, not the original Memcached :P

gOODiDEA
06/08/2008 05:27 AM by
gOODiDEA

hi Ayende,

can you test it for long time? I think GC will "destroy" cache although you set "System.Web.Caching.Cache.NoSlidingExpiration"

sirrocco
06/08/2008 08:24 AM by
sirrocco

Actually it really shouldn't remove it if you set CacheItemPriority to High ... or if you want even - NotRemovable.

http://msdn.microsoft.com/en-us/library/system.web.caching.cacheitempriority.aspx

Ayende Rahien
06/08/2008 09:42 AM by
Ayende Rahien

gOODiDEA,

It won't. That is why it is on the cache.

Luke Q
06/10/2008 03:38 AM by
Luke Q

At a glance it doesn't look like you allowed a cycle for the JIT to compile the code before the timing starts. The benchmarks that I've worked with allow time for results to converge, e.g. http://dacapobench.org/usage.html. This helps remove the JIT cost which is not really important for long running applications. Can you post some numbers with that approach?

Harry M
06/10/2008 08:37 AM by
Harry M

any chance you could write a cometd server next? :)

Ayende Rahien
06/10/2008 08:37 AM by
Ayende Rahien

This is running the client 10 times, using the exact approaches each time.

I include the first result, but it is not significantly higher than any other

Ayende Rahien
06/10/2008 08:59 AM by
Ayende Rahien

Harry,

Not likely, but a patch is always nice

Harry M
06/10/2008 10:22 AM by
Harry M

I'll get on it :)

Fahad
06/13/2008 02:03 PM by
Fahad

It's awesome to see a .net implementation of memcached. I think it would be great idea to add to the standard memcached protocol to include support for File Dependency and Sql Server dependency.

mycall
06/16/2008 09:20 PM by
mycall

Q: Why did you call you class CacheMixin instead of making them extension methods? Your http://www.ayende.com/Blog/archive/2005/09/19/8285.aspx article isn't make this clear.

Thanks for the awesome project!

Ayende Rahien
06/16/2008 09:26 PM by
Ayende Rahien

I need to keep state, and extension methods do not provide this

Comments have been closed on this topic.