Key value storage can come up for a variety of reasons when you’re writing an application. Sometimes it’s for simple configuration values while other times it can be for massive datasets shared across multiple machines.
Ruby developers have a number of options for this sort of thing.
Typical solutions include:
- Plain old Ruby Hashes (for tiny and ephemeral data)
- ENV vars
- Key/Value Caches like Redis and Memcached
- Traditional SQL and NoSQL Databases
Somewhere in between these options lies Ruby’s PStore Library. It’s a read optimized file backed key/value storage mechanism. It stores Ruby objects in a marshaled binary file format that is lighter on disk space than standard serialization methods. It also lets you store real ruby objects to disk. You’re not confined to usual 6 data types seen with other serialization formats.
When Should I Use PStore?
PStore is a good choice if:
- You need to persist object state across reloads.
- Your dataset is too big or complex for ENV vars.
- Your dataset is too small for a full blown cache server.
- You’re only using the data for one particular process (PStore is thread safe)
When NOT To Use PStore
- You need to share the data across many nodes (Use redis or Memcached)
- The data is unlikely to change during the lifetime of the application and the number of values are small (Just use ENV vars or Ruby constants)
- You need the data to be human readable (Use YAML or JSON serialization)
- You need to search the data (Use a Database)
How Do I Use It?
Basic CRUD operations in PStore are simple.
require 'pstore'
store = PStore.new('my_file.pstore')
store.transaction do
# Write a value to disk.
store[:value1] = "Saved on disk."
# Most Ruby objects can be stored
# directly to disk. Some objects
# can not be stored (such as `Proc`).
store[:value2] = [{}, [], :sym, ""]
# Delete values
store.delete(:value2)
# View all key names:
store.roots
# => :value1
# Read a value:
store[:value1]
# => "Saved on disk."
end
fetch_a_value = store.transaction { store[:value1] }
# => "Saved on disk."
store.transaction do
# Commit()ing transactions- optional, but useful
# if you want to save before finishing a full
# transaction.
store[:value1] = "This string will be saved "\
"because I call commit()"
store.commit
# abort()ing transactions will halt execution and
# revert all changes.
store[:value1] = "This string will never be "\
"saved because I call abort()"
store.abort
store[:never_reached] = "Abort() halts execution."\
"This code is never run."
end
As you can see, PStore has a very straightforward API. Aside from a few options
like thread_safe
and ultra_safe
, there’s not much overhead to get started.
PStore is a simple solution that just might save you the burden of writing an object caching system by hand. For the right use case, it is very effective.
I hope you give this lesser known corner of the Ruby library a look. Questions and comments are always welcome.