10

We've taken a good look at Tokyo Cabinet's Hash Database, but there's a lot more to the library than just that. Tokyo Cabinet supports three other kinds of databases. In addition, each database type accepts various tuning parameters that can be used to change its behavior. Each database type and setting involves different tradeoffs so you really have a lot of options for turning Tokyo Cabinet into exactly what you need. Let's look into some of those options now.

The B+Tree Database

Tokyo Cabinet's B+Tree Database is a little slower than the Hash Database we looked at before. That's its downside. However, giving up a little speed gains you several extra features that may just allow you to work smarter instead of faster.

The B+Tree Database is a more advanced form of the Hash Database. What that means is that all of the stuff I showed you in the last article still applies. You can set, read, and remove values by keys, iteration is supported, and you still have access to the neat options like adding to counters. With a B+Tree Database you get all of that and more.

1

Like most key-value stores, Tokyo Cabinet has a very Hash-like interface from Ruby (assuming you use Oklahoma Mixer). You can almost think of a Tokyo Cabinet database as a Hash that just happens to be stored in a file instead of memory. The advantage of that is that your data doesn't have to fit into memory. Luckily, you don't have to pay a big speed penalty to get this disk-backed storage. Tokyo Cabinet is pretty darn fast.

Getting and Setting Keys

Let's have a look at the normal Hash-like methods as well as the file storage aspect:

17

Like any system, Redis has strengths and weaknesses. Some of the biggest positives with Redis are:

It's wicked fast. In fact, it may just be the fastest key-value store.

The collection types and the atomic operations that work on them allow you to model some moderately complex data scenarios. This makes Redis fit some higher order problems where a simple key-value store wouldn't quite be enough.

The snapshot data dumping model can be an asset. You get persistence with Redis, but you pay a minimal penalty for it.

Of course, there are always some minuses. These are the two I consider the most important:

Redis is an in-memory data store, first and foremost. That means your entire dataset must fit completely in RAM and leave enough breathing room for anything else the server must do.

Snapshot backups are not perfect. If Redis fails between snapshots, you can lose data. You need to make sure that's acceptable for any application you use it in.

It may seem weird to call snapshots both a pro and a con, but it does work for you in some ways and against you in others. You have to decide where the trade-off is worth it.

16

[Update: though all of the techniques I show here still apply, many methods of the Redis gem have changed names to match the actual Redis commands they call. There are also easier and more powerful ways to do some of what I show in here, thanks to additions to Redis.]

Redis adds one huge twist to traditional key-value storage: collections. Supporting both lists and sets through some very powerful atomic operations allows for advanced key-value usage.

Lists

Redis allows a single key to hold a list of values. This is your typical ordered list with the operations you would expect: appending, indexed access, and access to a range of values.

This has many potential uses. I'll cover two that I think will be very common. First, if you are going to use Redis as a full database, you store things that are naturally a list of items, like comments, in a real list. Let's look at some code:

#!/usr/bin/env ruby -wKUrequire"redis"CLEAR=`clear`# create an article to comment ondb=Redis.newarticle_id=db.incr("global:next_article_id")article="article:#{article_id}"class<<articledefmethod_missing(field,*args,&blk)returnsuperunlessfield.to_s!~/[!?=]\z/&&args.empty?&&blk.nil?"#{self}:#{field}"endenddb[article.title]="My Favorite Language"db[article.body]="I love Ruby!"# initialize some session detailscomments_per_page=2comment_page=1login=ARGV.shift||"JEG2"loopdo# show articleprintCLEARputs"#{db[article.title]}:"puts" #{db[article.body]}"# paginate commentsstart=comments_per_page*(comment_page-1)finish=start+comments_per_page-1comments=db.list_range(article.comments,start,finish)pagination=Array(start.zero??nil:"(p)revious")pagination<<"(n)ext"ifdb.list_length(article.comments)-1>finish# show commentscomments.eachdo|comment|posted,user,body=comment.split("|",3)puts"----"puts" #{body}"puts" posted by #{user} on #{posted}"end# handle commandsputsprint"Command? [#{(%w[(c)omment (q)uit]+pagination).join(', ')}] "case(command=gets)when/\Ac(?:omment)?\Z/i# add a commentprint"Your comment? "comment=getsorbreakposted=Time.now.strftime('%m/%d/%Y at %H:%M:%S')db.push_tail(article.comments,"#{posted}|#{login}|#{comment.strip}")when/\Ap(?:revious)?\Z/i# view previous page of commentsifpagination.first=~/\A\(p\)/comment_page-=1elseputs"You are on the first page of comments."getsorbreakendwhen/\An(?:ext)?\Z/i# view next page of commentsifpagination.last=~/\A\(n\)/comment_page+=1elseputs"You are on the last page of comments."getsorbreakendwhen/\Aq(?:uit)?\Z/i,nil# exit programbreakendend

15

[Update: though all of the techniques I show here still apply, many methods of the Redis gem have changed names to match the actual Redis commands they call.]

Redis is a first and foremost a server providing key-value storage. As such, the primary features of any client library are for connecting to the server and manipulating those key-value pairs.

Connecting to the Server

Connecting to the Redis server can be as simple as Redis.new, thanks to some defaults in both the server and Ezra's Ruby client library for talking to that server. I won't pass any options to the constructor calls below, but you can use any of the following as needed:

:host if you need to connect to an external host instead of the default 127.0.0.1

:port if you need to use something other than the default port of 6379

:password if you configured Redis to require a password on connection

:db if you want to select one of the multiple configured databases, other than the default of 0 (databases are identified by a zero-based index)

:timeoeut if you want a different timeout for Redis communication than the default of 5 seconds

14

Before we can play with Redis, you will need to get the server running locally. Luckily, that's very easy.

Installing Redis

Building Redis is a simple matter of grabbing the code and compiling it. Once built, you can place the executables in a convenient location in your PATH. On my box, I can do all of that with these commands:

14

I've been playing with a few different key-value stores recently. My choices are pretty popular and you can find documentation for them. However, it can still be a bit of work to relate everything to Ruby specific usage, which is what I care about. Given that, here are my notes on the systems I've used.

About

James Edward Gray II was a part the Ruby community before Rails
ever shipped. He wrote code and documentation that now come with
the language. He ran two Red Dirt Ruby Conferences and is was
a regular on the Ruby Rogues podcast for years. He now creates
videos showing real programming in action.
He does all of this just because he loves to program. This site is
where he writes about that.