So I failed at writing some clustered code in Perl
Until this time next month I'll be posting code-based discussions only.
Recently I've been wanting to explore creating clustered services, because clusters are definitely things I use professionally.
My initial attempt was to write an auto-clustering version of memcached, because that's a useful tool. Writing the core of the service took an hour or so:
- Simple KeyVal.pm implementation.
- Give it the obvious methods get, set, delete.
- Make it more interesting by creating a read-only append-log.
- The logfile will be replayed for clustering.
At the point I was done the following code worked:
use KeyVal; # Create an object, and set some values my $obj = KeyVal->new( logfile => "/tmp/foo.log" ); $obj->incr( "steve" ); $obj->incr( "steve" ); print $obj->get( "steve" ) # prints 2. # Now replay the append-only log my $replay = KeyVal->new( logfile => "/tmp/foo.log" ); $replay->replay(); print $replay->get( "steve" ) # prints 2.
In the first case we used the primitives to increment a value twice, and then fetch it. In the second case we used the logfile the first object created to replay all prior transactions, then output the value.
Neat. The next step was to make it work over a network. Trivial.
Finally I wanted to autodetect peers, and deploy replication. Each host would send out regular messages along the lines of "Do you have updates made since $time?". Any that did would replay the logfile from the given unixtime offset.
However here I ran into problems. Peer discovery was supposed to be basic, and I figured I'd write something that did leader election by magic. Unfortunately Perls threading code is .. unpleasant:
- I wanted to store all known-peers in a singleton.
- Then I wanted to create threads that would announce and receive updates.
This failed. Majorly. Because you cannot launch the implementation of a class-method as a thread. Equally you cannot make a variable which is "complex" shared across threads.
I wrote some demo code which works without packages and a shared singleton:
The Ruby version, by contrast, is much more OO and neater. Meh.
I've now shelved the project.
My next, big, task was to make the network service utterly memcached compatible. That would have been fiddly, but not impossible. Right now I just use a simple line-based network protocol.
I suspect I could have got what I wanted using EventMachine, or similar, but that's a path I've not yet explored, and I'm happy enough with that decision.