UUIDv4 contains no information, so you cannot accidentally leak information through them.
UUIDs have nothing to do with cryptography. "Cryptographically safe" has no meaning when it comes to identifiers. Encrypting information-carrying identifiers like UUIDv7 is not a good way to hide the information, because you cannot rotate the key if/when it leaks. The moment your key leaks, all information in the identifier is public again.
There's only three ways to mitigate the risk of information-carrying identifiers:
* Don't have them. Use fully random identifiers instead. * Don't share them. Use a different identifier in public (including in URLs). * Carefully consider the information carried in the identifier to decide if the information can be made public.
For one similar example with sequentially generated IDs - https://www.albert.io/blog/german-tank-problem-explained-ap-....
That being said, unless you have a specific reason to use a UUID over the standard incrementing integer, I wouldn't if for no other reason than dealing with UUIDs is visually complicated.
Clustering indexes make a lot of sense for many workloads, you just have to design your data model with them in mind.
The effect is just worsened by clustering, as done in SQL Server for significant (or at least measurable) benefits for many data patterns, because your base data is bigger due to wasted space due to excess page splits, as well as your supplementary indexes, so that needs reorganising more often too.
A common answer is to use an int (or maybe bigint) for your internal keys and a UUID for anything external, so you have the benefits of a UUID for external use (practically zero chance of collision in distributed systems, if appropriately random, and not potentially leaking information in some security contexts) but the efficiently of the integer otherwise. Or to use a partially ordered UUID format, which balances the compromises slightly differently (benefit: single value, dropping some of the UUID issues, keeping some of the benefits; detriment: letting some of the disadvantages of UUIDs, potentially reducing some of the benefits).
Why would you think that? You think it's equally likely to need to look up a salesorder from ten years ago vs one from two weeks ago? Are Github issues from ten years ago read equally often as the ones created yesterday? Most systems have a long tail of historic data that's mainly kept as a reference.
SELECT * FROM forum_post WHERE created_at > $DATE AND created_at < $DATE
If you were using UUIDv7, you could even use that to get the created_at [0], although you'd likely lose the indexing unless you made a functional index on it, like this: CREATE INDEX foo_ctime_idx ON foo(extract_timestamp_from_uuid_v7(uuid_v7_col))
[0]: https://stackoverflow.com/a/75869619As to alternatives:
First, if you can model your data using a natural key (singular or composite), and it doesn't unduly bloat the key, do so. This isn't always possible, of course (you wouldn't want to use an email address as a PK, as tempting as it may seem, since they're subject to change by users), but in many cases it is. Even better, for pre-existing data that's unlikely to change, like SKUs, you could pre-sort the data so you avoid any random page costs.
Barring that, design your API in such a way that you can use an incrementing integer without fear of leaking anything. At the very least, you can create an association table that maps an external ID (preferably UUIDv7 or ULID, as they're k-sortable) to the internal integer ID.
If you can't do either, use UUIDv7 or ULID. I prefer UUIDv7, especially since Postgres will be natively supporting their generation in an upcoming release (and because you can store them in RDBMS' native binary format), but either will do.