No Coordination
UUID, ULID generate unique IDs without central coordination. Snowflake needs machine ID assignment but no runtime coordination.
In distributed systems, we need to generate unique identifiers for:
The Challenge: How do we generate unique IDs across multiple nodes without coordination? How do we ensure no collisions? How do we make IDs sortable and efficient?
Sequential IDs (like database auto-increment) require coordination:
Problems:
Not suitable for distributed systems!
UUID is a 128-bit identifier that’s globally unique without coordination.
UUID v4 uses 122 random bits (4 bits for version/variant).
Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
4 = version 4y = variant (8, 9, A, or B)Characteristics:
Use Cases:
UUID v7 uses timestamp + random bits (time-ordered).
Format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
7 = version 7Characteristics:
Use Cases:
Snowflake is Twitter’s ID generation algorithm. Generates 64-bit time-ordered IDs.
64-bit ID:├─ 41 bits: Timestamp (milliseconds since epoch)├─ 10 bits: Machine ID (0-1023 machines)└─ 12 bits: Sequence number (0-4095 per millisecond)How It Works:
Performance:
Requirements:
ULID combines timestamp and randomness in a lexicographically sortable format.
128-bit ULID:├─ 48 bits: Timestamp (milliseconds since 1970-01-01)└─ 80 bits: RandomFormat: 01ARZ3NDEKTSV4RRFFQ69G5FAV (Base32 encoded, 26 characters)
Characteristics:
Use Cases:
import uuidimport timeimport randomfrom datetime import datetime, timezone
# UUID v4 (Random)def generate_uuid_v4() -> str: """Generate UUID v4 (random)""" return str(uuid.uuid4())
# UUID v7 (Time-ordered) - Python doesn't have built-in v7, so we'll simulatedef generate_uuid_v7() -> str: """Generate UUID v7-like (time-ordered)""" # Get current timestamp in milliseconds timestamp_ms = int(time.time() * 1000)
# Extract timestamp bits (48 bits = 6 bytes) timestamp_bytes = timestamp_ms.to_bytes(6, byteorder='big')
# Generate random bytes for remaining 10 bytes random_bytes = random.randbytes(10)
# Combine: timestamp (6 bytes) + random (10 bytes) = 16 bytes combined = timestamp_bytes + random_bytes
# Create UUID-like format (set version 7, variant bits) uuid_bytes = bytearray(combined) uuid_bytes[6] = (uuid_bytes[6] & 0x0F) | 0x70 # Version 7 uuid_bytes[8] = (uuid_bytes[8] & 0x3F) | 0x80 # Variant
return str(uuid.UUID(bytes=bytes(uuid_bytes)))
# Snowflake Algorithmclass SnowflakeGenerator: """Snowflake ID generator"""
# Custom epoch (2020-01-01 00:00:00 UTC) EPOCH = 1577836800000 # milliseconds
def __init__(self, machine_id: int): if machine_id < 0 or machine_id > 1023: raise ValueError("Machine ID must be between 0 and 1023")
self.machine_id = machine_id self.sequence = 0 self.last_timestamp = 0
def generate(self) -> int: """Generate Snowflake ID""" timestamp = int(time.time() * 1000) - self.EPOCH
if timestamp < self.last_timestamp: raise Exception("Clock moved backwards")
if timestamp == self.last_timestamp: # Same millisecond, increment sequence self.sequence = (self.sequence + 1) & 0xFFF # 12 bits if self.sequence == 0: # Sequence overflow, wait for next millisecond timestamp = self._wait_next_millisecond(self.last_timestamp) else: self.sequence = 0
self.last_timestamp = timestamp
# Build ID: timestamp (41 bits) | machine_id (10 bits) | sequence (12 bits) snowflake_id = (timestamp << 22) | (self.machine_id << 12) | self.sequence
return snowflake_id
def _wait_next_millisecond(self, last_timestamp: int) -> int: """Wait until next millisecond""" timestamp = int(time.time() * 1000) - self.EPOCH while timestamp <= last_timestamp: timestamp = int(time.time() * 1000) - self.EPOCH return timestamp
# ULID Generationimport base64
def generate_ulid() -> str: """Generate ULID (time-ordered, lexicographically sortable)""" # Get current timestamp in milliseconds timestamp_ms = int(time.time() * 1000)
# Timestamp: 48 bits (6 bytes) timestamp_bytes = timestamp_ms.to_bytes(6, byteorder='big')
# Random: 80 bits (10 bytes) random_bytes = random.randbytes(10)
# Combine: timestamp + random = 16 bytes ulid_bytes = timestamp_bytes + random_bytes
# Base32 encode (URL-safe, no padding) # Using crockford's base32 alphabet alphabet = '0123456789ABCDEFGHJKMNPQRSTVWXYZ' ulid = '' value = int.from_bytes(ulid_bytes, byteorder='big')
while value > 0: ulid = alphabet[value % 32] + ulid value //= 32
# Pad to 26 characters ulid = ulid.zfill(26)
return ulid
# Usageprint("UUID v4:", generate_uuid_v4())print("UUID v7:", generate_uuid_v7())
snowflake = SnowflakeGenerator(machine_id=1)print("Snowflake:", snowflake.generate())print("Snowflake:", snowflake.generate())
print("ULID:", generate_ulid())import java.util.UUID;import java.util.concurrent.atomic.AtomicLong;import java.time.Instant;
// UUID v4 (Random)public static String generateUUIDv4() { return UUID.randomUUID().toString();}
// Snowflake Algorithmpublic class SnowflakeGenerator { private static final long EPOCH = 1577836800000L; // 2020-01-01
private final long machineId; private long sequence = 0; private long lastTimestamp = 0;
public SnowflakeGenerator(long machineId) { if (machineId < 0 || machineId > 1023) { throw new IllegalArgumentException("Machine ID must be between 0 and 1023"); } this.machineId = machineId; }
public synchronized long generate() { long timestamp = System.currentTimeMillis() - EPOCH;
if (timestamp < lastTimestamp) { throw new RuntimeException("Clock moved backwards"); }
if (timestamp == lastTimestamp) { sequence = (sequence + 1) & 0xFFF; // 12 bits if (sequence == 0) { timestamp = waitNextMillisecond(lastTimestamp); } } else { sequence = 0; }
lastTimestamp = timestamp;
// Build ID: timestamp (41 bits) | machine_id (10 bits) | sequence (12 bits) return (timestamp << 22) | (machineId << 12) | sequence; }
private long waitNextMillisecond(long lastTimestamp) { long timestamp = System.currentTimeMillis() - EPOCH; while (timestamp <= lastTimestamp) { timestamp = System.currentTimeMillis() - EPOCH; } return timestamp; }}
// ULID (using library like com.github.ulid)// For demo, simplified versionpublic class ULIDGenerator { private static final String ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
public static String generate() { long timestamp = System.currentTimeMillis();
// Timestamp: 48 bits StringBuilder ulid = new StringBuilder(); encodeTimestamp(ulid, timestamp);
// Random: 80 bits encodeRandom(ulid);
return ulid.toString(); }
private static void encodeTimestamp(StringBuilder ulid, long timestamp) { // Encode 48-bit timestamp to 10 base32 characters // Simplified - in production use proper base32 encoding }
private static void encodeRandom(StringBuilder ulid) { // Encode 80 random bits to 16 base32 characters // Simplified - in production use proper base32 encoding }}
// UsageSystem.out.println("UUID v4: " + generateUUIDv4());
SnowflakeGenerator snowflake = new SnowflakeGenerator(1);System.out.println("Snowflake: " + snowflake.generate());System.out.println("Snowflake: " + snowflake.generate());
System.out.println("ULID: " + ULIDGenerator.generate());import { randomBytes } from 'crypto';import { v4 as uuidv4 } from 'uuid';
// UUID v4 (Random)function generateUUIDv4(): string { return uuidv4();}
// Snowflake Algorithmclass SnowflakeGenerator { // Custom epoch (2020-01-01 00:00:00 UTC) private static readonly EPOCH = 1577836800000; // milliseconds private machineId: number; private sequence: number = 0; private lastTimestamp: number = 0;
constructor(machineId: number) { if (machineId < 0 || machineId > 1023) { throw new Error("Machine ID must be between 0 and 1023"); } this.machineId = machineId; }
generate(): bigint { // Generate Snowflake ID let timestamp = Date.now() - SnowflakeGenerator.EPOCH;
if (timestamp < this.lastTimestamp) { throw new Error("Clock moved backwards"); }
if (timestamp === this.lastTimestamp) { // Same millisecond, increment sequence this.sequence = (this.sequence + 1) & 0xFFF; // 12 bits if (this.sequence === 0) { // Sequence overflow, wait for next millisecond timestamp = this.waitNextMillisecond(this.lastTimestamp); } } else { this.sequence = 0; }
this.lastTimestamp = timestamp;
// Build ID: timestamp (41 bits) | machine_id (10 bits) | sequence (12 bits) const snowflakeId = BigInt(timestamp) << BigInt(22) | BigInt(this.machineId) << BigInt(12) | BigInt(this.sequence);
return snowflakeId; }
private waitNextMillisecond(lastTimestamp: number): number { // Wait until next millisecond let timestamp = Date.now() - SnowflakeGenerator.EPOCH; while (timestamp <= lastTimestamp) { timestamp = Date.now() - SnowflakeGenerator.EPOCH; } return timestamp; }}
// ULID Generationconst ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
function generateULID(): string { // Generate ULID (time-ordered, lexicographically sortable) const timestamp = Date.now();
// Timestamp: 48 bits (6 bytes) const timestampBuffer = Buffer.allocUnsafe(6); timestampBuffer.writeUInt32BE(Math.floor(timestamp / 0x100000000), 0); timestampBuffer.writeUInt16BE(timestamp & 0xFFFF, 4);
// Random: 80 bits (10 bytes) const randomBuffer = randomBytes(10);
// Combine: timestamp + random = 16 bytes const ulidBuffer = Buffer.concat([timestampBuffer, randomBuffer]);
// Base32 encode let value = BigInt('0x' + ulidBuffer.toString('hex')); let ulid = '';
while (value > 0) { ulid = ALPHABET[Number(value % BigInt(32))] + ulid; value = value / BigInt(32); }
// Pad to 26 characters return ulid.padStart(26, '0');}
// Usageconsole.log("UUID v4:", generateUUIDv4());
const snowflake = new SnowflakeGenerator(1);console.log("Snowflake:", snowflake.generate().toString());console.log("Snowflake:", snowflake.generate().toString());
console.log("ULID:", generateULID());#include <uuid/uuid.h>#include <chrono>#include <random>#include <string>#include <iostream>
// UUID v4 (Random)std::string generateUUIDv4() { uuid_t uuid; uuid_generate_random(uuid); char uuid_str[37]; uuid_unparse_lower(uuid, uuid_str); return std::string(uuid_str);}
// Snowflake Algorithmclass SnowflakeGenerator {private: static constexpr long long EPOCH = 1577836800000LL; // 2020-01-01 long long machineId; long long sequence = 0; long long lastTimestamp = 0;
public: SnowflakeGenerator(long long machineId) { if (machineId < 0 || machineId > 1023) { throw std::invalid_argument("Machine ID must be between 0 and 1023"); } this->machineId = machineId; }
long long generate() { auto now = std::chrono::system_clock::now(); auto timestamp_ms = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()).count(); long long timestamp = timestamp_ms - EPOCH;
if (timestamp < lastTimestamp) { throw std::runtime_error("Clock moved backwards"); }
if (timestamp == lastTimestamp) { sequence = (sequence + 1) & 0xFFF; // 12 bits if (sequence == 0) { timestamp = waitNextMillisecond(lastTimestamp); } } else { sequence = 0; }
lastTimestamp = timestamp;
// Build ID: timestamp (41 bits) | machine_id (10 bits) | sequence (12 bits) return (timestamp << 22) | (machineId << 12) | sequence; }
private: long long waitNextMillisecond(long long lastTimestamp) { auto now = std::chrono::system_clock::now(); long long timestamp = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()).count() - EPOCH; while (timestamp <= lastTimestamp) { now = std::chrono::system_clock::now(); timestamp = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()).count() - EPOCH; } return timestamp; }};
// Usageint main() { std::cout << "UUID v4: " << generateUUIDv4() << std::endl;
SnowflakeGenerator snowflake(1); std::cout << "Snowflake: " << snowflake.generate() << std::endl; std::cout << "Snowflake: " << snowflake.generate() << std::endl;
return 0;}using System;using System.Security.Cryptography;
// UUID v4 (Random)public static string GenerateUUIDv4() { return Guid.NewGuid().ToString();}
// Snowflake Algorithmpublic class SnowflakeGenerator { private const long EPOCH = 1577836800000L; // 2020-01-01
private readonly long machineId; private long sequence = 0; private long lastTimestamp = 0; private readonly object lockObject = new object();
public SnowflakeGenerator(long machineId) { if (machineId < 0 || machineId > 1023) { throw new ArgumentException("Machine ID must be between 0 and 1023"); } this.machineId = machineId; }
public long Generate() { lock (lockObject) { long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - EPOCH;
if (timestamp < lastTimestamp) { throw new Exception("Clock moved backwards"); }
if (timestamp == lastTimestamp) { sequence = (sequence + 1) & 0xFFF; // 12 bits if (sequence == 0) { timestamp = WaitNextMillisecond(lastTimestamp); } } else { sequence = 0; }
lastTimestamp = timestamp;
// Build ID: timestamp (41 bits) | machine_id (10 bits) | sequence (12 bits) return (timestamp << 22) | (machineId << 12) | sequence; } }
private long WaitNextMillisecond(long lastTimestamp) { long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - EPOCH; while (timestamp <= lastTimestamp) { timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - EPOCH; } return timestamp; }}
// ULID Generation (simplified)public class ULIDGenerator { private static readonly string Alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; private static readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
public static string Generate() { // Get current timestamp in milliseconds long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Timestamp: 48 bits (encode to base32) string ulid = EncodeBase32(timestamp, 10);
// Random: 80 bits (encode to base32) byte[] randomBytes = new byte[10]; rng.GetBytes(randomBytes); ulid += EncodeBase32(BitConverter.ToInt64(randomBytes, 0), 16);
return ulid; }
private static string EncodeBase32(long value, int length) { // Simplified base32 encoding // In production, use proper base32 encoding library return value.ToString("X").PadLeft(length, '0'); }}
// UsageConsole.WriteLine($"UUID v4: {GenerateUUIDv4()}");
var snowflake = new SnowflakeGenerator(1);Console.WriteLine($"Snowflake: {snowflake.Generate()}");Console.WriteLine($"Snowflake: {snowflake.Generate()}");
Console.WriteLine($"ULID: {ULIDGenerator.Generate()}");| Feature | UUID v4 | UUID v7 | Snowflake | ULID |
|---|---|---|---|---|
| Size | 128 bits | 128 bits | 64 bits | 128 bits |
| Sortable | No | Yes | Yes | Yes |
| Coordination | None | None | Machine ID needed | None |
| Performance | Fast | Fast | Very fast | Fast |
| Collision Risk | Very low | Very low | Very low | Very low |
| Database Indexing | Poor (random) | Good (time-ordered) | Excellent | Excellent |
Use UUID v4 when:
Use UUID v7 when:
Use Snowflake when:
Use ULID when:
No Coordination
UUID, ULID generate unique IDs without central coordination. Snowflake needs machine ID assignment but no runtime coordination.
Time-Ordered
UUID v7, Snowflake, ULID are time-ordered. Improves database indexing performance compared to random UUID v4.
Collision Probability
All algorithms have extremely low collision probability. UUID v4: ~5×10^-37 for single collision. Practical systems don’t worry about collisions.
Choose Wisely
Consider size, sortability, coordination needs, and database indexing when choosing ID generation strategy.