# Consistent Hashing - Quick Reference Guide

## Quick Start

```java
// 1. Create a consistent hash instance
ConsistentHash<String> hash = new ConsistentHash<>(150);  // 150 virtual nodes

// 2. Add nodes (servers, caches, etc.)
hash.addNode("server1");
hash.addNode("server2");
hash.addNode("server3");

// 3. Route keys to nodes
String node = hash.getNode("user:12345");

// 4. Remove nodes when needed
hash.removeNode("server1");
```

## Core API Reference

### Constructor

```java
// Default: 150 virtual nodes, MD5 hash function
ConsistentHash<T> hash = new ConsistentHash<>();

// Custom virtual nodes
ConsistentHash<T> hash = new ConsistentHash<>(500);

// Custom hash function
ConsistentHash<T> hash = new ConsistentHash<>(150, new MD5HashFunction());
```

### Node Management

```java
// Add a node
hash.addNode(node);

// Remove a node
hash.removeNode(node);

// Check if node exists
boolean exists = hash.containsNode(node);

// Get all nodes
Set<T> nodes = hash.getNodes();

// Count physical nodes
int count = hash.getNodeCount();

// Count virtual nodes
int ringSize = hash.getRingSize();

// Clear all nodes
hash.clear();
```

### Key Routing

```java
// Get responsible node for a key
T node = hash.getNode(key);  // Returns null if ring is empty

// Get keys assigned to a node (for analysis)
List<String> keys = hash.getKeysForNode(node, sampleSize);

// Get preceding node in ring
T predecessor = hash.getPrecedingNode(node);
```

### Analytics & Distribution

```java
// Get distribution across nodes
Map<T, Integer> distribution = hash.getDistribution(sampleSize);
// Returns: {node1: 3000, node2: 3100, node3: 2900} for 10000 keys

// Get formatted statistics
String stats = hash.getStatistics(sampleSize);
System.out.println(stats);
```

## Common Patterns

### Pattern 1: Cache Routing

```java
ConsistentHash<String> cache = new ConsistentHash<>(150);
cache.addNode("cache-01");
cache.addNode("cache-02");
cache.addNode("cache-03");

String cacheServer = cache.getNode("user:" + userId);
Value value = cacheServer.get(key);
if (value == null) {
    value = database.get(key);
    cacheServer.put(key, value);  // Cache miss handling
}
```

### Pattern 2: Load Balancer

```java
ConsistentHash<String> lb = new ConsistentHash<>(150);
lb.addNode("web-server-1");
lb.addNode("web-server-2");
lb.addNode("web-server-3");

// Route request based on session ID
String sessionKey = "session:" + request.getSessionId();
String server = lb.getNode(sessionKey);
request.forward(server);
// Same session always goes to same server
```

### Pattern 3: Database Sharding

```java
ConsistentHash<Integer> sharding = new ConsistentHash<>(150);
sharding.addNode(1);  // Shard 1
sharding.addNode(2);  // Shard 2
sharding.addNode(3);  // Shard 3

Integer shard = sharding.getNode("user:" + userId);
User user = database.shard(shard).get(userId);
```

### Pattern 4: Failure Recovery

```java
ConsistentHash<String> cache = new ConsistentHash<>(150);
cache.addNode("cache-01");
cache.addNode("cache-02");
cache.addNode("cache-03");

// Node fails
cache.removeNode("cache-02");  // ~1/3 of cache-02's keys rehash to others

// Node recovers
cache.addNode("cache-02");     // Automatically rebalances

// Node upgrade/migration
cache.removeNode("cache-01");
cache.addNode("cache-01-v2");  // Minimal disruption
```

## Hash Functions

### MD5HashFunction (Recommended)

```java
ConsistentHash<String> hash = new ConsistentHash<>(150, 
    new ConsistentHash.MD5HashFunction());

// Benefits:
// - Cryptographically strong
// - Uniform distribution
// - No collisions in practice
// - Industry standard
```

### SimpleHashFunction (Fast Alternative)

```java
ConsistentHash<String> hash = new ConsistentHash<>(150, 
    new ConsistentHash.SimpleHashFunction());

// Benefits:
// - Very fast
// - Suitable for non-critical applications
// - Less uniform distribution
```

### Custom Hash Function

```java
ConsistentHash<String> hash = new ConsistentHash<>(150, 
    new ConsistentHash.HashFunction() {
        @Override
        public long hash(String key) {
            // Your implementation
            return Math.abs((long) key.hashCode());
        }
    });
```

## Configuration Recommendations

### Virtual Nodes Per Node Count

| Nodes | Recommended VN | Max Imbalance |
|---|---|---|
| 1-3 | 150 | ~5-10% |
| 3-10 | 150 | ~3-5% |
| 10-50 | 100 | ~3-4% |
| 50-100 | 50 | ~2-3% |
| 100+ | 30 | ~1-2% |

**Rule of Thumb**: Total virtual nodes should be 150-500 for most use cases.

## Performance Tips

### 1. Reduce Virtual Node Count for Large Clusters
```java
// For 100+ nodes
ConsistentHash<String> hash = new ConsistentHash<>(30);  // Still balanced
```

### 2. Cache Node Lookups
```java
// Instead of:
String node = hash.getNode(key);  // O(log n) lookup

// Cache results for bulk operations
Map<String, String> nodeCache = new HashMap<>();
for (String key : keys) {
    nodeCache.put(key, hash.getNode(key));
}
```

### 3. Batch Distribution Queries
```java
// Instead of querying one key at a time
Map<String, Integer> dist = hash.getDistribution(10000);

// This is more efficient than
for (int i = 0; i < 10000; i++) {
    hash.getNode("key:" + i);
}
```

## Troubleshooting

### Problem: Uneven Distribution

**Symptom**: One node gets significantly more keys than others

**Causes**:
1. Too few virtual nodes (< 50)
2. Poor hash function
3. Biased key distribution

**Solutions**:
```java
// Increase virtual nodes
ConsistentHash<String> hash = new ConsistentHash<>(500);

// Use better hash function
hash = new ConsistentHash<>(150, new ConsistentHash.MD5HashFunction());

// Analyze with larger sample
Map<String, Integer> dist = hash.getDistribution(100000);
```

### Problem: Too Many Keys Rehashed

**Symptom**: Most keys move when nodes are added/removed

**Causes**:
1. Poor hash function causing clustering
2. Node ID collision
3. Inconsistent node names

**Solutions**:
```java
// Use MD5 hash function
hash = new ConsistentHash<>(150, new ConsistentHash.MD5HashFunction());

// Ensure unique, consistent node names
hash.addNode("server-1-prod-datacenter-a");  // Consistent identifier

// Test redistribution
Map<String, String> before = captureAssignments(hash, 10000);
hash.addNode("new-server");
Map<String, String> after = captureAssignments(hash, 10000);
int movedCount = countMoved(before, after);
double percentage = (movedCount * 100.0) / 10000;
// Should be ~25-35% for adding 1 node to 3-4 nodes
```

## Testing Checklist

```java
// ✓ Test node addition
hash.addNode("node1");
hash.addNode("node2");
assertEquals(2, hash.getNodeCount());

// ✓ Test node removal
hash.removeNode("node1");
assertEquals(1, hash.getNodeCount());

// ✓ Test key consistency
String node1 = hash.getNode("key");
String node2 = hash.getNode("key");
assertEquals(node1, node2);

// ✓ Test load distribution
Map<String, Integer> dist = hash.getDistribution(10000);
dist.values().forEach(count -> {
    assertTrue(count > 2500 * 0.6);  // At least 60% of ideal
    assertTrue(count < 2500 * 1.4);  // At most 140% of ideal
});

// ✓ Test key redistribution
Map<String, String> before = getAssignments(hash, 5000);
hash.addNode("node3");
Map<String, String> after = getAssignments(hash, 5000);
int moved = countMoved(before, after);
assertTrue(moved > 5000 * 0.2);  // At least 20% moved
assertTrue(moved < 5000 * 0.5);  // Less than 50% moved

// ✓ Test edge cases
hash.addNode("node1");
assertNotNull(hash.getNode(""));
assertNotNull(hash.getNode("a".repeat(10000)));
```

## Common Use Cases

| Use Case | Virtual Nodes | Key Type |
|---|---|---|
| Cache Layer | 150 | Cache Key |
| Load Balancer | 150 | Session ID |
| Database Sharding | 100 | User ID or Partition Key |
| CDN | 200 | Content ID |
| Task Distribution | 50 | Task ID |
| Message Queue | 100 | Message Key |

## Advanced Topics

### Weighted Nodes (Custom Implementation)

```java
// Custom implementation for weighted nodes
public class WeightedConsistentHash<T> {
    private ConsistentHash<T> hash;
    private Map<T, Integer> weights;
    
    public void addWeightedNode(T node, int weight) {
        for (int i = 0; i < weight; i++) {
            hash.addNode(node);  // Add multiple times
        }
    }
}
```

### Replica Placement (For Fault Tolerance)

```java
// Find replicas for a key
public List<String> getReplicaNodes(String key, int replicaCount) {
    List<String> replicas = new ArrayList<>();
    String currentNode = hash.getNode(key);
    replicas.add(currentNode);
    
    for (int i = 1; i < replicaCount; i++) {
        currentNode = hash.getPrecedingNode(currentNode);
        replicas.add(currentNode);
    }
    return replicas;
}
```

## References

- **Original Paper**: "Consistent Hashing and Random Trees" - Karger et al.
- **Memcached**: Uses consistent hashing for node distribution
- **Redis Cluster**: Uses hash slots (similar concept)
- **Cassandra**: Uses token-based consistent hashing
- **DynamoDB**: Uses consistent hashing for partition assignment

---

**Last Updated**: March 2026

