UUID Versions Explained: Why Go Supports Both v4 and v7 — Understanding the Engineering Trade-offs
A deep dive into UUID version evolution, v4 vs v7 performance comparison, and practical implementation guides across languages and…
A deep dive into UUID version evolution, v4 vs v7 performance comparison, and practical implementation guides across languages and databases
From the Go Proposal
A few days ago, while browsing Go’s issue tracker, I came across an interesting proposal — #76319: adding UUIDv4() and UUIDv7() functions to the crypto/rand package. This proposal sparked some questions.
UUID is familiar to most developers, commonly used for generating unique identifiers in distributed systems. But the proposal raises a key question: why support both v4 and v7? Isn’t v7 just an upgraded version of v4? Why not only support the “latest” v7?
With this question in mind, I dove into RFC 9562 (the specification that defines UUIDs), examined implementations across mainstream languages and databases, and discovered there’s more to this story. v4 and v7 aren’t simply old vs new versions — they’re two engineering solutions designed for different scenarios. In this article, we’ll start from this proposal and systematically explore UUID version evolution and how to choose the right version in practice.
What is UUID
UUID (Universally Unique Identifier) is a 128-bit identifier, also known as GUID (Globally Unique Identifier) in Microsoft ecosystems. Its core advantage: it provides high uniqueness across both space and time without requiring a centralized registration authority.
The standard string representation of UUID follows the “hex-and-dash” format, for example:
550e8400-e29b-41d4-a716-446655440000The segments correspond to lengths of 8–4–4–4–12 hexadecimal characters. In databases, UUIDs are typically stored as 16-byte binary values to save space and improve efficiency.
The UUID originated with Apollo NCS and was later adopted and popularized by the OSF DCE and Microsoft platforms. Currently, IETF’s RFC 9562 is the latest standard specification for UUIDs. Published in May 2024, it obsoletes the older RFC 4122 and adds several new versions to meet the needs of modern distributed systems.
UUID have extensive application scenarios: database primary keys, event IDs in distributed systems, session identifiers, resource naming, etc. Due to its fixed length and high randomness, UUIDs support tens of millions of generations per second on a single machine with extremely low collision probability.
UUID Version Evolution: v1 to v8
UUID isn’t a single-generation algorithm but an identifier family containing multiple versions. Each version has its own design goals and applicable scenarios. Let’s examine them one by one.
UUIDv1 and v6: Classic Timestamp-Based Approach
UUIDv1 is the earliest time-based UUID. It consists of three parts:
- 60-bit timestamp (100ns intervals since 1582–10–15)
- 14-bit clock sequence (handles clock rollback or node changes)
- 48-bit node ID (typically network MAC address, can be replaced with a random value)
v1’s problems:
- First, it exposes MAC addresses, creating privacy risks.
- Second, its timestamp fields are scattered across the low and middle bits, making it impossible to sort by time in a direct byte-order comparison and unfriendly to database indexes.
UUIDv6 was created to solve v1’s sorting issue. It retains all v1 fields but rearranges the timestamp to “high-bit-first”, enabling direct byte-order sorting and improving database B-tree index locality. v6 is suitable for systems needing to migrate from v1 while maintaining compatibility.
UUIDv3 and v5: Name-Based Hashing
Both versions map “namespace + name” to stable UUIDs. The difference lies in the hash algorithm:
- UUIDv3 uses MD5
- UUIDv5 uses SHA-1
Since MD5 and SHA-1 have security concerns, RFC 9562 recommends prioritizing v5 or adopting stronger hash algorithms, such as SHA-256, in the v8 custom space.
The typical scenario for these UUIDs: always generating the same UUID for the same input. For example, generating stable UUIDs from URLs, DNS names, or other identifiers.
UUIDv4: Pure Random
UUIDv4 is the most widely used version. It fills 122 bits with cryptographically secure random numbers (CSPRNG), with the other 6 bits marking version and variant.
v4 advantages:
- Completely random, unpredictable, high security
- Doesn’t leak any time or machine information
- Simple generation, no clock synchronization needed
v4 drawbacks:
- Random insertion creates scattered positions in database B-tree indexes, causing page splits, fragmentation, and write amplification, impacting performance
- Cannot sort by generation time
UUIDv7: Modern Time-Based UUID
UUIDv7 is a key addition in RFC 9562, designed for modern distributed system needs. Its structure:
- 48-bit Unix millisecond timestamp (in highest position, usable until year 10889)
- 74 bits of random or “sub-millisecond precision + counter + random” combination
v7’s design philosophy balances randomness and sortability:
- Timestamp in high bits ensures lexicographic sorting by generation time
- Subsequent bits filled with random numbers ensure uniqueness in distributed environments
- Optional sub-millisecond counter ensures monotonic increment within the same millisecond
v7 is recommended by RFC as a replacement for v1/v6, suitable for scenarios requiring time-ordering characteristics, especially high-write-load database primary keys.
UUIDv8: Custom Space
UUIDv8 defines an experimental custom space. It only mandates setting version and variant bits, with the remaining 122 bits defined by implementers.
Important note: v8 is not a v4 replacement. v4 requires all 122 bits to be random, while v8 allows embedding additional information or using other generation algorithms, but uniqueness guarantees are the implementer’s responsibility.
UUIDv2 and Special Values
UUIDv2 is reserved for DCE Security, its definition is outside RFC scope, and mainstream implementations typically don’t support it.
Additionally, RFC defines two special values:
- Nil UUID (all zeros): used as default value
- Max UUID (all ones): used as sentinel value

v4 vs v7: Two Different Engineering Trade-offs
Now let’s return to the question at the article’s beginning: why does Go support both v4 and v7? Aren’t they in an old-new version relationship?
They Solve Different Problems
UUIDv4’s design goals:
- Maximize unpredictability and security
- Avoid leaking any information (time, machine, business logic)
- Suitable for security-sensitive scenarios like session tokens, temporary credentials
UUIDv7’s design goals:
- Provide time-ordering characteristics, support sorting and range queries by time
- Improve database index write locality, reduce page splits
- Suitable for high-concurrency write scenarios like database primary keys, event stream IDs
These are two completely different engineering requirements. v7 isn’t an “upgrade” of v4, but another design targeting different scenarios.
Performance Impact Comparison
In database primary key scenarios, v4 and v7’s performance differences are significant:
+---------------------+-----------------------------------+-------------------------------------------------------+
| Feature | UUIDv4 | UUIDv7 |
+---------------------+-----------------------------------+-------------------------------------------------------+
| Insertion Order | Fully random, scattered in B-tree | Time-incremental, concentrated at rightmost leaf node |
| Page Splits | Frequent, can happen anywhere | Only at rightmost edge, predictable |
| Cache Hit Rate | Low, requires frequent disk reads | High, hot data in memory |
| Range Queries | Cannot query by time | Direct time-range queries |
| Write Amplification | High | Low |
+---------------------+-----------------------------------+-------------------------------------------------------+PostgreSQL 18 natively supports uuidv7(), MySQL's UUID_TO_BIN(UUID(), TRUE) uses byte-order rearrangement to improve locality, SQL Server's NEWSEQUENTIALID() follows similar thinking — all confirming this issue's universality.

Security Considerations
RFC 9562 explicitly states:
- If UUIDs will be used in security-sensitive scenarios (like access tokens or session IDs), prioritize v4 to avoid time information leakage
- v7’s timestamp is visible, attackers can infer generation time and somewhat narrow the brute-force space
So for security tokens, don’t use v7 — stick with v4.
Why Support Both
The answer is now clear:
- Different scenarios: v4 for scenarios requiring absolute randomness, v7 for time-ordered scenarios
- Forced choice creates problems: supporting only v7 risks security-sensitive scenarios; supporting only v4 suffers poor performance in high-concurrency database writes
- Ecosystem reality: mainstream languages and databases are supporting both versions because they genuinely solve different engineering problems
It’s like HTTP and HTTPS: they’re not simply a version evolution relationship, but two protocols for different security requirements. UUID is the same — different versions serve different engineering scenarios.
UUID in Practice: Language and Database Support
Having understood the theory, let’s examine UUID support across languages and databases in actual development.
Programming Languages
Python (3.14+)
Python 3.14’s uuid module implements RFC 9562, adding v6/v7/v8 support:
import uuid
# Generate v4 (pure random)
uuid.uuid4()
# Generate v7 (time-based)
uuid.uuid7()
# Generate v6 (reordered v1)
uuid.uuid6()Java
Java’s standard library java.util.UUID natively supports v3 and v4:
// v4 random
UUID.randomUUID();
// v3 name hash (MD5)
UUID.nameUUIDFromBytes(bytes);v7 support is under discussion (JDK-8357251 proposal). Currently, use the third-party Java UUID Generator (JUG) library for complete v1/v3/v4/v5/v6/v7 support.
JavaScript/Node.js
Browsers and Node.js natively provide v4:
// Built-in browser/Node.js
crypto.randomUUID(); // v4For multi-version support, use the uuid package:
import { v4 as uuidv4, v7 as uuidv7 } from 'uuid';
uuidv4(); // random
uuidv7(); // time-basedRust
The uuid crate supports all versions, enabled through features:
use uuid::Uuid;
// v4
Uuid::new_v4();
// v7 (requires v7 feature)
Uuid::now_v7();C#/.NET
.NET now supports v7:
// v7
Guid.CreateVersion7();
// v4-type random GUID
Guid.NewGuid();Go
Go’s standard library currently lacks native UUID support, but as mentioned at the article’s start with proposal #76319, the community is discussing adding v4 and v7 support to the crypto/rand package.
Currently, actual projects widely use the google/uuid library (over 100k imports):
import "github.com/google/uuid"
// v4 random
uuid.New() // or uuid.NewString()
uuid.NewRandom() // explicit v4
// v7 time-based
uuid.NewV7()
// v6 reordered v1
uuid.NewV6()
// v5 name hash
uuid.NewSHA1(namespace, []byte(name))This library has implemented all RFC 9562 versions, including v1/v3/v4/v5/v6/v7. The proposal notes that v4 and v7 usage frequency far exceeds other versions (code search shows v4 used ~400k times, v7 ~5.6k times), hence suggesting adding these two most commonly used versions to the standard library.
Databases
PostgreSQL
PostgreSQL has a native uuid type (16 bytes), with PostgreSQL 18 adding the uuidv7() function:
-- v4 random
SELECT gen_random_uuid();
-- v7 time-based (PostgreSQL 18+)
SELECT uuidv7();
-- Extract timestamp
SELECT uuid_extract_timestamp(uuidv7());MySQL
MySQL natively supports v1, providing byte-order conversion to improve index locality:
-- Generate v1
SELECT UUID();
-- Convert to binary and reorder bytes for better indexing
INSERT INTO t VALUES(UUID_TO_BIN(UUID(), TRUE));
-- Convert back to string
SELECT BIN_TO_UUID(uuid_column) FROM t;SQL Server
SQL Server uses the uniqueidentifier type:
-- Random GUID
SELECT NEWID();
-- Sequential GUID (improves index locality)
CREATE TABLE t (
id uniqueidentifier DEFAULT NEWSEQUENTIALID()
);MongoDB
MongoDB stores UUIDs as BSON Binary subtype 4. Most actual deployments use the default ObjectId (12 bytes, includes timestamp) as _id:
# PyMongo using UUID
from pymongo import MongoClient
from bson.binary import Binary, UuidRepresentation
import uuid
client = MongoClient(uuidRepresentation=UuidRepresentation.STANDARD)
db.collection.insert_one({"_id": uuid.uuid4()})Oracle
Oracle provides SYS_GUID() to generate globally unique RAW(16) values:
CREATE TABLE t (
id RAW(16) DEFAULT SYS_GUID()
);Adoption Trends
From the above, we can see:
- v4 remains mainstream: due to historical reasons and simplicity, v4 has native support in most language standard libraries
- v7 is rapidly spreading: Python 3.14, .NET, PostgreSQL 18, Rust already support it, Java is under discussion
- Databases evolving toward time-ordering: nearly all mainstream databases recommend or support time-ordered ID schemes to improve index performance
Selection Guide: How to Choose the Right UUID Version
Having understood each version’s characteristics, how should you choose in actual projects? Here’s a decision framework.

Selection by Scenario
Scenario 1: Database Primary Key / High-Concurrency Writes
Recommendation: UUIDv7
Reasons:
- Time-incremental characteristics dramatically reduce page splits and write amplification
- Supports time-range queries
- Modern databases (PostgreSQL 18, MySQL’s UUID_TO_BIN reordering, etc.) all recommend this approach
Scenario 2: Security Tokens / Session IDs / Temporary Credentials
Recommendation: UUIDv4
Reasons:
- Completely random, unpredictable
- Doesn’t leak time information, avoiding side-channel attacks
- RFC explicitly recommends v4 for security-sensitive scenarios
Scenario 3: Event Streams / Log IDs / Trace IDs
Recommendation: UUIDv7
Reasons:
- Time-ordered arrangement facilitates log analysis and queries
- In distributed systems, it enables time-range queries through UUID itself
Scenario 4: Stable Mapping (URL/DNS/Resource Names)
Recommendation: UUIDv5 (or SHA-256 implementation in v8)
Reasons:
- Generates same UUID for same input, ensuring idempotency
- v5 is more secure than v3
Scenario 5: Legacy System Compatibility (v1 Migration)
Recommendation: UUIDv6
Reasons:
- Retains v1 field semantics, only reorders bytes
- Can maintain protocol compatibility with v1 systems
Best Practices in Real Projects
- Clearly distinguish purposes: in the same system, using different versions for different scenarios is normal and reasonable. For example:
- Database primary keys use v7
- API tokens use v4
- Event IDs use v7
- Storage format: use 16-byte binary storage in databases, not string form (36 bytes). This saves space and improves index performance.
- Generation location:
- v4 can be generated anywhere (client, server, database)
- v7 recommended for server or database generation to ensure clock consistency
- Don’t over-rely on time ordering: while v7 sorts by time, don’t treat it as a precise timestamp. If precise time is needed, store a separate
created_atfield. - Check language/database support: when choosing a version, confirm your language and database have native support or mature third-party libraries.
Conclusion
Returning to the initial question: why does Go support both UUIDv4 and UUIDv7? The answer is now clear.
UUID isn’t a simple “version upgrade” system, but an identifier family designed for different engineering scenarios. v4 pursues absolute randomness and security, v7 pursues time-ordering characteristics and database performance — they solve different problems.
RFC 9562’s release marks UUID standards entering a new stage. v7’s addition isn’t to replace v4, but to provide more suitable tools for modern distributed systems. In actual projects, we need to choose the appropriate UUID version based on specific scenario requirements — security, performance, sortability — rather than blindly pursuing the “latest.”
I hope this article helps you build a clear understanding of the UUID version system, enabling more informed technical decisions next time.
References: