A map is a data structure that stores <key, value>
pairs and allows two fundamental operations: PUT(key, value)
to add or update a value under a specific key and GET(key)
to retrieve the value associated with a particular key. This makes maps highly useful for quickly retrieving specific values.
A Distributed Hash Table (DHT), on the other hand, is a more sophisticated structure that extends the functionality of maps to a distributed system. In a DHT, data is dispersed across multiple nodes (computers) in a network.
To function effectively, a DHT system must meet two critical criteria:
In DHTs, data assignment to nodes typically works like this:
Key = hash(data)
.In this context, the PUT
and GET
operations involve finding the node that is 'closest' to a key to store the corresponding value or retrieve it. However, how do we find the 'closest' node to a given key in a distributed system?
The answer depends on how we visualize and implement the DHT: as a hash ring or as a binary tree.
A Chord DHT represents the system as a circular hash table (or hash ring), where each node is assigned a position on the ring based on its ID, and each key is assigned to the node that is its successor on the ring.
In contrast, a Kademlia DHT represents the system as a binary tree, with the node ID determining its position in the tree. This tree-based approach helps in efficient routing and searching for nodes in large networks.
In Kademlia, 'distance' is defined using the XOR operator, applied to the IDs (interpreted as non-negative integers) of two nodes, or between a node ID and a key. Essentially, the 'closest' node to a key in Kademlia is the one with the smallest XOR result when the key is XORed with the node's ID.
Having established the rules for how keys are assigned to nodes, we need to define the process for nodes to discover keys stored on other nodes. This task involves striking a balance between the number of other nodes' addresses that each node needs to store (referred to as its 'routing table') and the time it takes to locate a particular key in the network.
In a network where nodes are neither joining nor leaving, one efficient way to manage the discovery of keys is through the use of 'k-buckets'.
In Kademlia, each node maintains a set of k-buckets, where each bucket corresponds to a specific range of distances from the node. Each k-bucket stores the contact information of up to k
other nodes in the network, which fall within the bucket's corresponding distance range. The 'k' in k-buckets refers to this limit on the number of entries in each bucket.
Even with this structure, it's not guaranteed that every node will have information on every other node in the network. There will be situations where a node needs to perform a GET
operation on a key but does not know which other node holds that key.
In such a case, the node will consult its k-buckets and contact the known node that is 'closest' to the desired key (according to the XOR distance metric). This contacted node can then either provide the requested data, if it holds it, or help the requester by forwarding the request to another node that is 'closer' to the key.
In a dynamic network where nodes can join or leave at any time, Kademlia uses 'k-buckets' in a slightly different way. Here, k
is typically chosen to be a number representing nodes that are extremely unlikely to leave the network within a certain time frame (say an hour). Each k-bucket can then store information about up to k
other nodes.
k
closest nodes to a given key in your routing table (using XOR metric).k
closest nodes:k
closest nodes for their own k
closest nodes.k
closest nodes based on the new information received.In case a node leaves the network and cannot respond to queries, it will be replaced in the k-buckets of other nodes, thus maintaining the network's robustness.
To refill a k-bucket after nodes have left, the system performs 'bucket refreshes'. These involve executing lookups on random IDs within the k-bucket's ID range, helping discover active nodes in that range.
When a new node j
joins the network, it must initially know the ID and IP address of at least one other node c
. The new node then performs the following steps:
j
and can add it to their k-buckets if they have room.Moreover, whenever a node learns about a new node that's closer to any of the keys it stores, it can proactively move those keys to the new node by issuing PUT
requests. This helps ensure that keys are optimally distributed in the network.
Kademlia defines four Remote Procedure Calls (RPCs) for nodes to interact:
PING
: Checks if a node is online.STORE
: Instructs a node to store a <key, value>
pair.FIND_NODE
: Takes a 160-bit key and returns information about the k
closest nodes to that key.FIND_VALUE
: Similar to FIND_NODE
but returns the stored value if the key is found, else returns the k
closest nodes to the key.