Chord is a protocol and algorithm for a peer-to-peer distributed hash table.
- Nodes and keys are assigned an
m-bit identifier (ID). - Therefore at most a number of
2^mnodes are allowed in the system. - A base hashing function will calculate this identifier (ID), e.g. SHA-1 (
m=160) by applying it on the IPAddr of the node or the keys themselves - The nodes are arranged in an identifier circle
2^m(Chord Circle) - A key
kgets assigned to nodenwhose identifier (ID) is greater or equal (>=) to the identifier (ID) of the keyk - This node is called the successor node of
k - Chord only provides one function: Find for a given key
kthe corresponding noden. Chord doesn't provide functionality actually to secure data on the on the responsible nodes.
- Linear search provides a simple technique to query a node within the Chord Circle.
- Every node
nonly knows its successorsn' (Successor). No more information is needed.
n.find_successor(id)
if ( id ∈ (n, successor] )
return successor;
else
return successor.find_successor(id);- If a query gets relayed to a node, this node verifies if its successor has the queried key. If so, the search is finished.
- Otherwise, the query gets passed on to the succeeding nodes until its target is reached.
- This query is inefficient because the length of the search path is linear to the number of nodes in the system.
- Linear search provides a fallback variant.
- Therefore the minimal requirement of linear search would be that every node knows its successor.
- Consequently, linear search constitutes the minimum requirement of the correctness of the Chord protocol.
- To provide a more efficient search more information about the Chord Circle is needed.
- Every node gets a table with references to
mfurther nodes, wherebymconstitutes the number of bits of the used identifiers (IDs). - The references are called
Finger. The table is calledFingertable.
- The first entry of the table is actually the node's immediate successor (therefore an extra successor field is not needed).
- The start value of the
i-th table's entry of nodenis occupied byn + 2^(i-1). The node-value of this entry points to the first node which follows nodenwith a distance of a least2^(i-1).
n.find_successor(id)
n' = find_predecessor(id);
return n'.successor;
n.find_predecessor(id)
n' = n;
while (id ∉ (n', n'.successor])
n' = n'.closest_preceding_finger(id);
return n';
/* This is a non-transparent approach.
A transparent version would instead forward the query to the finger-node
than access the finger-node directly. */
n.closest_preceding_finger(id)
for i = m downto 1
if (finger[i].Knoten ∈ (n, id))
return finger[i].Knoten;
return n;- If node
ngets a search query after key IDkit looks for- closest, known predecessor of
kin its fingertable - by scanning the fingertable vom bottom to top
- until the first entry
iis found whose node is not located between nodenand key IDk - therefore finger entry
i+1constitutes the closest predecessor of thekwhich nodenis aware of
- closest, known predecessor of
- The search continues with the help of the in the
i+1finger entry`s referenced node. - The search continues until the node is found whose
successoris responsible fork.
- Node
8gets a search query after key ID3. - Therefore this node searches at first for its closest, known predecessor of
3, which is node1and forwards the search query to this node. - Node
1determines that its successor (node4) is responsible for this key with ID3and finally ends the search.
Structure of fingertable of node n=8
| # | start (n + 2^(k-1)) | node |
|---|---|---|
| 1 | 9 | 11 |
| 2 | 10 | 11 |
| 3 | 12 | 14 |
| 4 | 16 | 17 |
| 5 | 24 | 1 |
finger[k].Start = (n+2k – 1) mod 2m; (1 ≤ k ≤ m)
finger[k].Knoten = erster Knoten ≥ finger[k].Start;
successor = finger[1].Knoten;- To ensure correct lookups all successor pointers must be up to date.
- Therefore, a stabilisation protocol is running periodically in the background which updates finger tables and successor pointers.
n.stabilize()
x = successor.predecessor;
if( x ∈ (n, successor) )
successor = x;
successor.notify(n);
n.notify(n')
if ( predecessor is nil or n' ∈ (predecessor, n) )
predecessor = n';
n.fix_fingers()
for i = 1 to m
finger[i].Knoten = find_successor(finger[i].Start);stabilize():nasks its successor for its predecessorpand decides whetherpshould ben‘s successor instead (this is the case ifprecently joined the system).notify(): notifiesn‘s successor of its existence so that it can change its predecessor tonfix_fingers(): updates finger tables
-
Whenever a new node joins, three invariants should be maintained (the first two ensure correctness and the last one keeps querying fast)
- Each node's successor points to its immediate successor correctly.
- Each key is stored in
successor(k). - Each node's finger table should be correct.
-
To satisfy these invariants a
predecessorfield is maintained for each node. -
The following tasks should be done for a newly joined node
n:- Initialise node
n(the predecessor and the finger table). - Notify other nodes to update their predecessors and finger tables.
- The new node takes over its responsible keys from its successor.
- Initialise node
n.join(n')
predecessor = nil;
successor = n'.find_successor(n);- The predecessor of
ncan be easily obtained from the predecessor ofsuccessor(n)(in the previous circle). - As for its finger table, there are various initialisation methods.
- The best method is to initialise the finger table from its immediate neighbours and make some updates, which is
O(log N).
