Exid - Elixir Xid

View Source

An Elixir implementation of the globally unique ID generator xid, suited for web scale, ported from the Go xid package.

Overview

Exid generates 12-byte globally unique IDs using the MongoDB Object ID algorithm:

  • 4 bytes: Unix timestamp (seconds since epoch)
  • 3 bytes: Machine identifier
  • 2 bytes: Process ID
  • 3 bytes: Counter (auto-incrementing, starting with random value)

Why Xid?

  • Size: 12 bytes (96 bits) - smaller than UUID (16 bytes), larger than Snowflake (8 bytes)
  • Sortable: K-ordered, can be lexicographically sorted
  • No Configuration: Automatically detects machine and process IDs
  • Web Ready: Base32 hex encoded string representation (20 characters)
  • Unique: Guaranteed uniqueness for 16,777,216 (2^24) IDs per second per host/process
  • Cross-Platform: Compatible with Go xid package

String Format

IDs are encoded as 20-character strings using base32 hex alphabet (0-9, a-v):

iex> id = Exid.new()
iex> Exid.to_string(id)
"c0nsb7f1en2k9b5ls9bg"

Installation

Add exid to your list of dependencies in mix.exs:

def deps do
  [
    {:exid, "~> 0.1.0"}
  ]
end

Quick Start

# Initialize (must be called once at application startup)
Exid.init()

# Generate a new ID
id = Exid.new()

# Convert to string
id_string = Exid.to_string(id)

# Parse from string
{:ok, id} = Exid.from_string(id_string)

# Extract components
timestamp = Exid.time(id)
machine_id = Exid.machine(id)
process_id = Exid.pid(id)
counter = Exid.counter(id)

# Sort IDs (they're sortable!)
ids = [Exid.new(), Exid.new(), Exid.new()]
sorted = Exid.sort(ids)

API Reference

Generation

Conversion

Inspection

Comparison & Sorting

Utilities

Machine ID Detection

Xid automatically detects the machine ID using the following priority order:

  1. Environment Variable: XID_MACHINE_ID (must be a number 0-16777215)
  2. Platform-Specific ID:
    • Darwin (macOS): ioreg -rd1 -c IOPlatformExpertDevice → IOPlatformUUID
    • FreeBSD: sysctl kern.hostuuid
    • OpenBSD: sysctl hw.uuid
    • Linux: /etc/machine-id or /sys/class/dmi/id/product_uuid
    • Windows: Registry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography → MachineGuid
  3. Hostname Hash: SHA256 hash of system hostname
  4. Random: Cryptographically random bytes (fallback)

Setting Machine ID

# Override with environment variable
export XID_MACHINE_ID=12345

# Must be a valid 24-bit number (0-16777215)
export XID_MACHINE_ID=16777214  # OK
export XID_MACHINE_ID=16777216  # Error: out of range

Cross-Compatibility with Go xid

This implementation is compatible with the Go xid package. IDs generated in Go can be decoded in Elixir and vice versa:

# Decode Go-generated ID
{:ok, id} = Exid.from_string("9m4e2mr0ui3e8a215n4g")

# Verify components match Go implementation
Exid.time(id)     # => 1300816219
Exid.machine(id)  # => <<0x60, 0xf4, 0x86>>
Exid.pid(id)      # => 58408 (0xe428)
Exid.counter(id)  # => 4271561

ETS Table

Xid uses an ETS table named :xid_counter to maintain the atomic counter for generating unique IDs. The table is created automatically on application startup.

  • Table: :xid_counter (named, public, set, compressed)
  • Keys: :machine_id, :counter
  • Initialization: Called by Exid.Application.start/2

Testing

Run the test suite:

mix test

Tests include:

  • ID generation and uniqueness
  • String encoding/decoding
  • Cross-compatibility with Go xid (test vectors)
  • ID parts extraction
  • Sorting and comparison
  • Platform-specific machine ID detection
  • Edge cases and error handling

They are mostly ported from the Go xid package, with some additional tests and benchmarks.

Running Benchmarks

mix run benchmark/benchmark.exs

References

License

The Elixir port maintains compatibility with the original Go implementation by Olivier Poitrey, licensed under the MIT License.