Universally Unique Identifiers (UUID for short) are 128-bit numbers with a high degree of unicity so that the possibility of collision is extremely low. They are used for various purposes such as identifying resources, correlation IDs in logging and tracing, primary keys in databases, and more. UUIDs are known as GUIDs (Globally Unique Identifiers) on Windows systems. Several version have been defined over time, version 7 being the latest of them, designed to solve some problems present in the other version.
What is a UUID
A UUID is a 128-bit number that is typically formatted as 36 character hexadecimal string with hyphens. It’s defined by the RFC 4122 standard (which overseeded the now obsolete RFC 9562). A formatted UUID looks like this:
D5501097-8306-4009-8532-715DB2C65607
The general format is
xxxxxxxx-xxxx-Vxxx-Rxxxx-xxxxxxxxxxxx
In this representation
xis a hexadecimal characterVis the versionRis the variant (10xxfor RFC 4122 compliant UUIDs)
UUID variants
There are multiple variants for UUIDs. He is a table with a short description:
| Variant | Method | Description |
|---|---|---|
| 1 | Timestamp + MAC address | Reveals device information (MAC address) |
| 3 | Name-based (MD5 hash) | Generated by hashing a namespace + name pair, using MD5 which is cryptographically broken. |
| 4 | Random | 122 random bits |
| 5 | Name-based (SHA-1 hash) | Generated by hashing a namespace + name pair, using SHA-1 is considered obsolete |
| 7 | Time-based + random | Optimized for use in databases |
| 8 | Custom | Experimental, reserved for application-specific formats |
The older versions of UUID had different problems:
- versions 3 and 5 rely on cryptographically broken or obsolete hashes, and shouldn’t be used where uniqueness or secrecy depend of cryptographical strength
- version 1 embeds the MAC address of the device on which it was created as well as the timestamp when it was created which means the location and time of creating can be tracked; moreover, UUIDs may collide because VM or contains may share the same MAC address
- version 4 are purely random, which means they are completely out of order, which makes them inappropriate for indexed database columns; however, they are very good for cases where you need simple unique IDs without carrying about ordering or readability
Enter version 7
The UUID v7 was designed to provide better performance when using UUIDs in databases (especially for primary keys). It consists of two main parts:
- the most significant 48 bits are the Unix timestamp of the moment when it was created, with millisecond resolution
- the rest of the bits (except the 4 bit for version and 2 for the variant) are completely random (just like for version 4)
This means this UUIDs are ordered chronologically while still retaining a high degree of uniqueness, which makes them more index-friendly.
Here is, for instance, an article that attempts to answer the question “Is UUIDv7 actually better than UUIDv4 in PostgreSQL?“. The conclusion, reproduced below, is that both insert times and index size are significantly better:

Generating UUIDs v7
Windows API
There is no support in the native API for creating v7 UUIDs. UuidCreate(), as well as CoCreateGui() that is basically just a wrapper over UuidCreate, still generate only v4 UUIDs.
Linux
It’s the same situation, the uuid_generate() API (from <uuid/uuid.h>) only generates v4 (although libuuid also supports creating v1 UUIDs).
C++
There is no standard library function for creating UUIDs, but we can write such an implementation, that works on all platforms.
using uuid_t = std::array<uint8_t, 16>;
uuid_t generate_uuidv7()
{
uuid_t bytes{};
// get current Unix timestamp in milliseconds
auto now = std::chrono::system_clock::now();
uint64_t ms_since_epoch =
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
// fill with random bytes
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint64_t> dist;
uint64_t rand_hi = dist(gen);
uint64_t rand_lo = dist(gen);
for (int i = 0; i < 8; ++i) bytes[i] = (rand_hi >> ((7 - i) * 8)) & 0xFF;
for (int i = 0; i < 8; ++i) bytes[8 + i] = (rand_lo >> ((7 - i) * 8)) & 0xFF;
// overwrite the first 6 bytes with timestamp (big-endian)
bytes[0] = (ms_since_epoch >> 40) & 0xFF;
bytes[1] = (ms_since_epoch >> 32) & 0xFF;
bytes[2] = (ms_since_epoch >> 24) & 0xFF;
bytes[3] = (ms_since_epoch >> 16) & 0xFF;
bytes[4] = (ms_since_epoch >> 8) & 0xFF;
bytes[5] = (ms_since_epoch) & 0xFF;
// set version (UUIDv7 = 0b0111)
bytes[6] = (bytes[6] & 0x0F) | 0x70;
// set variant (RFC 4122)
bytes[8] = (bytes[8] & 0x3F) | 0x80;
return bytes;
}
std::string to_string(const uuid_t& uuid)
{
return std::format(
"{:02x}{:02x}{:02x}{:02x}-"
"{:02x}{:02x}-"
"{:02x}{:02x}-"
"{:02x}{:02x}-"
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5],
uuid[6], uuid[7],
uuid[8], uuid[9],
uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]
);
}
This could be used as follows:
int main()
{
for (int i = 0; i < 5; ++i)
{
auto uuid = generate_uuidv7();
std::println("{}", to_string(uuid));
}
}
.NET
Starting with .NET 9, you can use the Guid.CreateVersion7() method, as shown below:
var guid = Guid.CreateVersion7(); Console.WriteLine(guid);
This function has an overload and you can also pass a DateTimeOffset to it:
var guid = Guid.CreateVersion7(TimeProvider.System.GetUtcNow());
For previous versions of .NET, you’d have to rely on 3rd party NuGet package.
Read more
You can read more about UUIDs v7 on this articles:
I believe that the RFC 9562 obsoleted the RFC 4122 standard.