Initial Release of Moneta: Unified Key/Value Store API

I'm happy to announce the (very) initial release of a new library called moneta, which aims to provide a unified interface for key/value stores. The interface mirrors the key/value API of hash, but not the iteration API, since iteration is not possible across all key/value stores. It is likely that a future release will add support for enumeration by keeping a list of keys in a key (very meta :P )

In order to prove out the API, I created five highly experimental stores for the initial release:

  • A basic file store: It uses the file name as the key, the file contents as the value, and xattrs to hold expiration information. The file store requires the xattr gem
  • An xattrs-only store: This uses a single file's xattrs for keys and values, and a second file's xattrs to hold an identical hash with expiration information
  • An in-memory store: This was the first store I wrote, purely to prove out the API. It uses a Hash internally, and a second hash for expiration information
  • A DataMapper store: Uses any DataMapper-supported storage with three columns: the_key, value, and expiration
  • A memcached store: Uses memcached and its native expiration

Note that the initial release also does not do any kind of locking; a Moneta Store should be treated as a standard hash, and should be locked appropriately by the consumer. Also note that the stores themselves do not perform any locking, so they should probably not be used between processes at this time (i.e. they are only experimental). It should be pretty straight-forward to add locking to most of the stores.

Likely the only locking issue in the memcached store is in #delete, which does:

def delete(key)
  value = self[key]
  @cache.delete(key) if value
  value
end

As a result, it is possible (at the moment, again) for delete to return a key that has been modified before it was actually deleted. For most use-cases, this is rather unlikely to matter, but keep in mind that pretty much all of the stores are unoptimized and not concurrent-safe across processes. Treat the stores, at the moment, as proofs of concept for the overall API.

Some details

The Moneta API is purposely extremely similar to the Hash API. In order so support an
identical API across stores, it does not support iteration or partial matches, but that
might come in a future release.

The API:

#initialize(options)
options differs per-store, and is used to set up the store

#[](key)
retrieve a key. if the key is not available, return nil

#[]=(key, value)
set a value for a key. if the key is already used, clobber it.
keys set using []= will never expire

#delete(key)
delete the key from the store and return the current value

#key?(key)
true if the key exists, false if it does not

#has_key?(key)
alias for key?

#store(key, value, options)
same as []=, but you can supply an :expires_in option, 
which will specify a number of seconds before the key
should expire. In order to support the same features
across all stores, only full seconds are supported

#update_key(key, options)
updates an existing key with a new :expires_in option.
if the key has already expired, it will not be updated.

#clear
clear all keys in this store