Choosing a Decentralized Distributed ID: UUIDv4 vs UUIDv7 vs ULID vs Nano ID

This article examines how traceId, eventId, and shareId differ in requirements and walks through a detailed comparison of UUIDv4, UUIDv7, ULID, and Nano ID—covering generation principles, ordering characteristics, storage impact, Java code examples, and practical guidance for selecting the right scheme.

Yumin Fish Harvest
Yumin Fish Harvest
Yumin Fish Harvest
Choosing a Decentralized Distributed ID: UUIDv4 vs UUIDv7 vs ULID vs Nano ID

When designing an e‑commerce system I first list three identifiers: a traceId generated by the gateway, an eventId for order creation events, and a shareId for public product links. Although all are IDs, their usage patterns diverge, so a single generation method is rarely optimal.

Comparing the four local‑generation schemes

UUIDv4 : a 128‑bit random value with 122 bits of randomness; version and variant bits are fixed.

UUIDv7 : places a Unix‑millisecond timestamp in the high 48 bits, making values roughly time‑ordered.

ULID : also 128‑bit, composed of 48‑bit millisecond time and 80‑bit random part, encoded in Crockford Base32 (26 characters, no hyphens).

Nano ID : a URL‑friendly string built from a 64‑character alphabet; default length 21 characters (~126 bits of randomness).

Decision factors before picking an algorithm

I first ask whether the ID generation needs a network call. Local generation avoids a remote service and scales horizontally; central services are needed only for strictly sequential numbers.

Network required: central services (e.g., DB auto‑increment, segment services).

No network: UUIDv4, UUIDv7, ULID, Nano ID.

Horizontal scaling: local generators work out‑of‑the‑box; central services must be scaled together.

Strict monotonicity: only central services guarantee global monotonic order.

Ordering terminology

Unordered : later values may be smaller (UUIDv4, Nano ID).

Time‑sortable : values generated at later timestamps sort after earlier ones (UUIDv7, ULID).

Monotonic : within the same generator and millisecond the suffix increments (monotonic ULID).

Continuous : no gaps; only true for DB auto‑increment, not for the four schemes.

UUIDv4 in Java

package com.demo.id.uuidv4;

import java.util.UUID;

public final class UuidV4Demo {
    private static UUID nextUuidV4() {
        return UUID.randomUUID();
    }
    public static void main(String[] args) {
        UUID id = nextUuidV4();
        System.out.println("UUIDv4 = " + id);
        System.out.println("长度 = " + id.toString().length());
        System.out.println("版本 = " + id.version());
        System.out.println("变体 = " + id.variant());
    }
}

A typical run prints a 36‑character string, version 4, variant 2. UUIDv4 is suitable for traceId, idempotent request keys, or temporary file names, but its randomness can cause page splits when used as a clustered primary key.

UUIDv7 (RFC 9562)

UUIDv7 embeds a Unix‑millisecond timestamp in the most significant 48 bits, followed by a 4‑bit version field and 12‑bit random data. The layout enables local time ordering while preserving the UUID format.

package com.demo.id.uuidv7;

import com.github.f4b6a3.uuid.UuidCreator;
import java.time.Instant;
import java.util.UUID;

public final class UuidV7Demo {
    private static UUID nextUuidV7() {
        return UuidCreator.getTimeOrderedEpoch();
    }
    private static Instant extractInstant(UUID uuid) {
        if (uuid.version() != 7) {
            throw new IllegalArgumentException("Expected UUIDv7 but got version " + uuid.version());
        }
        String first48Bits = uuid.toString().replace("-", "").substring(0, 12);
        long epochMillis = Long.parseUnsignedLong(first48Bits, 16);
        return Instant.ofEpochMilli(epochMillis);
    }
    public static void main(String[] args) {
        UUID id = nextUuidV7();
        System.out.println("UUIDv7 = " + id);
        System.out.println("版本 = " + id.version());
        System.out.println("生成时间 = " + extractInstant(id));
    }
}

UUIDv7 is a good choice when the ID must be time‑sortable but still conform to the UUID ecosystem.

ULID

ULID stores a 48‑bit millisecond timestamp and an 80‑bit random component, encoded in a 26‑character Base32 string. Two variants exist:

Normal ULID – random suffix each call.

Monotonic ULID – increments the random part when the generator stays within the same millisecond.

package com.demo.id.ulid;

import com.github.f4b6a3.ulid.Ulid;
import com.github.f4b6a3.ulid.UlidCreator;
import java.time.Instant;

public final class UlidDemo {
    private static String nextUlid() {
        return UlidCreator.getUlid().toString();
    }
    private static String nextMonotonicUlid() {
        return UlidCreator.getMonotonicUlid().toString();
    }
    private static Instant extractInstant(String value) {
        return Ulid.getInstant(value);
    }
    public static void main(String[] args) {
        String normal = nextUlid();
        System.out.println("普通 ULID = " + normal);
        System.out.println("生成时间 = " + extractInstant(normal));
        for (int i = 1; i <= 3; i++) {
            System.out.println("单调 ULID " + i + " = " + nextMonotonicUlid());
        }
    }
}

ULID is ideal for event IDs, log IDs, or URL‑friendly resources where a 26‑character string is acceptable.

Nano ID

Nano ID generates a short, URL‑safe string by randomly selecting characters from a 64‑symbol alphabet. The default length of 21 characters yields about 126 bits of entropy.

package com.demo.id.nanoid;

import java.security.SecureRandom;

public final class NanoIdDemo {
    private static final char[] ALPHABET = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
    private static String nextNanoId() {
        byte[] random = new byte[21];
        SECURE_RANDOM.nextBytes(random);
        char[] result = new char[random.length];
        for (int i = 0; i < random.length; i++) {
            result[i] = ALPHABET[random[i] & 63]; // 64 = 2^6, no bias
        }
        return new String(result);
    }
    public static void main(String[] args) {
        String id = nextNanoId();
        System.out.println("Nano ID = " + id);
        System.out.println("长度 = " + id.length());
        System.out.println("格式正确 = " + id.matches("[A-Za-z0-9_-]{21}"));
    }
}

Nano ID shines for public share links because it is short, URL‑friendly, and does not expose creation time.

Storage impact on InnoDB

Using a UUID (or ULID) as a clustered primary key forces InnoDB to insert rows across many pages, increasing page splits. Converting the 36‑character UUID string to BINARY(16) reduces index size but does not change the random insertion pattern. For write‑heavy tables, a sequential key (auto‑increment or a time‑ordered UUIDv7/ULID) is usually preferable.

Choosing the right scheme – a decision matrix

traceId : needs to be generated locally, no ordering – UUIDv4.

eventId : prefers time‑locality – UUIDv7 or ULID.

shareId : short, URL‑friendly, no time info – Nano ID.

Database primary key : if strict global order is required, use auto‑increment; otherwise consider a compressed BINARY(16) UUIDv4 or a tested UUIDv7/ULID after load testing.

User‑visible order numbers : design a separate business number; do not rely on any of the four schemes.

Scenario examples

Gateway traceId – generate once with UUID.randomUUID() and propagate via HTTP headers.

package com.demo.id.scenario;
import java.util.Map;
import java.util.UUID;
public final class TraceIdDemo {
    public static void main(String[] args) {
        String traceId = UUID.randomUUID().toString().replace("-", "");
        Map<String, String> downstreamHeaders = Map.of("X-Trace-Id", traceId);
        System.out.println("X-Trace-Id: " + downstreamHeaders.get("X-Trace-Id"));
    }
}

Event ID with ULID – store the ULID string and a separate event_time column.

event_id CHAR(26) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
event_time DATETIME(3) NOT NULL

Share link – generate a Nano ID and build the URL.

String shareId = nextShareId();
String shareUrl = "https://example.com/s/" + shareId;
System.out.println("分享链接 = " + shareUrl);

Final guidance

Start by clarifying the ID’s responsibility (uniqueness, ordering, URL friendliness, or business semantics). Then evaluate time‑ordering needs, length constraints, ecosystem compatibility, and whether strict global increment is required. Choose UUIDv4 for generic random IDs, UUIDv7 or ULID when time locality matters, and Nano ID for short public tokens. Complement the chosen scheme with unique constraints, business timestamps, and access‑control checks as needed.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javauuiddistributed-idulidnano-id
Yumin Fish Harvest
Written by

Yumin Fish Harvest

A deep‑sea salvage fisherman sharing architecture insights, practical tips, and lessons learned.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.