Time-Based UUIDs: Leveraging Version 1 for Chronological Ordering

    29 April 2024
    9 min read
    Technical explainer
    Tutorial
    uuid
    database
    best-practices

    When most people think of UUIDs, they think of chaos: random strings that can’t be sorted, predicted, or interpreted. That’s true — if you’re using UUIDv4.

    But what if you want some order?

    That’s where UUIDv1 comes in. It’s a time-based identifier that encodes creation time, enabling natural sort order and efficient querying for time-series data.

    Let’s take a look at how UUIDv1 works, when to use it, and the trade-offs you should be aware of.


    🧬 What Is UUIDv1?

    UUIDv1 is defined in RFC 4122 and encodes:

    • 60 bits of timestamp (in 100-nanosecond intervals since Oct 15, 1582)
    • 48 bits of node ID (usually MAC address)
    • 14 bits of clock sequence (to prevent duplication)

    The layout ensures that UUIDs generated later will sort after those generated earlier — making UUIDv1 ideal for ordered data.


    📦 Why Use UUIDv1?

    UUIDv1 offers:

    • Globally unique identifiers
    • Natural chronological ordering
    • No central coordination
    • Fast generation

    This makes it perfect for:

    • Time-series logs
    • Message queues
    • Event sourcing
    • Append-only databases
    • Data replication where ordering matters

    ⚠️ Trade-Offs of UUIDv1

    Despite its benefits, UUIDv1 has some drawbacks:

    ❌ Privacy Risk

    • Encodes part of the MAC address
    • Reveals creation timestamp — down to 100ns

    ❌ Predictability

    • If you know the pattern, UUIDv1s are guessable in order
    • Not suitable for public identifiers or security tokens

    🧪 Generating UUIDv1 (Examples)

    🔹 Python

    python
    import uuid
    
    u = uuid.uuid1()
    print(u)

    Python’s uuid1() uses your machine’s MAC address and system time. You can override the node for privacy:

    python
    uuid.uuid1(node=0x123456789abc)

    🔹 Go (using github.com/gofrs/uuid)

    go
    package main
    
    import (
        "fmt"
        "github.com/gofrs/uuid"
    )
    
    func main() {
        id, _ := uuid.NewV1()
        fmt.Println("UUIDv1:", id)
    }

    🔹 Java

    java
    import com.fasterxml.uuid.Generators;
    import com.fasterxml.uuid.impl.TimeBasedGenerator;
    
    public class UUIDv1Example {
        public static void main(String[] args) {
            TimeBasedGenerator gen = Generators.timeBasedGenerator();
            System.out.println(gen.generate());
        }
    }

    📅 UUIDv1 for Time-Series Databases

    Because UUIDv1s sort chronologically, they’re a good fit for:

    • Time-ordered tables (e.g., in PostgreSQL or MySQL)
    • Index-friendly inserts (better than UUIDv4 for B-trees)
    • Range queries based on time

    PostgreSQL Tip

    sql
    CREATE TABLE events (
      id UUID PRIMARY KEY,
      event_data JSONB,
      created_at TIMESTAMPTZ DEFAULT NOW()
    );
    
    -- Index for time-range scanning
    CREATE INDEX ON events (id);

    With UUIDv1, ORDER BY id DESC roughly equals ORDER BY created_at DESC.


    🧰 Alternatives If UUIDv1 Isn't Right

    If you're concerned about leaking timestamps or MAC addresses:

    • UUIDv7: A newer spec that encodes timestamp + randomness without MAC
    • ULID: Lexicographically sortable, base32-encoded timestamped ID
    • Snowflake IDs: Used by Twitter, encodes timestamp + worker ID + sequence

    > TL;DR: Use UUIDv1 for internal ordered IDs, but not for public-facing identifiers.


    🔐 Security Considerations

    • Don’t expose UUIDv1 in URLs or public APIs
    • Be aware of info leaks from MAC address and creation time
    • Prefer UUIDv4, UUIDv7, or secure tokens for anything involving authentication

    Final Thoughts

    UUIDv1 might be old, but it’s far from obsolete. If your system needs order, uniqueness, and performance — and you’re not worried about exposing timestamps — it’s still a solid choice.

    Just use it wisely, and always understand the trade-offs before choosing an identifier format.

    🕒 UUIDv1: Because sometimes, order really does matter.

    Generate Your Own UUIDs

    Ready to put this knowledge into practice? Try our UUID generators:

    Summary

    This article explores UUIDv1 — the time-based version — showing how it enables chronological ordering and supports time-series data. Includes practical code examples and best practices for real-world implementation.

    TLDR;

    UUIDv1 is a time-based identifier that preserves natural order while maintaining global uniqueness.

    Key takeaways:

    • UUIDv1 encodes a timestamp and MAC address (or node ID)
    • It's ideal for time-series applications and ordered inserts
    • Use with caution: exposes creation time and potential system info

    When you need ordering and uniqueness without coordination, UUIDv1 is a powerful (if often misunderstood) tool.