<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Distributed Systems | Ahmed K Emara</title><link>https://akemara.com/en/tags/distributed-systems/</link><atom:link href="https://akemara.com/en/tags/distributed-systems/index.xml" rel="self" type="application/rss+xml"/><description>Distributed Systems</description><generator>Akemara Kit (https://akemara.com)</generator><language>en</language><lastBuildDate>Thu, 20 Nov 2025 00:00:00 +0000</lastBuildDate><image><url>https://akemara.com/media/logo.svg</url><title>Distributed Systems</title><link>https://akemara.com/en/tags/distributed-systems/</link></image><item><title>Scaling Payments Without Losing a Cent</title><link>https://akemara.com/en/talks/scaling-payments/</link><pubDate>Thu, 20 Nov 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/talks/scaling-payments/</guid><description>&lt;p&gt;How to keep money correct while scaling: idempotency keys, double-entry
ledgers, reconciliation, and the failure modes that bite payment systems under
load — with patterns that hold up in production.&lt;/p&gt;</description></item><item><title>Goodbye Random Inserts: UUIDv7 vs ULID vs UUIDv4 — Unique Identifiers, Performance, and Database Impact</title><link>https://akemara.com/en/blog/uuidv7-ulid-uuidv4/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://akemara.com/en/blog/uuidv7-ulid-uuidv4/</guid><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Unique identifiers are what makes data models work in modern apps. Choosing the right format for identifiers can affect performance and scalability, whether they are primary keys in a database or IDs in a distributed system. &lt;strong&gt;UUIDs (Universally Unique Identifiers)&lt;/strong&gt; have been popular for a long time because they are unique all over the world without a central coordinator. &lt;strong&gt;UUID version 4 (UUIDv4),&lt;/strong&gt; a randomly generated 128-bit ID, is one of the most popular UUIDs because it is simple and has a very low chance of colliding with another ID. But pure randomness has certain drawbacks in database environments, especially when it comes to indexing and performance. Newer schemes &lt;strong&gt;like ULID (Universally Unique Lexicographically Sortable Identifier)&lt;/strong&gt; and &lt;strong&gt;UUID version 7 (UUIDv7)&lt;/strong&gt; include time elements in the ID, making it possible to sort identifiers lexicographically (by time) and fix the problems with UUIDv4. In this article, we’ll look at the differences between UUIDv4, ULID, and UUIDv7. We’ll also talk about the problems with UUIDv4 in databases, how ULID fixes those problems, and how ULID compares to the new UUIDv7. We will also talk about how each one can be used and how it affects databases (with a focus on MySQL/MariaDB and PostgreSQL), including performance, sorting behavior, entropy, and other practical issues.&lt;/p&gt;
&lt;h2 id="uuidv4-pure-randomness-and-its-challenges"&gt;UUIDv4: Pure Randomness and Its Challenges&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;UUIDv4&lt;/strong&gt; is a 128-bit ID that has 122 bits of randomness. The other bits are used to mark the version and variant. A normal UUIDv4 has 32 hex characters (128 bits) in the 8–4–4–4–12 pattern, like 7f750c37–4d0b-4f0b-b922–37be0b9138a7. The fact that it only uses random bytes and has a very low risk of collisions (almost zero for most applications, given 2¹²² possibilities) are two of its best features. UUIDv4 is great for distributed systems because it makes sure that IDs are unique. Any node can make IDs on its own, and the chance that two nodes will make the same ID is very low. It’s also good when you need things to be unpredictable (like session tokens or security-sensitive IDs) because the randomness makes it impossible to guess IDs in order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt; occurs when UUIDv4 acts as a primary key or index within a database. UUIDv4s are pretty much random, so new inserts don’t go in any natural order. Most relational databases use a B-Tree index for primary keys. When you insert random values, each new value can end up in a random spot in the index. This causes index fragmentation, which means that data pages often split and stay half-empty, and records get spread out across the disk. Using a random UUID as the primary key in MySQL’s InnoDB engine (and MariaDB’s engine, which works in the same way) can make performance drop a lot because it clusters the table by the primary key. InnoDB expects primary keys to be in order and optimizes by filling pages to about 94% full. When random inserts are used, pages may only fill to about 50% full before splitting. This means that a table with random UUID primary keys can grow to almost twice the size of the same data with sequential keys because there are so many pages that are only partially filled. A real-world example from Percona showed that a table with 1 billion rows and UUIDv4 as a CHAR(36) primary key was almost 1 TB in size. However, after being reorganized with an ordered key, it shrank to about 450 GB, or half the size. The random inserts had caused “extreme fragmentation,” which meant that the index pages for the UUID table were only about 50% full, while the index pages for an auto-increment or ordered UUID key were about 90% full.&lt;/p&gt;
&lt;p&gt;UUIDv4’s randomness has significant impacts on performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Index Fragmentation and Page Splits:&lt;/strong&gt; When you add random items, the B-tree has to split pages in the middle instead of just adding them to the end. A lot of page splits leave a lot of half-empty pages and a “overly large and sparsely packed index.” This slows down insertion throughput and uses up disk space. MySQL with a random UUID primary key will have more index maintenance overhead in systems with a lot of data, which will slow down writes. One explanation says that when values go up, a new page can be added at the end. But when values are random, a split makes two half-full pages that stay half-full for a long time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cache Locality:&lt;/strong&gt; Accessing records that were recently added is less cache-friendly because records with UUIDv4 keys are spread out across the index. The database buffer/cache can’t keep “recent” inserts together because “recent” is everywhere. With sequential inserts, on the other hand, the most recently added data is stored in a small number of contiguous pages that are hot in cache. With UUIDv4, the working set is spread out, which causes cache misses. Because of this, the cache doesn’t work as well. The database might have to load a lot of different pages to work with recent data. When you use a sequential key, MySQL and PostgreSQL will have fewer cache hits and more disk I/O for normal access patterns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Disk I/O:&lt;/strong&gt; Because UUIDv4 is random, the storage engine can’t just add new data; it has to keep finding and rearranging pages. This means that workloads that require a lot of inserts will have more disk I/O. InnoDB will do a lot of writes and splits in the middle of the index. For applications that write a lot, a sequential key is much better for I/O. By avoiding the random insertion pattern, you can often get much faster insert rates. Researchers have found that time-ordered IDs like UUIDv7 or ULID “significantly reduce the disk I/O overhead” for inserts compared to UUIDv4.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;More Storage and Index Overhead:&lt;/strong&gt; UUIDs are big (16 bytes), which makes them more fragmented. They take up even more space (36 bytes plus some overhead) when stored as text (a 36-character string). An INT auto-increment in MySQL is 4 bytes, but a UUID stored in binary is 16 bytes (4 times larger), and a UUID stored as a string is 36 bytes (9 times larger than INT). The primary key value is also in all secondary indexes, so if the primary key is bigger, the secondary indexes will be bigger too. All UUID variants (v4, v7, ULID, etc.) are 128 bits, but it’s best to store them in a small 16-byte binary format to keep them from getting too big. MySQL’s UUID_TO_BIN() with the swap flag, for instance, can store v1/v2 in an ordered way in 16 bytes. There is no inherent order to exploit for v4, but binary(16) still takes up less space than char(36).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall &lt;strong&gt;UUIDv4&lt;/strong&gt; is great for uniqueness and easy to make, but it doesn’t have any sortable elements, which makes it “inefficient in situations where sorting is crucial for optimal performance, such as database keys.” Many teams have found that using UUIDv4 as the main key can make inserts slower and the index bigger over time. PostgreSQL’s experience is a little better than MySQL’s because PostgreSQL doesn’t cluster the table by PK by default (the table heap is separate). This means that random PKs mainly affect the index pages and not the order of the physical rows. Still, PostgreSQL B-tree indexes will have the same problems with page splits and half-full pages when the data is random. In short, the problem with UUIDv4 is that it is completely random, which means the database is not very local.&lt;/p&gt;
&lt;h2 id="ulid-sortable-identifiers-with-embedded-timestamp"&gt;ULID: Sortable Identifiers with Embedded Timestamp&lt;/h2&gt;
&lt;p&gt;Alizain Feerasta came up with &lt;strong&gt;ULID (Universally Unique Lexicographically Sortable Identifier)&lt;/strong&gt; in 2016 to fix the problems with random UUIDs. A ULID is also a 128-bit identifier, but its structure is very different from the one used in UUIDv4. The first &lt;strong&gt;48 bits are a timestamp&lt;/strong&gt; (milliseconds since the Unix epoch), and the last &lt;strong&gt;80 bits are random&lt;/strong&gt;. This design means that ULIDs still have globally unique randomness, but they also have an ordering dimension. This means that if you create ULIDs over time, their lexical order (when compared as strings or bytes) shows when they were created. To put it another way, ULIDs can be sorted by time with millisecond accuracy. A standard ULID is not shown in hex, but in Crockford’s Base32 encoding, which makes a string with 26 characters (for example, 01H9AZ98MT1XY7TK3XN9B0AJP3). This base32 doesn’t have any characters that are hard to understand (like I, L, O, or U) and doesn’t care about case, which makes ULIDs easier for people to read, copy, or put in URLs. The 26-character ULID string is easier to read and takes up less space than the 36-character UUID string (with hyphens) or the 32 hex characters (without hyphens).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How ULID solves the problems with UUIDv4:&lt;/strong&gt; ULIDs make sure that IDs generated in chronological order will sort in chronological order by putting time into the ID. For databases, this has immediate benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Append-like Insertion Pattern:&lt;/strong&gt; When ULIDs are used as primary keys, new inserts usually have larger (later) timestamps, which means they are sorted in a larger order. This means that new rows are usually added to the “end” of the index, though they may be out of order if the clock times are slightly different. The way the index works is much more like an auto-increment or sequential key. This cuts down on page splits and fragmentation a lot. Using a time-ordered key like ULID or UUIDv7 in MySQL/InnoDB “results in fewer page splits and an overall improvement in storage efficiency.” The data stays grouped by when it was made, which is useful for many applications, such as event logs. Inserts become more predictable, and you can often handle them by adding new pages at the end instead of splitting up old ones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better Cache Locality:&lt;/strong&gt; ULIDs make it so that recently added IDs are close together in value, so the most recently added rows are in a small part of the index. The database can keep the end of the index (the most recent pages) in memory, which makes it more likely that recent inserts and queries will hit the cache. UUIDv4 put new rows all over the place, but ULID keeps them in order by time. This makes caching work better. For example, a query for the most recent N records can just scan a range of the index without having to jump around.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural Time Sorting:&lt;/strong&gt; ULIDs let you sort by the ID itself to get chronological order. This means that in some cases, you may not need a separate timestamp column for sorting. If your primary key is a ULID, for instance, an ORDER BY id DESC gives you the newest entries first because the highest ULID corresponds to the most recent timestamp. This can make application logic or indexing easier, but keep in mind that if you need full date-time information or queries by date, you probably still want to keep a normal timestamp column for convenience. But the most important thing to remember is that ULID encodes time, which can be very useful for logs or time-series data where getting them back in order is important.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monotonic within a millisecond:&lt;/strong&gt; If more than one ULID is created in the same millisecond, it becomes a problem. The timestamp bits would be the same for both. When the timestamp doesn’t change, the ULID specification says to use a monotonic increment for the random part. This means that if you call the ULID generator more than once in less than a millisecond, it will see that the timestamp hasn’t changed and just add 1 to the random part for each new ID. This makes sure that ULIDs with the same timestamps stay in order (the one generated second will have a slightly bigger random part, making it lexicographically bigger by 1). For instance, if two ULIDs came out at the same ms, you might see 01BX5ZZKBKACTAV9WEVGEMMVS0 followed by 01BX5ZZKBKACTAV9WEVGEMMVS1. The last character went from 0 to 1. This one-step process makes sure that there are no duplicate ULIDs in a single timestamp and keeps the order. In real systems, the chance of needing more than 2⁸⁰ ULIDs in one millisecond is almost zero (that’s 1.2e24, which is a lot more than millions of IDs per second), so overflow isn’t a problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collision and entropy:&lt;/strong&gt; ULIDs have 80 bits of randomness, which is a little less than the 122 bits of randomness in UUIDv4, but still very large. The 80-bit random part gives 1.2×10²⁴ options per millisecond, even if the timestamp part repeats. For any possible use, the chance of a collision is still almost zero (to put it in perspective, generating 2⁴⁰ ≈ 1 trillion ULIDs gives a collision chance of about 1 in 2⁴⁰, which is very small). One study found that ULIDs have “negligible collision probabilities even at high generation rates,” and that they are actually less likely to collide than a UUIDv7 with 74 random bits. In short, ULIDs are still unique all over the world, just like UUIDv4.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to Read:&lt;/strong&gt; People often say that ULIDs are easier for people to read. The Crockford Base32 alphabet doesn’t use characters that look alike, so the 26-character string (for example, 01GZ9TGXKJQR6Z1YT1BP7FY4WX) is easier to work with than a 36-character UUID with mixed case hex and dashes. ULIDs don’t care about case, which can help you avoid mistakes when you handle them by hand (you don’t have to keep case like you do with hex). They are also safe to use in URLs because they only have letters and numbers. These properties can be helpful if you need to show or log identifiers that someone might read or type. But there is a trade-off: you can either store ULIDs as text (26 characters) or as binary (16 bytes). Comparing strings is a little slower than comparing raw bytes, and the text form is bigger (26 bytes vs. 16 bytes). Some databases, like Postgres, have a UUID type for 16-byte values but not a native ULID type. You can store ULIDs in a CHAR(26) or as binary (BYTEA(16) after changing Base32 to bytes). You can get both the raw 16-byte binary and the encoded string from many ULID libraries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, ULIDs enable globally unique IDs easier to use and faster to find in databases that deal with time-based data. In many systems that need the benefits of UUIDs without the performance hit of completely random keys, they have become a de facto standard. The bad news is that ULID is not (yet) an official standard from the IETF or ISO, and not all languages or databases support it. Most of the time, you’ll use a library to make ULIDs. The good news is that there are implementations in a lot of languages, like Java, Go, Python, and JavaScript. If you need to, you can also easily implement the spec yourself. Also, keep in mind that ULIDs, like any other time-based ID, show the timestamp. If someone sees a ULID, they can decode the first 10 characters to get the exact time it was made. In a lot of cases, this isn’t a problem (and it can even be helpful), but if you don’t want to give away the age or order of records (for security or privacy reasons), a time-based scheme might not be the best choice. We’ll talk about this more in the section on comparisons.&lt;/p&gt;
&lt;h2 id="uuidv7-the-new-time-ordered-uuid-standard"&gt;UUIDv7: The New Time-Ordered UUID Standard&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;UUIDv7&lt;/strong&gt; is a new member of the UUID family. It was standardised in RFC 9562 (May 2024), which obsoletes RFC 4122, as a time-ordered UUID variant. It borrowed a lot of inspiration from how well ULID did. UUIDv7 formalises the idea of a timestamp plus random bits within the UUID standard. A UUIDv7 is like a ULID in that it has 128 bits and a 48-bit Unix epoch timestamp (in milliseconds) in the most important bits. The rest of the bits are random, but you can add a monotonic counter to make sure there are no duplicates in the same millisecond. UUIDv7 gives 48 bits to the timestamp, 4 bits to the version number (which will be 0111 for version 7), and the rest (128−48−4 = 76 bits) to a mix of random and optional counter bits. After setting aside some bits for the version and the UUID “variant” indicator, there are about 74 bits of pure entropy left over. The variant is the two most important bits of a byte that show an RFC 4122 UUID. For version 7, those bits are set to the standard 10xx pattern. This gives UUIDv7 a little less randomness than ULID’s 80 bits, but it still has a huge space. UUIDv7 can make about 2⁷⁴ unique IDs every millisecond, just like ULID. If you need more than that, the spec says to use a counter and roll over the timestamp if needed. But in reality, 2⁷⁴ (1.8e22) per ms will never be reached. There are almost no collisions; one study found that even at very high rates, the risk of a collision with UUIDv7 is extremely low (and only slightly higher than with ULID).&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;main advantage of UUIDv7&lt;/strong&gt; is that it gives you a UUID that is the same for everyone and is in order by time. You get the benefits of ULID (sequential by time, better database performance) but in a way that works with UUID tools and types that are already out there. The standard way to write a UUIDv7 is as a 36-character hex string with hyphens. For example, 0199c376-ce80–7b08–81fd-5524d4a20ed1 could be a UUIDv7. The 7 in the third group shows that it is version 7. The first part, 0199c376ce80, is a timestamp (0x0199c376ce80 in hex). Sorting &lt;strong&gt;UUIDv7 lexicographically (or comparing as raw bytes)&lt;/strong&gt; is the same as putting them in chronological order because the timestamp is stored in the high bits. If you put UUIDv7 values into a UUID column in PostgreSQL or a BINARY(16) column in MySQL, their natural byte order is time-ordered. This means that an ascending index on that column will automatically time-order your rows. This is the same property that ULID has, but ULID uses a different way of writing it down.&lt;/p&gt;
&lt;p&gt;From a &lt;strong&gt;database point of view&lt;/strong&gt;, UUIDv7 works a lot like ULID:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ordered Inserts:&lt;/strong&gt; New UUIDv7s usually have a timestamp component that gets increasing over time (unless the clock is skewed or the UUIDs are generated out of order), so inserts usually go to the “end” of the index. This gives you a low fragmentation and a high page fill factor, just like ULID. In terms of how well it uses index pages, MySQL’s InnoDB will treat a UUIDv7 primary key almost as well as an AUTO_INCREMENT. To avoid problems with page splitting, the PlanetScale team strongly suggests using “time-based UUIDs such as version 6 or 7.” PostgreSQL also benefits from the better locality, which means that it needs less index rebalancing over time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance:&lt;/strong&gt; In practice, many have found that UUIDv7 and ULID offer better insert and query performance than UUIDv4. Benchmarks often show that inserts happen faster and use less disc space. For example, one study found that using time-ordered IDs (ULID or v7) can speed up generation and lower network overhead in distributed systems. In their tests, the improvements almost doubled the speed of ID generation and cut index storage and traffic by a large amount. You may have different experiences, but what is happening in general is clear: the database “will thank you” for using a sorted UUID.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compatibility:&lt;/strong&gt; UUIDv7 follows the UUID standard, so it can be used anywhere a “UUID” is needed. PostgreSQL now has a UUID data type that can store UUIDv7 without any changes (it’s just a 16-byte value). Support for v7 is being added to the UUID libraries of many languages. As of 2023–2024, there are libraries in Java, Go, Rust, Python, and other languages that can make UUIDv7. Even if native support isn’t great yet, you can make a UUIDv7 by taking the output of a ULID library and formatting it as a UUID. You don’t need a special column type or encoding to store UUIDv7, which is nice because it’s still a UUID. You would save it as a BINARY(16) in MySQL, or you could use UNHEX() on the text version. You can cast from a string to a UUID in Postgres or use extension functions to make v7. There is talk of adding uuid_generate_v7() to the pgcrypto extension or something like that. The UUIDv7 spec is now published as RFC 9562 (May 2024), which obsoletes RFC 4122 — so official support across platforms is arriving quickly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How it is different from ULID:&lt;/strong&gt; UUIDv7 and ULID are similar, but there are some small differences between them:&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardization:&lt;/strong&gt; UUIDv7 is an official standard (RFC 9562), but ULID is not officially standardized; instead, it is a de facto standard based on how it is used. This means that UUIDv7 has an official specification that all implementations must follow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Representation:&lt;/strong&gt; UUIDv7 uses the common UUID hex format, which is usually shown with hyphens. By default, ULID uses Base32 text. The choice can affect how easy it is to use. ULIDs are shorter and easier for people to read, but UUIDv7 might work better with your tools if they expect hex UUID strings. There is no technical advantage to one encoding over the other, except for length and how easy it is to read. (26-character base32 string vs. 36-character hex string).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage in DB:&lt;/strong&gt; Both are 128-bit when stored in a DB. When you store ULIDs as text, they take up 26 bytes, while UUIDv7 takes up 36 bytes. But the best way to store both is as 16-byte binary files. You can’t directly use a ULID string with PostgreSQL’s UUID type because it’s not in the UUID format. However, you could convert ULID Base32 to hex. UUIDv7 lets you use the UUID type right away. You would use BINARY(16) for both in MySQL. So, UUIDv7 might be a little easier to use with UUID columns that are already there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Randomness:&lt;/strong&gt; ULID has 80 random bits, while UUIDv7 has about 74 random bits, depending on how it is set up with counters. In real life, both give you a lot of uniqueness. The random part of ULID is always 80 bits, but UUIDv7 lets you use some of those bits as a counter if you need to make a lot of IDs in the same millisecond. If an implementation uses, for example, a 12-bit counter and 62 bits of random, then in theory, ULID’s full 80-bit random might have a lower chance of colliding. But, as was said, 62 random bits is more than enough. UUIDv7 implementations that work correctly use a secure PRNG for the random part, which makes them strong. One study found that ULID’s risk of collision was 98% lower than that of UUIDv7 when using a certain scheme with fewer random bits for v7. However, both were practically zero for real-world volumes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Information Exposure:&lt;/strong&gt; Both of them encode the timestamp down to the millisecond. So both show when the ID was made, which could give away the approximate system clock and the order of events. If you need IDs that don’t show the order in which they were created (for example, to keep users from figuring out how many records were made between two IDs or to keep the timestamp of an action hidden), then neither ULID nor UUIDv7 will work. You would go back to UUIDv4 or another scheme that is completely random. This is something to think about when creating identifiers for the public. An internal database primary key might not need to be kept secret, but an external API ID might.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In summary, UUIDv7 is the best of both worlds for many applications. It has the speed and ordering benefits of ULIDs as well as the ease of use and compatibility of the UUID format. One source says, “with today’s tools, UUIDv7 is the default choice for internal primary keys.” ULID should only be used when human-readability or specific legacy reasons require it, and UUIDv4 should only be used when you can’t expose time info. Now, let’s look at these plans side by side in different ways.&lt;/p&gt;
&lt;h2 id="use-cases-and-recommendations"&gt;Use Cases and Recommendations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;When to use UUIDv4:&lt;/strong&gt; If you don’t want the ID to have any information (like timestamps or order), and you want it to be unpredictable, UUIDv4 is still a good choice. It’s also everywhere and doesn’t need any extra libraries or special care. Use UUIDv4 for things like security tokens, API keys, or external IDs when you don’t want the creation time to be known. If you don’t want users to be able to guess how many orders your system has by looking at an order ID, a random UUIDv4 is a good choice. Also, in very rare cases of very distributed generation where even a timestamp could cause problems, v4’s randomness is easy to understand. Just know that there is a performance cost in databases. For large internal databases, v4 is usually not the best choice for the reasons given. Using UUIDv4 with auto-increment (for example, having an internal auto-increment key and using the UUID as an external reference) might help with some problems, but it makes things more complicated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use ULID:&lt;/strong&gt; ULID is a great choice for apps that need IDs in a certain order and are easy for people to read. ULIDs make it easier to read logs and URLs if you are logging events or have a system where IDs might show up in logs or URLs. They are also useful on the front end or in places where users can see them because users can tell which of two ULIDs is newer (they sort alphabetically by time). ULIDs are great for event sourcing, logging, messaging systems, and any other place where you want to keep IDs in the order they happened and maybe even use the ID to quickly figure out when an event happened. ULIDs are used to make k-sortable keys in many NoSQL databases (like some key-value stores) and messaging systems. This makes time-range queries faster. One thing to keep in mind is that ULIDs depend on timestamps, so make sure that all systems’ clocks are synchronised to some extent. If a machine’s clock is way off, its ULIDs will show that (even though timestamps that are out of order will eventually sort themselves out by absolute time, you might get some out of order if one server’s clock is a little slow). If you need to work with IDs by hand from time to time, like copying one from one place to another, ULIDs are helpful because they don’t have hyphens and are shorter, which makes mistakes less likely. ULID is a proven alternative if your environment doesn’t yet easily support UUIDv7. Just make sure to use a good library to handle the monotonic increment correctly. Also, if you don’t need to read them directly in the DB, think about storing them as binary for efficiency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When to use UUIDv7:&lt;/strong&gt; UUIDv7 is a great default for most new systems, especially internal microservices and databases. It works with the UUID standard and gives you almost the same benefits as ULID when it comes to sorting and indexing. If you work in an environment that is quickly adopting new standards or if you want to make sure your IDs will work in the future, UUIDv7 is the best choice. For example, if you’re using PostgreSQL and want to use the native UUID type because it’s easier, you can make UUIDv7 in your app and save it without any problems. UUIDv7 is great for primary keys in databases where you want to avoid the problems that come with auto-increments, like hitting sequence limits or sequence contention, but you still want the speed of sequential inserts. They are also useful in distributed systems where you need timestamps in IDs to help with debugging or sorting events around the world. One important thing to think about is that if the timestamp being embedded is thought to be sensitive (for example, it could show how users behave in some situations), then treat the IDs as if they are sensitive too. But for internal use, like database keys and internal messaging, that’s usually not a problem.&lt;/p&gt;</description></item></channel></rss>