Rick Carlino

Personal blog of Rick Carlino, senior software engineer at Qualia Labs, co-founder of Fox.Build Makerspace. Former co-founder of FarmBot.

Persistent Key/Value Storage via Ruby Standard Lib

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.

If you enjoyed this article, please consider sharing it on sites like Hacker News or Lobsters.