How fabrica-util Handles Distributed ID Generation Across Multiple Game Server Zones
fabrica-util solves distributed ID generation across multiple game server zones by embedding zone identifiers directly into 64-bit integers, eliminating the need for central coordination while guaranteeing global uniqueness.
Distributed game architectures require unique identifiers that span multiple server zones without collisions. The go-pantheon/fabrica-util repository provides a compact, zone-aware solution through its xid package, which embeds region data directly into the ID bit structure. This approach enables stateless generation across distributed game server zones, ensuring that a player created in zone 12 receives an ID that never conflicts with one generated in zone 34, even when both use the same local game identifier.
64-Bit Bit Layout and Zone Architecture
The core mechanism resides in xid/id.go, where the 64-bit integer is partitioned into distinct fields to encode both the game-specific entity and its origin zone.
The layout allocates space as follows:
- Game ID: Occupies the high bits (63 down to
gameIDSlotBit), providing the entity identifier space - Zone: Occupies the low 8 bits (
zoneSlotBit), supporting 256 distinct zones - Sign bit: Remains untouched to ensure positive values
Key constants defined in the source include:
gameIDSlotBit = 16: Defines the boundary between game ID and zone datazoneBit = 8: Size of the zone fieldMaxZone = 255: The highest valid zone value
This structure ensures that two servers operating in different zones produce different 64-bit outputs even when incrementing the same local gameID counter.
Core API for Zone-Aware ID Management
Building UIDs with BuildUID
The BuildUID function (lines 58-69 in xid/id.go) constructs the composite identifier. It validates the gameID against MaxGameID and MinGameID thresholds, then left-shifts the game ID by gameIDSlotBit positions before bitwise-ORing the zone value.
Extracting Components with SplitUID
To retrieve the original values, SplitUID (lines 71-78) reverses the operation. It right-shifts the UID to isolate the game ID and masks the lower bits to extract the zone identifier.
External Encoding with Hashids
For client-facing representation, EncodeID (lines 81-94) transforms the integer into an 18-character base-62 string using the Hashids algorithm. Negative IDs bypass this encoding and render as plain decimal strings. The complementary DecodeID function (lines 96-112) reverses this process, ensuring the system can handle signed integer edge cases safely.
Advantages for Distributed Game Servers
This architecture delivers specific benefits for multi-zone deployments:
- Stateless Operation: The
BuildUIDfunction requires only local knowledge of the server's zone constant and the next available game ID. No network calls or shared databases are necessary during generation. - Collision Prevention: Because the zone occupies dedicated bits, zone 12 and zone 13 produce mathematically distinct ID spaces, eliminating collision risks across regions.
- Database Efficiency: The zone information travels with the ID itself, removing the need for separate sharding columns or lookup tables when routing requests.
- Predictable Limits: With
MaxZonefixed at 255 and remaining bits allocated to game IDs, capacity planning is straightforward for operators managing shard growth.
Practical Implementation Example
The following example demonstrates the complete lifecycle from generation to decoding:
package main
import (
"fmt"
"log"
"github.com/go-pantheon/fabrica-util/xid"
)
func main() {
// Suppose this server belongs to zone 12 (e.g., "EU‑West")
const zone uint8 = 12
// A game‑specific identifier, e.g., a sequential player number
var gameID int64 = 123456
// Create a zone‑aware UID
uid, err := xid.BuildUID(gameID, zone)
if err != nil {
log.Fatalf("cannot build UID: %v", err)
}
fmt.Printf("Combined UID (int64): %d\n", uid)
// Encode the UID for external use (e.g., in URLs)
encoded, err := xid.EncodeID(uid)
if err != nil {
log.Fatalf("cannot encode UID: %v", err)
}
fmt.Printf("Encoded UID (string): %s\n", encoded)
// Later, decode back to the original integer
decoded, err := xid.DecodeID(encoded)
if err != nil {
log.Fatalf("cannot decode UID: %v", err)
}
fmt.Printf("Decoded UID (int64): %d\n", decoded)
// Split the UID to retrieve gameID and zone
splitGameID, splitZone := xid.SplitUID(decoded)
fmt.Printf("Extracted gameID: %d, zone: %d\n", splitGameID, splitZone)
}
Summary
- fabrica-util embeds zone identifiers into 64-bit integers using the
xidpackage to enable distributed ID generation across multiple game server zones. - The bit layout reserves 8 bits for zone data (supporting 256 zones) and the upper bits for game-specific identifiers, implemented in
xid/id.go. BuildUIDcombines local game IDs with zone constants via bit-shifting, whileSplitUIDextracts these components for reverse lookup.- Hashids encoding provides URL-safe, 18-character string representations for client communication without exposing raw bit patterns.
- This stateless approach requires no central coordinator, allowing independent ID generation across all zones while maintaining global uniqueness.
Frequently Asked Questions
How does fabrica-util ensure unique IDs across different zones without coordination?
By dedicating the lower 8 bits of every 64-bit ID to the zone identifier, fabrica-util mathematically partitions the ID space. A game ID of 123456 in zone 12 produces a different 64-bit integer than the same game ID in zone 13, preventing collisions regardless of generation timing or server synchronization.
What is the maximum number of game server zones supported by this system?
The system supports 256 zones (0-255), defined by the MaxZone constant and the 8-bit zone field in xid/id.go. This accommodates most regional sharding strategies while reserving sufficient bits for high-volume game entity identifiers.
Can fabrica-util handle negative ID values?
Yes. The EncodeID function detects negative integers and returns them as plain decimal strings rather than Hashids-encoded values. This ensures that error codes or sentinel values using negative numbers pass through the encoding layer without corruption, as implemented in lines 81-94 of xid/id.go.
Why choose Hashids over standard Base64 for external ID representation?
Hashids generates 18-character, base-62 strings that are URL-safe and visually distinct, avoiding ambiguous characters that could confuse players. The implementation in xid/id.go uses the github.com/speps/go-hashids/v2 library to create compact strings that obscure the underlying database sequence while remaining reversible via DecodeID.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →