As so often happens, we resist things we don’t understand, in favor of those we do, but if we only take the time to learn…
Geek-dom ahead, you have been warned.
I do almost all of my programming in Perl these days–in fact, for the last decade and a half or so. I’m not interested in getting into a langage war here–I know Perl’s weaknesses as well as its strengths.
Anyway, for the last three years or so, we at AnteSpam have used a Perl script to manage refusing connections from malign hosts and rejecting requests to send mail to non-existent addresses–generally at a sustained rate of several per second, occasionally peaking into dozens per second or more, per server (we have 16 production servers).
Perl has no useful multi-threading, and if we tried to service these requests using one script per connection, we would be screwed–in fact, when we first tried to manage this stuff ourselves, three years ago, our first implementation did just that, and the servers melted; they couldn’t take the load of all of those memory-piggy scripts running at once.
Back in 2007, looking for a solution to this, I found POE, a mature Perl framework that allows you to do event-driven cooperative-multitasking with asynchronous I/O and various other bells and whistles.
It is very good at what it does, and over the last few years I’ve become very conversant with it. We have several very important pieces of our infrastructure written with it, and they work very, very well.
Still, it has some issues, the biggest of which is that you have to write your code in a very particular style–short routines that queue events that are handled by other routines and things like that–things that mean that if you write code to integrate smoothly with POE, it’s going to look very weird if you try to use it outside the POE framework, and if you write your code outside the POE framework, it’s not going to play well with POE.
As a consequence, there are lots of libraries that don’t play well with POE–you can use them, but you loose the smooth cooperative-multitasking and asynchrony; basically, you lose the ability to handle many things at once, at least with low-latency. And people who aren’t used to POE end up looking at your code in bafflement.
Still, this has been a fine solution for us for years, and I’ve resisted changing it, because every time I’ve tried to work with something else–and here I’m thinking specifically of AnyEvent, I just couldn’t see the big benefit. The one time I tried reimplementing something with it, the code got a few lines shorter, but otherwise, it was 6 of one, half-dozen of the other.
And then I had my epiphany.
What I realized is that I could rewrite some code that was duplicated between the “regular” programs, and the high-performance daemons to use AnyEvent in such a way that I could use
the same code for both–when I needed high-peformance cooperative multitasking, I would have it, and when I didn’t need it, the code would look exactly the same.
In effect, I was going to be able to get rid of a huge chunk of duplicative code and use the same code everywhere, transparently. And once I got the basic libraries re-done, there was even more code I was going to be able to merge.
In the space of 24 hours, I rebuilt our low-level LDAP and Memcache access layers to transparently use AnyEvent. I didn’t change any code outside of those libraries, and all tests passed once I was done. That performance-critical daemon I talked about at the beginning–I’ve almost finished rewriting it in the space of a couple of hours.
By making this change, everything is looking cleaner and more straightforward than ever before.
When you have that moment of realization, everything can change.