Exid - Elixir Xid
View SourceAn 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
xidpackage
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"}
]
endQuick 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
Exid.new/0- Generate a new ID using current timeExid.new_with_time/1- Generate an ID with specific Unix timestamp
Conversion
Exid.to_string/1- Encode ID to 20-character base32 hex stringExid.from_string/1- Parse ID from base32 hex stringExid.to_bytes/1- Get binary representation (12 bytes)Exid.from_bytes/1- Parse ID from 12-byte binary
Inspection
Exid.time/1- Extract Unix timestampExid.machine/1- Extract 3-byte machine identifierExid.pid/1- Extract 2-byte process identifierExid.counter/1- Extract 3-byte counter value
Comparison & Sorting
Exid.compare/2- Compare two IDs (-1, 0, or 1)Exid.sort/1- Sort list of IDs
Utilities
Exid.nil_id/0- Get nil ID (all zeros)Exid.is_nil/1- Check if ID is nilExid.init/0- Initialize ETS table (called automatically by application)
Machine ID Detection
Xid automatically detects the machine ID using the following priority order:
- Environment Variable:
XID_MACHINE_ID(must be a number 0-16777215) - Platform-Specific ID:
- Darwin (macOS):
ioreg -rd1 -c IOPlatformExpertDevice→ IOPlatformUUID - FreeBSD:
sysctl kern.hostuuid - OpenBSD:
sysctl hw.uuid - Linux:
/etc/machine-idor/sys/class/dmi/id/product_uuid - Windows: Registry
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography→ MachineGuid
- Darwin (macOS):
- Hostname Hash: SHA256 hash of system hostname
- 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) # => 4271561ETS 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.