Skip to content

Commit

Permalink
Implement an in-memory persistence layer cache
Browse files Browse the repository at this point in the history
Fixes #6
  • Loading branch information
strugee committed Nov 26, 2017
1 parent f8aba3b commit 0a33bf4
Showing 1 changed file with 38 additions and 1 deletion.
39 changes: 38 additions & 1 deletion lib/persistence.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ function computePath(storagePath, key) {
}

function createPersistenceDbFactory(storagePath) {
/*
This is a simple filesystem-backed persistence layer for lazymention. Each key
corresponds to one file - the filename is set to the slash-escaped key and the
value is serialized to JSON and written to the file.
The factory is initialized with a filesystem storage path and an in-memory cache
backed by a Map. When a write occurs, the cache is updated and a file is written
atomically. When a read occurs, the cache is checked and if there's a hit, the
callback is scheduled for invocation with the result. If there's a miss, the
disk is read. If there's a result the contents of the relevant file are
deserialized and passed to the callback; if not an empty object is passed
instead. Note that this means that you're always guaranteed to get an object
back and it will just be empty if nothing's been set before.
Note also that the in-memory cache serves two extremely important functions - it
acts as a performance boost, but it also serves as a consistency construct that
eliminates the need for any sort of locking. Because Node.js is single-threaded,
a write is guaranteed to update the cache in the same tick of the event
loop. Any code that would perform a read while the disk write is still in
progress will hit the cache and not the disk, and so get back the new, correct
result.
*/

var cache = new Map();

return function createPersistenceDb(_dbLog) {
var dbLog = _dbLog.child({component: 'persistence'});

Expand All @@ -48,6 +75,13 @@ function createPersistenceDbFactory(storagePath) {

log.debug({key: key}, 'Retrieving key.');

// XXX should we cache empty results?
if (cache.has(key)) {
log.trace({key: key}, 'Returning key from in-memory cache.');
process.nextTick(cb.bind(undefined, undefined, cache.get(key)));
return;
}

fs.readFile(computePath(storagePath, key), function(err, data) {
// XXX make sure it was the leaf of the directory tree
if (err && err.code === 'ENOENT') {
Expand All @@ -74,9 +108,12 @@ function createPersistenceDbFactory(storagePath) {

log.debug({key: key, value: data}, 'Setting key.');

cache.set(key, data);

writeFileAtomic(computePath(storagePath, key), JSON.stringify(data), function(err) {
if (err) {
log.error({err: err}, 'Error setting key.');
// XXX should we do something more drastic here? Maybe restore the old value or crash?
log.error({err: err}, 'Error setting key; in-memory cache is out-of-sync!');
} else {
log.debug('Set key.');
}
Expand Down

0 comments on commit 0a33bf4

Please sign in to comment.