A shard is a concrete representation of the values of a range of keys [lo,hi) in the directory. The protocol uses shards to send directory data between servers. Shards have to be a POD datatype because we send them over the network. Shards are encoded so that if a key does not occur in the array, then its value is implicitly the background value 0. This means the interval [lo,hi) can be much larger than the number of keys actually represented in the shard.

We implement a shard as a struct containing the bounds lo and hi, represented by key iterators, and an array of key/value pair that maps each key in the range [lo,hi) that is present in the hash table to its value.

To represent the key/value array, we use the keyvalue module from the standard library. The specification of keyvalue provides a relation key_at(A,I,K) indicating that in array A, index I contains key K. There is also a function value_at(A,I) that yields the value at index I. Specifying the keys with a relation rather than a function is very helpful in this application, because it let’s use write a property like “every key K with property p is present in the array” without creating a function cycle. It’s worth having a look at the module keyval in collections.ivy, since it uses a very typical approach to specifying a containter type.

Meanwhile, here is the definition of shards:

include order
include collections

module table_shard(key,data) = {

    instance index : unbounded_sequence
    instance kvt : keyval(index,key.t,data)

    type t = struct {
        lo : key.iter.t,
        hi : key.iter.t,
        kv : kvt.t
    }

    relation key_at(S:t,I:index.t,X:key.t) = kv(S).key_at(I,X)
    function value_at(S:t,I:index.t) = kv(S).value_at(I)

    function value(S:t,X:key.t) = some Y. key_at(S,Y,X) in value_at(S,Y) else 0
    function valid(s:t) = forall X,Y,Z. key_at(s,Y,X) & key_at(s,Z,X) -> Y = Z

}

For convenience, we define key_at and value_at as shorthands for the corresponding members of keyval.

The representation function value returns some data associated with a key if the key occurs anywhere in the shard, otherwise it returns the background value 0. This gives the key/value map associated with the shard. The some quantifier in the definition of value introduces an implicit function from keys to their positions in the array. This is not a problem, however, since there is no function in the interface from positions back to keys.

The valid predicate tells us that a given key does not accur twice in the array. In particular, in a valid shard, the value function is uniquely defined. The predicate valid is an object invariant that users of table_shard will have to carry around.