We have a phrase around the office: "Do it live!" It comes from the incredible freakout of Bill O'Reilly. We use it to mean something along the lines of, "This is a startup. The plan might change at any time. Changes go to production when we need them to, and we roll with bugs as best we can." Far from encouraging careless, fickle choices, it's a reminder that the camera is on, we're live, and we are actively developing a product that is under close scrutiny.
Luckily, we have the power of Erlang behind us. The dynamic nature of the language and runtime is a fantastic fit for an environment in which things may change at a moment's notice.
Erlang's dynamic nature also came in useful for me on BeerRiot last night. I've blogged about hot code loading before, but last night I dipped into the world of OTP applications and Mnesia.
I realized late yesterday afternoon that I had left the login code in a state where usernames were case-sensitive. People could have signed up as "Bryan" and "BRYAN", even though I already owned the login "bryan". Basically, I was lazy; the username lookup code was roughly:
What I needed to do was downcase both the test name and the stored name, and compare those results. I could have just tossed in a call to string:to_lower and reloaded the login module, except that I'm trying to support UTF-8 everywhere. To downcase a UTF-8 string, I needed another library (because I'm not going to both implementing my own).
Google pointed me in the direction of Starling. Despite the strange build process, starling provides an Erlang interface to the ICU libraries, to enable unicode manipulations. A quick build and test, and we have
Toss an application:start(starling) in the BeerRiot startup code, and everything's set to go ... but why would I want to restart the webserver? Restarting is lame - we're doing it live!
Instead of restarting, we'll connect to the webserver through an erl shell (see my earlier hot code loading post about doing this) and modify the running system. We just need two simple commands to get this done.
Command 1 tells Erlang to add a path to its library loading search. Command 2 starts the starling application. Starling is now up and running, and we can ustring:downcase/1 as much as we want.
But, I really don't want to downcase every stored username every time. It's also kind of nice for people's usernames to display as they typed them, but not require the same capitalization in their login form. So, I'll need to store the downcased version somewhere, in addition to keeping the original. I could put it in a new table, mapping back to the persons table, but it's person data - let's keep it with the person.
I need to add a field to my person record. But if I do that, all of my code looking for a person record of the current format will break. I need to update all of my person records in storage as soon as I load the code with the modified person record definition.
Mnesia gives us just the tool for this: mnesia:transform_table/3. All we have to do is provide a function that knows how to translate an old person record into a new one. Something like this will do:
Stick that code in the person module, where the person record is defined. Now, connect back to the webserver and simply:
There's a short period of time in there, between the ends of commands 3 and 4 where any code that looks up a person record will break. But, it's short, and the entire rest of the site will continue functioning flawlessly.
And that's the amazing power of Erlang. A very brief, very limited hiccup, and new functionality is deployed. Assuming the appropriate code was put in place to start everything up on restart, the system will come up in exactly the state you want it if the server should ever reboot.
Now back to tinkering... :)
Post Copyright © 2009 Bryan Fink