Skip to content

Latest commit

 

History

History
217 lines (151 loc) · 9.96 KB

File metadata and controls

217 lines (151 loc) · 9.96 KB

UUIDv7 - Swift

A minimal, high-performance UUID v7 implementation for Swift.

Introduction

UUID v7 is a time-ordered UUID format that encodes a Unix timestamp in the most significant 48 bits, making UUIDs naturally sortable by creation time. This is useful for:

  • Database indexed fields that benefit from sequential ordering
  • Distributed systems where time-based ordering is valuable
  • Event logs and audit trails where chronological sorting is important

This library provides a lightweight implementation that works seamlessly with Swift's standard UUID type.

Compact Base62 Format — Block's Standard ID Format

At Block, the compact string format is our standard for UUID v7 identifiers. When you don't have a native UUID storage type (like PostgreSQL's uuid or a 128-bit binary column), the compact format is the optimal choice for storing and transmitting IDs as text.

Why Compact Format?

The standard UUID string format (01936c0a-5e0c-7b3a-8f9d-2e1c4a6b8d0f) was designed for human readability with hyphens, but this comes at a cost:

Aspect Standard UUID Compact String Advantage
Length 36 characters 22 characters 39% smaller
Storage 36 bytes (text) 22 bytes (text) Less disk, memory, bandwidth
Index size Larger B-tree nodes Smaller B-tree nodes Faster queries
URL encoding Hyphens are safe, but verbose No encoding needed Cleaner URLs
Sort order Lexicographic ≠ time order Lexicographic = time order Natural sorting works

The Sortability Problem with Standard UUIDs

Standard UUID strings don't sort chronologically because the string representation doesn't preserve numeric order:

Standard (WRONG order when sorted as strings):
  01936c0a-5e0c-7...  →  timestamp: 1700000000000
  f1936c0a-5e0c-7...  →  timestamp: 1600000000000  ← sorts AFTER despite being EARLIER

The compact Base62 format uses big-endian encoding that preserves the numeric ordering:

Compact (CORRECT order when sorted as strings):
  0K5VxR...  →  timestamp: 1600000000000
  0K8TmN...  →  timestamp: 1700000000000  ← correctly sorts after

When to Use Each Format

Storage Type Recommended Format
PostgreSQL uuid column Native UUID (binary)
MySQL BINARY(16) Native UUID (binary)
Text columns (VARCHAR, TEXT) Compact string
JSON APIs Compact string
URLs and paths Compact string
Logs and debugging Either (standard more readable)

Design

The compact format uses Base62 encoding with the alphabet 0-9A-Za-z:

  • 22 characters encode the full 128-bit UUID
  • Big-endian byte order ensures lexicographic sorting matches numeric sorting
  • No special characters — alphanumeric only, URL-safe without encoding
  • Fixed length — always exactly 22 characters, no padding needed

Example: 01JDQYZ9M6K7TCJK2F3W8N (compact) vs 01936c0a-5e0c-7b3a-8f9d-2e1c4a6b8d0f (standard)

Design Principles

Minimal API Surface: Static utility methods and extensions that work with Swift's UUID rather than introducing a new type. This ensures maximum compatibility with existing code.

Separate Types for Different Use Cases: Two distinct implementations for different performance/ordering trade-offs:

  • UUIDv7: Uses non-cryptographic random with zero synchronization overhead for maximum performance. UUIDs generated in the same millisecond may not be strictly ordered, but uniqueness is maintained through random bits.
  • MonotonicUUIDv7: Uses a synchronized counter to ensure strict ordering within the same millisecond, following RFC 9562 recommendations. Best for database primary keys and scenarios requiring guaranteed sequential ordering.

Timestamp Extraction: UUIDs contain timing information, and this library makes it easy to extract this for debugging, observability, and time-based queries.

Flexible Generation: Static factories for common cases, configurable generators for testing or custom clock sources.

Installation

Swift Package Manager

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/block/uuidv7.git", from: "1.0.0")
]

Or add via Xcode: File → Add Package Dependencies → Enter the repository URL.

Usage

import UUIDv7

// Generate a UUID v7 (maximum performance, no ordering guarantees)
let uuid = UUIDv7.generate()

// Generate with monotonic ordering (for database primary keys)
let monotonicUuid = MonotonicUUIDv7.generate()

// Extract the timestamp (milliseconds since Unix epoch)
let timestamp = try uuid.timestamp

// Generate a compact string directly (22 characters, Base62)
let compactId = UUIDv7.generateCompactString()

// Convert UUID to compact string (preserves sort order)
let compact = uuid.compactString

// Convert compact string back to UUID
let fromCompact = try UUID(compactString: compact)

// Custom clock for testing (non-monotonic)
let testUuid = UUIDv7.generate(clock: { 1234567890000 })

// Custom clock with monotonic ordering
let generator = MonotonicUUIDv7()
let monotonicTestUuid = generator.generate(clock: { 1234567890000 })

// Access version and variant
print(uuid.version)  // 7
print(uuid.variant)  // 2

Design Details

Format

UUID v7 follows RFC 9562:

  • Bits 0-47: Unix timestamp in milliseconds (48 bits)
  • Bits 48-51: Version field (0111 for v7)
  • Bits 52-63: Counter or random bits (12 bits, called rand_a)
    • Monotonic mode: Sequential counter for strict ordering within the same millisecond
    • Non-monotonic mode: Random bits for maximum performance
  • Bits 64-65: Variant field (10 for RFC 4122)
  • Bits 66-127: Random bits (62 bits, called rand_b)

API

UUIDv7 (High Performance, No Ordering Guarantees)

UUIDv7.generate(): Generate a new UUID v7 using current system time. Uses non-cryptographic random with no synchronization for maximum performance. Returns UUID.

UUIDv7.generate(clock:): Generate a new UUID v7 with a custom clock source (milliseconds since Unix epoch). Useful for testing or specialized use cases.

MonotonicUUIDv7 (Sequential Ordering Guaranteed)

MonotonicUUIDv7.generate(): Generate a new UUID v7 using current system time with monotonic ordering. Uses a synchronized counter to ensure strict ordering within the same millisecond. Returns UUID.

MonotonicUUIDv7.generate(clock:): Generate a new UUID v7 with a custom clock source and monotonic ordering. Useful for testing monotonic behavior with controlled clock sources.

MonotonicUUIDv7.shared: Shared singleton instance for convenient access.

Utility Methods

UUIDv7.getTimestamp(_:): Extract the millisecond timestamp from any UUID v7. Returns UInt64. Throws if not version 7.

Compact String Methods:

  • UUIDv7.generateCompactString(): Generate a new UUID v7 and return it as a 22-character compact string
  • UUIDv7.generateCompactString(clock:): Generate with custom clock source, return as compact string
  • UUIDv7.toCompactString(_:): Convert any UUID to a 22-character Base62 compact string
  • UUIDv7.fromCompactString(_:): Convert a compact string back to UUID

UUID Extensions:

  • uuid.timestamp - Extension property for idiomatic timestamp extraction (throws if not v7)
  • uuid.compactString - Extension property for conversion to compact string format
  • uuid.version - The version number of the UUID
  • uuid.variant - The variant number of the UUID
  • UUID(compactString:) - Initialize from compact string

Implementation Details

MonotonicUUIDv7 class:

  • Uses a synchronized counter (NSLock) for strict ordering within the same millisecond
  • Guarantees ordering: uuid1 < uuid2 for sequential generation
  • Can generate up to 4096 UUIDs per millisecond before blocking
  • If counter overflows, waits for the next millisecond to maintain uniqueness
  • Counter resets to a random value when the timestamp advances (unpredictability)
  • Best for: Database primary keys, audit logs, any scenario requiring guaranteed ordering

UUIDv7 enum:

  • Uses non-cryptographic random for the counter field with zero synchronization overhead
  • Maximum performance with no blocking possible
  • Uniqueness guaranteed by random bits, but ordering within a millisecond is not guaranteed
  • Best for: High-throughput scenarios, distributed systems, logging, tracing

Random Number Generation

This implementation uses Swift's default random (which is non-cryptographic on most platforms) for generating random bits:

  • Performance: Standard random is significantly faster than cryptographic random
  • No Synchronization: No lock contention in multi-threaded environments when using UUIDv7
  • Sufficient for UUIDs: Cryptographic randomness is not required for UUID generation. The primary goals are uniqueness and unpredictability, not security
  • RFC 9562 Compliance: The RFC does not mandate cryptographic randomness for UUID generation

Platform Support

  • macOS 12+
  • iOS 15+
  • tvOS 15+
  • watchOS 8+

Project Resources

Resource Description
CODEOWNERS Outlines the project lead(s)
CODE_OF_CONDUCT.md Expected behavior for project contributors, promoting a welcoming environment
CONTRIBUTING.md Developer guide to build, test, run, access CI, chat, discuss, file issues
GOVERNANCE.md Project governance
LICENSE Apache License, Version 2.0