Kademlia Explained: A Deep Dive into Distributed Hash Tables

Arpit Bhayani

Arpit Bhayani

Aug 17, 2022 • 11 min read

Play

Kademlia: A Deep Dive into Distributed Hash Tables

In traditional peer-to-peer (P2P) systems such as early BitTorrent designs, peer discovery relied heavily on a central authority called a tracker. A tracker monitors all participating peers, their upload/download volumes, and provides newly joined peers with a random subset of active peers to bootstrap connectivity.

However, relying on a central tracker creates two fundamental vulnerabilities:

  1. Single Point of Failure (SPOF): If the tracker goes down, the entire torrent collapses.
  2. Target for Attacks: Being a central authority, it is susceptible to targeted attacks, denial of service, and censorship.

To move from a hybrid P2P architecture to a pure P2P overlay network, systems eliminate central trackers in favor of a Distributed Hash Table (DHT). Among DHT designs, Kademlia stands out for its simplicity, efficiency, and deterministic convergence properties.


1. Node and Key Representation

Every entity in a Kademlia network—both participants (nodes/peers) and data objects (keys)—is assigned an identifier within the same key space:

  • Identifiers: Unique 160-bit (20-byte) integers.
  • Node ID: Derived by hashing the node’s IP address (or assigned randomly upon generation) using a cryptographic hash function like SHA-1.
  • Key ID: Derived by hashing the key of the key-value pair being stored using the same hash function.

Because keys and nodes share the identical 160-bit address space, Kademlia can measure closeness between a key and any given node using a uniform distance metric.

+-------------------------------------------------------------+
|                     160-bit Identifier Space                |
|  Node IDs:  SHA-1(Node IP / Port / Random Seed)             |
|  Key IDs:   SHA-1(Data Key)                                 |
+-------------------------------------------------------------+

2. Key Ownership and the Distance Metric

The Ownership Problem

In a distributed network, a mechanism must decide which node holds a given key-value pair: The key KK is assigned to the node(s) with the closest Node ID to KK.

Physical distance or geographic coordinates cannot be used in a virtual network topology. Instead, the network requires a mathematical metric that quantifies “closeness.”

Formal Metric Requirements

Any valid metric d(x,y)d(x, y) in a metric space must satisfy three properties:

  1. Identity of Indiscernibles: d(x,x)=0d(x, x) = 0
  2. Positivity / Non-negativity: d(x,y)>0d(x, y) > 0 if xyx \neq y
  3. Triangle Inequality: d(x,y)+d(y,z)d(x,z)d(x, y) + d(y, z) \ge d(x, z)

The XOR Metric

Kademlia introduces bitwise XOR (\oplus) as its distance function:

d(x,y)=xyd(x, y) = x \oplus y

The computed distance between two 160-bit identifiers is simply the numerical integer representation of their XOR value.

Why XOR Satisfies Metric Space Properties:

  1. d(x,x)=xx=0d(x, x) = x \oplus x = 0.
  2. xy>0x \oplus y > 0 for all xyx \neq y (since identifiers are non-negative and differing bits produce a non-zero integer).
  3. Triangle Inequality Verification: d(x,y)d(y,z)=(xy)(yz)=x(yy)z=xz=d(x,z)d(x, y) \oplus d(y, z) = (x \oplus y) \oplus (y \oplus z) = x \oplus (y \oplus y) \oplus z = x \oplus z = d(x, z) Since bitwise addition modulo 2 satisfies a+baba + b \ge a \oplus b for all non-negative integers: d(x,y)+d(y,z)d(x,y)d(y,z)=d(x,z)d(x, y) + d(y, z) \ge d(x, y) \oplus d(y, z) = d(x, z)

Properties of XOR Distance

  • Symmetry: d(x,y)=d(y,x)d(x, y) = d(y, x) (unlike distance functions used in algorithms like Chord).
  • Prefix Closeness: When XORing two numbers, matching bits from the most significant bit (MSB) downward yield 0. The first differing bit yields 1. Therefore: The longer the shared common prefix between two IDs, the smaller the numerical distance between them.
Example (4-bit space):
  Node A: 1 1 1 1 (15)
  Node B: 1 1 0 1 (13) -> Prefix match: "11" -> Distance = 15 ^ 13 = 2 (0010)
  Node C: 0 1 0 1 (5)  -> Prefix match: ""   -> Distance = 15 ^ 5  = 10 (1010)
  
  Node B is substantially closer to Node A than Node C.

3. Visualizing Distance: The Binary Trie

Because distance correlates directly to shared prefix length, the identifier space can be visualized as a full binary tree (or trie) of depth 160 (or depth 4 in a simplified 4-bit space):

  • Left branches represent bit 1.
  • Right branches represent bit 0.
  • Leaves represent nodes or keys placed at their corresponding binary path.
graph TD
    Root((Root)) -->|1| N1[1...]
    Root -->|0| N0[0...]
    
    N1 -->|1| N11[11..]
    N1 -->|0| N10[10..]
    
    N0 -->|1| N01[01..]
    N0 -->|0| N00[00..]
    
    N11 -->|1| N111[111.]
    N11 -->|0| N110[110.]
    
    N111 -->|1| Leaf15[Node 15: 1111]
    N110 -->|1| Leaf13[Key KB: 1101]

In practice, building a full binary tree for 160 bits would consume immense memory. Instead, nodes prune the tree into a compressed path, only storing populated leaves at the shortest disambiguating depth.


4. Routing Table Architecture and K-Buckets

To route requests without a central coordinator, a node cannot know about every peer in a network of millions. Doing so would require immense bandwidth for broadcast updates and unbounded memory.

The Core Invariant

Every node partitions the identifier space into a series of subtrees based on prefix difference. A node must know at least one contact (and in practice, up to kk contacts) in every subtree that it does not belong to.

For an nn-bit space, a node maintains nn separate lists called kk-buckets:

  • Bucket 0: Contains nodes sharing no common prefix bit (differing at the MSB).
  • Bucket 1: Contains nodes sharing 1 common prefix bit, differing at the second bit.
  • Bucket ii: Contains nodes sharing ii prefix bits, differing at bit i+1i+1.
For Node ID 0100 (4-bit example):
  Subtree 1: Nodes with prefix 1...         (Differs at 1st bit) -> Covers 50% of the network
  Subtree 2: Nodes with prefix 00...        (Differs at 2nd bit) -> Covers 25% of the network
  Subtree 3: Nodes with prefix 011..        (Differs at 3rd bit) -> Covers 12.5% of the network
  Subtree 4: Nodes with prefix 0101         (Differs at 4th bit) -> Closest peers

As a result, a node has fine-grained, detailed routing knowledge about nodes close to it, and progressively coarse-grained knowledge about nodes farther away.

Anatomy of a K-Bucket

  • Capacity (kk): A system-wide parameter (commonly k=20k = 20 in implementations like BitTorrent). It represents the maximum number of contacts stored per bucket.
  • Entry Information: Each entry contains (IP Address, UDP Port, Node ID).
  • Order: Entries in a kk-bucket are ordered by time of last contact:
    • Head: Least Recently Seen (LRS) node.
    • Tail: Most Recently Seen (MRS) node.

The Bucket Replacement Policy (Exploiting Node Longevity)

When a message is received from a peer, the receiver attempts to update its corresponding kk-bucket:

  1. If the peer already exists in the bucket: Move the peer to the tail (most recently seen).
  2. If the peer is new and the bucket is not full: Append the peer to the tail.
  3. If the peer is new and the bucket is full:
    • Send a PING RPC to the node at the head (the least recently seen peer).
    • Case A (Head responds): The head node is alive. Move the head node to the tail of the bucket and discard the new node.
    • Case B (Head fails to respond): The head node is dead. Evict the head node and insert the new node at the tail.
flowchart TD
    Recv[Message Received from Node P] --> Exists{P in k-bucket?}
    Exists -- Yes --> MoveTail[Move P to Tail / MRS]
    Exists -- No --> Full{Is k-bucket Full?}
    Full -- No --> InsertTail[Insert P at Tail / MRS]
    Full -- Yes --> PingHead[PING Node at Head / LRS]
    PingHead --> HeadAlive{Did Head Respond?}
    HeadAlive -- Yes --> KeepOld[Move Head to Tail. Discard P]
    HeadAlive -- No --> Replace[Evict Head. Insert P at Tail]

Why This Strategy Works

Empirical studies of P2P networks show that the longer a node has been online, the higher the statistical probability that it will remain online in the near future. Conversely, newly joined nodes have high churn and churn out quickly. Prioritizing stable, long-running nodes over new nodes builds an exceptionally robust routing table that resists denial-of-service and churn-induced degradation.


5. Routing Convergence: Finding Nodes

Because every node has contacts in every subtree disjoint from its own path, lookups are guaranteed to make forward progress toward the target ID with every hop.

Convergence Walkthrough

Assume a 4-bit space where Node N1N_1 (0000) wants to find Node N2N_2 (1111):

  1. N1N_1 consults its routing table for the node closest to 1111. Because N1N_1 maintains contacts in the subtree starting with 1..., it selects node NAN_A (1000).
  2. N1N_1 queries NAN_A: “Who do you know that is closest to 1111?”
  3. NAN_A looks up its routing table. NAN_A has a contact in the subtree starting with 11..., say node NBN_B (1101), and returns NBN_B‘s contact info to N1N_1.
  4. N1N_1 queries NBN_B: “Who do you know that is closest to 1111?”
  5. NBN_B directly knows N2N_2 (1111) and returns N2N_2‘s network address to N1N_1.
  6. N1N_1 successfully contacts N2N_2.

Each step halves the remaining distance in the identifier space. Consequently, lookups converge in O(logN)O(\log N) hops, where NN is the total number of nodes in the system.

Important Routing Detail: Intermediate nodes do not act as proxies. They do not forward the message recursively. Instead, Kademlia employs an iterative lookup process: the querying node receives the contact addresses of the kk closest nodes and fires subsequent queries itself.


6. The Core RPC Protocol

Kademlia defines four primary Remote Procedure Calls (RPCs), typically transmitted over UDP:

RPCParametersReturn ValuePurpose
PINGNoneNone / ACKProbes a node to verify if it is alive. Used for kk-bucket maintenance.
STOREkey, valueStatus (Success/Failure)Instructs a node to store a key-value pair locally.
FIND_NODEnode_idkk closest contactsReturns the (IP, Port, ID) of the kk closest nodes known to the receiver relative to the target node_id.
FIND_VALUEkeyStored value OR kk closest contactsIf the receiver holds the key, it returns the value. Otherwise, it behaves like FIND_NODE and returns the kk closest contacts.

Iterative Node Lookup Algorithm

To perform a lookup for target ID TT:

  1. The initiating node identifies the α\alpha closest nodes to TT from its local kk-buckets (where α\alpha is a system concurrency parameter, typically α=3\alpha = 3).
  2. The initiator sends parallel FIND_NODE (or FIND_VALUE) RPCs to these α\alpha nodes.
  3. Upon receiving responses containing closer nodes, the initiator updates its closest-nodes set and sends further parallel queries to the newly discovered closest nodes.
  4. If a round of requests fails to return any node closer than the closest already seen, the initiator queries all of the kk closest nodes not yet contacted.
  5. The search terminates when the closest kk nodes have been queried and have responded, or when FIND_VALUE successfully returns the data.

7. Storing and Caching Data

Storing Key-Value Pairs

When an application instructs a node to store a key-value pair (K, V):

  1. The initiating node runs an iterative lookup to find the kk closest nodes to key KK.
  2. Once the kk closest nodes are located, the initiator issues parallel STORE RPCs to each of those kk nodes.

Storing the data across kk separate nodes ensures durability. Even if several nodes suddenly leave the network or crash, the data remains retrievable from the surviving nodes.

Lookup-Path Caching Optimization

To optimize hot-key lookups and reduce load on the closest nodes:

  • When a node queries along an iterative path and eventually retrieves a value from the target node, it can issue a STORE RPC to the closest node that queried it but did not hold the key.
  • This effectively caches the key-value pair along the search path.
  • As a popular key is repeatedly requested, copies proliferate closer to the network periphery, dramatically decreasing lookup latency and balancing read throughput.

8. Summary of Architectural Advantages

  • No Centralized Vulnerability: Eliminates trackers or masters, making the overlay immune to targeted infrastructure attacks.
  • Symmetric and Unidirectional Distance: XOR distance allows a node to receive routing information from queries it receives without sending extra synchronization messages.
  • Deterministic Convergence: The strict prefix partitioning in routing tables guarantees convergence to the target in O(logN)O(\log N) steps without dead ends or loops.
  • Robustness Against Churn: The least-recently-seen replacement policy systematically favors long-lived peers, shielding the DHT from churn caused by short-lived nodes joining and leaving.
Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses