Shards
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.