EXtensible Multi-Universal Unique Identifier
Heat Death Invariant Identifiers
A unique identifier system designed with the understanding that your data might outlive your star.
Existing identifier standards encode an uncomfortable assumption: that time has an end. UUIDv7 will roll over around 10,889 AD. Snowflake IDs expire in 2079. These dates seem distant until you consider that the last mass extinction was 66 million years ago, and we're already archiving data in Arctic vaults.
MUUID-X uses variable-length encoding for timestamps. If your system runs for 10¹⁰⁰ years, the identifier simply grows by a few bytes. The sort order remains correct. The index remains coherent.
- Unbounded timestamps via Order-Preserving Varints (OPV)
- Lexicographic sorting — binary comparison equals chronological ordering
- Hybrid Logical Clocks — causality-aware, handles clock drift between nodes (or planets)
- Zero coordination — no central authority, no worker ID configuration
- Universe isolation — administrative domain hashing prevents collisions across disjoint networks
- Fork-safe entropy — PID-checked CSPRNG reseeding
- Zero dependencies — Zig standard library only
Read the "paper" for detailed design rationale and formal analysis.
Add to your build.zig.zon:
.dependencies = .{
.muuidx = .{
.url = "https://github.com/ccheney/muuid-x/archive/v1.0.0.tar.gz",
.hash = "...",
},
},Then in build.zig:
const muuidx = b.dependency("muuidx", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("muuidx", muuidx.module("muuidx"));const muuidx = @import("muuidx");
pub fn main() !void {
// Optional: identify your administrative domain
muuidx.init(.{ .universe = "earth.sol.orion-arm" });
// Generate
const id = muuidx.generate();
// Encode to bytes (for storage)
var buf: [muuidx.MAX_ENCODED_SIZE]u8 = undefined;
const len = try muuidx.encode(&buf, &id);
// Or to hex string (for logs, APIs)
var hex: [muuidx.MAX_HEX_LEN]u8 = undefined;
const str = try muuidx.toHex(&hex, &id);
// → "mu1_4d55081887cc3edef1f2f001010100abb26ca504c9ff7627ad10173cd856f907bce743ef3ea7b2716fe800"
}MUUID-X is variable-length, typically ~43 bytes:
| Magic | TimeNS | Counter | V | F | Universe | Observer | Len | Entropy | TLV | ∅ |
|---|---|---|---|---|---|---|---|---|---|---|
"MU" |
(OPV) | (OPV) | 0x01 |
0x00 |
(4B) | (6B) | (1B) | (N B) | ... | 0x00 |
The OPV encoding ensures that lexicographic byte comparison produces chronological ordering — your B-tree remains sorted by time regardless of whether the timestamp is 8 bytes or 20.
| Format | Prefix | Use Case |
|---|---|---|
| Hex | mu1_ |
Database keys, debugging (preserves sort order) |
| Base64URL | mu1b64_ |
APIs, URLs (~33% smaller, does not sort) |
// Hex (sortable)
const hex = try muuidx.toHex(&buf, &id);
// → "mu1_4d55081887cc3edef1f2f001010100abb26ca504c9ff7627ad10173cd856f907bce743ef3ea7b2716fe800"
// Base64 (compact)
const b64 = try muuidx.toBase64(&buf, &id);
// → "mu1b64_TVUIGIfMPt7x8vABAQEAq7JspQTJ_3YnrRAXPNhW-Qe850PvPqeycW_oAA"MUUID-X handles the reality that simultaneity is, at best, a polite fiction:
// Node receives an ID from another node (possibly with significant latency)
muuidx.sync(&received_id);
// Subsequent IDs are guaranteed to be causally after the received one
const next = muuidx.generate();
std.debug.assert(received_id.isBefore(next));const id = muuidx.generate();
// Unix timestamps
const secs = id.getTimestampSecs();
const millis = id.getTimestampMillis();
// Full datetime
const dt = id.getDateTime();
std.debug.print("{d}-{d:0>2}-{d:0>2}T{d:0>2}:{d:0>2}:{d:0>2}Z\n", .{
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
});Measured on commodity hardware. Your mileage may vary with relativistic effects.
| Operation | ns/op | ops/sec |
|---|---|---|
| Generate | 43 | 23,000,000 |
| Encode (binary) | 9 | 109,000,000 |
| Decode (binary) | 15 | 64,000,000 |
| Compare | <1 | 2,200,000,000 |
| Encode (hex) | 26 | 37,000,000 |
| Decode (hex) | 65 | 15,000,000 |
| Encode (base64) | 30 | 32,000,000 |
muuidx.init(.{
// Administrative domain (hashed to 4-byte UniverseID)
.universe = "earth.sol.orion-arm",
// Node identity override (hashed to 6-byte ObserverID)
.observer = "node-7",
// Entropy bytes per ID (default: 16)
.entropy_len = 16,
// Reject generation if clock drifts >500ms
.check_clock_skew = false,
});Why not UUIDv7?
UUIDv7 is excellent for the next 8,000 years. We're planning slightly further ahead.
Is the 43-byte size a problem?
It's larger than a UUID's 16 bytes. In exchange, you receive unbounded time, causal ordering, universe isolation, and the quiet confidence that comes from knowing your primary keys will survive the Andromeda-Milky Way collision.
What's the fine-structure constant doing in my entropy?
The physical constant α ≈ 137.036 is mixed into the PRNG seed. Consider it a signature binding your identifiers to this particular configuration of reality. Simulations with different physics would naturally generate non-colliding ID spaces.
Is this overkill for my CRUD app?
Possibly. But the same was said about 64-bit pointers.
git clone https://github.com/ccheney/muuid-x.git
cd muuid-x
zig build test # Run tests
zig build bench # Run benchmarks
zig build # Build libraryMIT
"The identifier serves as the immutable anchor upon which relationships, causality, and state are constructed."