The dRuby Book

9.3 Drip Compared to Hash

In this section, you’ll learn advanced usage of Drip by comparing it to KVS or Hash.

Using Tags

Drip#write will allow you to store an object with tags. The tags must be instances of String. You can specify multiple tags for one object. You can read with tag names, which lets you retrieve objects easily. By leveraging these tags, you can simulate the behavior of Hash with Drip.

Let’s treat tags as Hash keys. “write with tags” in Drip is equivalent to “set a value to a key” in Hash. “read the latest value with the given tag” is equivalent to reading a value from Hash with the given tag. Since “the latest value” in Drip is equivalent to a value in Hash, “the older than the latest value” in Drip is similar to a Hash with version history.

Accessing Tags with head and read_tag Methods

In this section, we’ll be using the head and read_tag methods.

  ​Drip#head(n=1, tag=nil)​

head returns an array of the first n elements. When you specify tags, then it returns n elements that have the specified tags. head doesn’t block, even if Drip has fewer than n elements. It only views the first n elements.

  ​Drip#read_tag(key, tag, n=1, at_least=1, timeout=nil)​

read_tag has a similar operation to read, but it allows you to specify tags. It only reads elements with the specified tags. If elements newer than the specified keys don’t have at_least elements, then it will block until enough elements arrive. This lets you wait until elements with certain tags are stored.

Experimenting with Tags

Let’s emulate the behavior of Hash using head and read_tag. We’ll keep using the MyDrip we invoked earlier.

First, let’s set a value. This is how you usually set a value in a Hash.

  ​hash['seki.age'] = 29​

And here is the equivalent operation using Drip. You write a value 29 with the tag seki.age.

  ​>> MyDrip.write(29, 'seki.age')​
  ​=> 1313358208178481​

Let’s use head to retrieve the value. Here is the command to take the first element with a seki.age tag.

  ​>> MyDrip.head(1, 'seki.age')​
  ​=> [[1313358208178481, 29, "seki.age"]]​

The element consists of [key, value, tags] as an array. If you’re interested only in reading values, you can assign key and value into different variables as follows:

  ​>> k, v = MyDrip.head(1, 'seki.age')[0]​
  ​=> [[1313358208178481, 29, "seki.age"]]​
  ​>> v​
  ​=> 29​

Let’s reset the value. Here is the equivalent operation in Hash:

  ​hash['seki.age'] = 49​

To change the value of seki.age to 49 in Drip, you do exactly the same as before. You write 49 with the tag seki.age. Let’s try to check the value with head.

  ​>> MyDrip.write(49, 'seki.age')​
  ​=> 1313358584380683​
  ​>> MyDrip.head(1, 'seki.age')​
  ​=> [[1313358584380683, 49, "seki.age"]]​

You can check the version history by retrieving the history data. Let’s use head to take the last ten versions.

  ​>> MyDrip.head(10, 'seki.age')​
  ​=> [[1313358208178481, 29, "seki.age"], [1313358584380683, 49, "seki.age"]]​

We asked for ten elements, but it returned an array with only two elements, because that’s all Drip has for seki.age tags. Multiple results are ordered from older to newer.

What happens if you try to read a nonexistent tag (key in Hash)?

  ​>> MyDrip.head(1, 'sora_h.age')​
  ​=> []​

It returns an empty array. It doesn’t block either. head is a nonblocking operation and returns an empty array if there are no matches.

If you want to wait for a new element of a specific tag, then you should use read_tag.

  ​>> MyDrip.read_tag(0, 'sora_h.age')​

It now blocks. Let’s set up the value from a different terminal.

  ​>> MyDrip.write(12, 'sora_h.age')​
  ​=> 1313359385886937​

This will unblock the read_tag and return the value that you just set.

  ​>> MyDrip.read_tag(0, 'sora_h.age')​
  ​=> [[1313359385886937, 12, "sora_h.age"]]​

Let’s recap again. In this section, we saw that with tags we can simulate the basic operation of setting and reading values from Hash.

The difference is as follows:

You can’t remove an element like you do in Hash, but you can work around by adding nil or another special object that represents the deleted status. As a side effect of not being able to remove elements, you can see the entire history of changes.

I didn’t create keys and each methods on purpose. It’s easy to create them, so I created them once but deleted them later. There are no APIs in Drip at this moment. To implement keys, you need to collect all elements first, but this won’t scale when the number of elements becomes very big. I assume this is why many distributed hash tables don’t have keys.

There are also some similarities with TupleSpace. You can wait for new elements or their changes with read_tag. This is a limited version of read pattern matching in Rinda TupleSpace. You can wait until elements with certain tags arrive. This pattern matching is a lot weaker than Rinda’s pattern matching, but I expect that this is enough for the majority of applications.

When I created Drip, I tried to make the specification narrower than that of Rinda so that it’s simple enough to optimize. Rinda represents an in-memory, Ruby-like luxurious world, whereas Drip represents a simple process coordination mechanism with persistency in mind.

To verify my design expectations, we need a lot more concrete applications.

In the previous two sections, we explored Drip in comparison with Queue and Hash. You can represent some interesting data structures using this simple append-only stream. You can stream the world using Drip because you can traverse most of the data structures one at a time.