# Exid - Elixir Xid
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):
```elixir
iex> id = Exid.new()
iex> Exid.to_string(id)
"c0nsb7f1en2k9b5ls9bg"
```
## Installation
Add `exid` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:exid, "~> 0.1.0"}
]
end
```
## Quick Start
```elixir
# 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 time
- `Exid.new_with_time/1` - Generate an ID with specific Unix timestamp
### Conversion
- `Exid.to_string/1` - Encode ID to 20-character base32 hex string
- `Exid.from_string/1` - Parse ID from base32 hex string
- `Exid.to_bytes/1` - Get binary representation (12 bytes)
- `Exid.from_bytes/1` - Parse ID from 12-byte binary
### Inspection
- `Exid.time/1` - Extract Unix timestamp
- `Exid.machine/1` - Extract 3-byte machine identifier
- `Exid.pid/1` - Extract 2-byte process identifier
- `Exid.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 nil
- `Exid.init/0` - Initialize ETS table (called automatically by application)
## 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
```bash
# 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:
```elixir
# 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:
```bash
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
```bash
mix run benchmark/benchmark.exs
```
## References
- [Go xid Package](https://github.com/rs/xid)
- [MongoDB Object ID Specification](https://docs.mongodb.org/manual/reference/object-id/)
## License
The Elixir port maintains compatibility with the original Go implementation by Olivier Poitrey, licensed under the MIT License.