Skip to content

ccheney/muuid-x

Repository files navigation

MUUID-X

EXtensible Multi-Universal Unique Identifier

Heat Death Invariant Identifiers

A unique identifier system designed with the understanding that your data might outlive your star.

The Problem

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.

Features

  • 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.

Installation

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"));

Quick Start

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"
}

Wire Format

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.

String Formats

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"

Distributed Systems

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));

Accessing Time

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,
});

Performance

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

Configuration

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,
});

FAQ

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.

Building from Source

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 library

License

MIT


"The identifier serves as the immutable anchor upon which relationships, causality, and state are constructed."

About

EXtensible Multi-Universal Unique Identifier

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors