Key/Value Server Part Two

Updating the key/value server to persist its state to disk during shutdowns.

In part one we created a key/value server that stores data in memory. Here in part two we'll add a few lines of code to make the server persist its data to disk on shutdown and restore them again on startup. Finally, in part three, we'll refactor the code so that it conforms to Erlang/OTP conventions.

Complete code for this tutorial is available in the GitHub repository.

Adding Persistent State

The existing key/value server works well, but it will lose its state when shut down. Fortunately, it will take only a few lines of new code to improve it.

In the previous tutorial we used the code in the kv.erl module. I've duplicated that code into a module called pkv.erl, which is where we'll extend the server to read and write its data to disk.

The business logic for the change is simple: when the server is shutting down, it needs to write the keys and values from its state dictionary to a disk file. When starting, if that disk file exists the server must load it and parse the contents back into the state dictionary.

Writing the terms two disk requires two functions. First, we'll add

which extracts a list of {Key, Value} tuples from the state dictionary, then passes them to the second function, called unconsult. This function is the logical inverse of the native file:consult/1 function provided by OTP:

The unconsult function accepts a list, opens a file on disk, then writes each item in the list to the file by using the pretty print formatter, ~p, and the io module's native serialization scheme. This results in a data file that is very neat and tidy:

{"beefe116d3c0889262cf492b317e0a024e3103c4","Cole is Canadian."}.
{"64925710385ac7111192c31282c459b8cfc235de","Canadians eat bacon."}.
        

When the server starts back up, this convenient format means we can read the entire disk file with a simple call to file:consult/1 like so:

If the file does not exist, an empty dictionary is returned (as in the first tutorial).

Integrating these new features into the server requires only one new line of code in the start/0 function:

and one new line in the server loop where it handles a {stop} command:

And that's it! Those small additions have converted a non-persistent, in-memory server into a persistent server that retains its state across restarts. The final change to this system is to make it OTP-compliant so that it can be used in an existing Erlang application and included in a supervision tree.