# How Fabrica-Util's Consistent Hashing Implementation Distributes Game Load Across Server Instances

> Discover how Fabrica-util's consistent hashing distributes game load across servers. Learn about its ring architecture, virtual spots, and Murmur3 hashing for efficient scaling and O(log N) lookups.

- Repository: [Pantheon/fabrica-util](https://github.com/go-pantheon/fabrica-util)
- Tags: how-to-guide
- Published: 2026-03-02

---

**Fabrica-util's consistent hashing implementation uses a ring-based architecture with 160 virtual spots per physical server and Murmur3 hashing to distribute game sessions across instances, ensuring minimal session migration during scaling events and O(log N) lookup performance.**

Fabrica-util, the open-source utility library from the go-pantheon ecosystem, provides production-ready consistent hashing primitives specifically engineered for high-throughput gaming infrastructure. This article examines how fabrica-util's consistent hashing implementation balances player sessions across server instances while maintaining stability during dynamic fleet scaling.

## Ring-Based Partitioning with Virtual Nodes

Both `HashRing` (for string keys) and `Int64HashRing` (for int64 keys) maintain a **sorted ring of virtual nodes** that map game sessions to physical servers. This architecture decouples the key space from physical server count, allowing smooth distribution regardless of cluster size.

### Virtual Spot Distribution

Each physical server is represented by a configurable number of *virtual spots* (defaulting to **160 spots per node**), causing a single server to appear multiple times on the ring. This replication smooths out uneven key distributions and eliminates hot-spots that could overload individual instances. In [`consistenthash/string_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/consistenthash/string_ketama.go) lines 78-84 and [`consistenthash/int64_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/consistenthash/int64_ketama.go) lines 62-78, the implementation generates these virtual node identifiers using the format `<nodeName>:<index>`.

### Murmur3 Hashing

The library employs **Murmur3 64-bit hashing** via `github.com/spaolacci/murmur3` to convert virtual node identifiers into deterministic ring positions. The string implementation stores 32-bit positions, while the int64 variant converts the 64-bit hash results to `int64` keys when needed, handling negative values gracefully as seen in lines 14-19 of [`int64_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/int64_ketama.go).

## Fast O(log N) Lookup Performance

When routing a game session to a server, the `GetNode` method executes a **binary search** against the ordered slice of virtual nodes using `sort.Search`.

The search logic, found at lines 26-34 of [`string_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/string_ketama.go) and lines 21-29 of [`int64_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/int64_ketama.go), locates the nearest clockwise virtual node for any given key. If the search reaches the end of the slice, it wraps to index 0, guaranteeing **continuous coverage** of the entire 64-bit hash space without gaps.

This binary search approach delivers **O(log N) complexity** where N represents the total virtual spots, enabling sub-microsecond routing decisions even with large server fleets.

## Dynamic Membership for Elastic Scaling

Game backends require elastic capacity to handle player influx. Adding or removing nodes is an **O(virtualSpots log N)** operation that minimizes session churn.

### Adding Nodes

The `AddNode` method creates virtual spots for new servers, appends them to the ring slice, and re-sorts the structure. Only keys falling between the new node's virtual spots and their immediate predecessors migrate to the new server, leaving existing session mappings largely undisturbed.

### Removing Nodes

`RemoveNode` filters out all virtual spots belonging to the departing server while preserving the remaining ring intact. Because each key maps to the nearest clockwise virtual node, only sessions previously assigned to the removed node's spots relocate to the next clockwise successor—a tiny fraction of total load.

## Thread-Safe Concurrency

Both implementations embed a `sync.RWMutex` and a `sync.Pool` of hash objects to handle concurrent game traffic.

- **Read operations** (`GetNode`) acquire read locks, allowing thousands of concurrent session look-ups per second without blocking.
- **Write operations** (`AddNode` / `RemoveNode`) acquire exclusive locks, guaranteeing ring consistency during fleet scaling events.

This design ensures that high-frequency routing queries never block on administrative cluster changes.

## Game Load Distribution Benefits

Fabrica-util's consistent hashing implementation addresses specific challenges inherent to game infrastructure:

| Game Load Characteristic | How the Ring Solves It |
|--------------------------|------------------------|
| **Uneven session distribution** | 160 virtual spots per server smooth out randomness; Murmur3 provides uniform hash distribution. |
| **Frequent server spin-up/spin-down** | Only sessions near the changed node's virtual spots migrate, preventing mass reshuffling. |
| **Low-latency routing** | Binary search on sorted slices delivers O(log N) performance with lock-free reads. |
| **Scalable instance counts** | Ring size grows linearly with virtual spots (160 × node count), not with total active sessions. |

## Implementation Examples

### String-Key Routing for Player Usernames

Use `HashRing` when routing by player usernames or string identifiers:

```go
package main

import (
    "fmt"
    "github.com/go-pantheon/fabrica-util/consistenthash"
)

func main() {
    // Create ring with default 160 virtual spots per server
    ring := consistenthash.NewRing(0)
    
    // Register game servers
    _ = ring.AddNode("us-east-1")
    _ = ring.AddNode("us-west-2")
    _ = ring.AddNode("eu-central-3")
    
    // Route player to server
    player := "Player_42"
    srv, ok := ring.GetNode(player)
    if ok {
        fmt.Printf("Player %s connects to %s\n", player, srv)
    }
}

```

### Int64-Key Routing for Numeric Session IDs

Use `Int64HashRing` for numeric session IDs or shard keys:

```go
package main

import (
    "fmt"
    "github.com/go-pantheon/fabrica-util/consistenthash"
)

func main() {
    ring := consistenthash.NewInt64Ring(0)
    
    // Register shards
    _ = ring.AddNode("shard-a")
    _ = ring.AddNode("shard-b")
    _ = ring.AddNode("shard-c")
    
    // Route numeric session ID
    var sessionID int64 = 9876543210
    server, _ := ring.GetNode(sessionID)
    fmt.Printf("Session %d routes to %s\n", sessionID, server)
}

```

### Dynamic Scaling During Runtime

Handle server crashes or capacity increases without dropping players:

```go
ring := consistenthash.NewRing(0)
_ = ring.AddNode("node-1")
_ = ring.AddNode("node-2")

// Spin up additional capacity
_ = ring.AddNode("node-3")  // Minimal key migration occurs

// Handle node failure
ring.RemoveNode("node-2")   // Sessions automatically remap to next clockwise node

```

## Summary

- **Virtual node architecture**: Each physical server receives 160 virtual spots on the ring, balancing load via [`consistenthash/string_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/consistenthash/string_ketama.go) and [`consistenthash/int64_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/consistenthash/int64_ketama.go) implementations.
- **Minimal migration**: Only sessions between a removed node's virtual spots and its predecessor relocate, preventing mass reshuffling during scaling.
- **Performance optimized**: `GetNode` utilizes binary search (`sort.Search`) for O(log N) lookups, protected by `sync.RWMutex` for high-concurrency scenarios.
- **Dual key support**: Separate implementations handle string usernames (`HashRing`) and numeric session IDs (`Int64HashRing`) with Murmur3 hashing.
- **Production hardened**: Thread-safe operations allow concurrent reads during node addition/removal, essential for real-time game services.

## Frequently Asked Questions

### How does fabrica-util's consistent hashing implementation minimize session migration when servers join or leave?

The implementation assigns 160 virtual spots to each physical server on the ring. When adding or removing a node via `AddNode` or `RemoveNode`, only keys falling between that node's virtual spots and the immediately preceding spots remap to different servers. This confines migration to a small fraction of total sessions—approximately 1/N of the load where N is the node count—rather than triggering a full rehash of all existing sessions.

### What hash function does fabrica-util use for consistent hashing, and why?

Fabrica-util uses **Murmur3 64-bit hashing** from `github.com/spaolacci/murmur3`. This non-cryptographic hash function provides excellent distribution properties and high performance, generating deterministic 32-bit or 64-bit positions for virtual nodes in [`string_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/string_ketama.go) (lines 78-84) and [`int64_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/int64_ketama.go) (lines 62-78). The uniform distribution prevents clustering and hot-spots on the ring.

### How does the implementation handle high-concurrency routing requests?

Both `HashRing` and `Int64HashRing` embed a `sync.RWMutex` separating read and write operations. The `GetNode` method acquires a read lock, allowing thousands of concurrent session look-ups to proceed in parallel. Only administrative operations like `AddNode` or `RemoveNode` acquire exclusive write locks, ensuring ring consistency without blocking routine game traffic.

### Can fabrica-util's consistent hashing handle negative int64 session IDs?

Yes. The `Int64HashRing` implementation in [`consistenthash/int64_ketama.go`](https://github.com/go-pantheon/fabrica-util/blob/main/consistenthash/int64_ketama.go) specifically handles negative values during the conversion from Murmur3's 64-bit unsigned output to signed `int64` keys (lines 14-19). This ensures that any 64-bit integer—including negative values derived from certain UUID layouts or timestamp-based IDs—maps correctly to a valid server node.