6

I've heard advice from several people about using UUIDs as ID in your database. For one this has the benefit of making your URLs unguessable. It also masks how many objects you have in the system. e.g. If your user ID is #412, then you know there are about 412 users in the system.

One way to do this is to use a UUID as the primary key in your database instead of a long or an integer. I've heard this can have performance implications on your database.

I've also heard that you should never expose the primary key to your end users. This is because it exposes an implementation detail that the users don't need to know about. The problem with this is you essentially have two IDs, one public and one private and it adds complexity between swapping between the two.

For instance let's say I have an account object and each account can have multiple users. My user object would need a reference to the foreign key of the accounts table. Which ID do I reference? The public one or the private one? When end users call the API they will POST with the public ID of the account because they don't know the private, primary ID.

Or is this all just too much work and not worth it. I know some companies don't bother and just expose the primary key. Then again I don't want to be Parler. https://www.wired.com/story/parler-hack-data-public-posts-images-video/

3
  • 4
    Right, so you understand there are trade-offs here and there is no one single perfect answer which applies in every scenario. What do you need help with? Commented Oct 6 at 19:54
  • Does there have to be user information in the URL? Could you use a login and secure cookies instead? Commented Oct 7 at 16:07
  • 3
    I typically do both, a numeric primary key for internal use and a guid for public reference. Commented Oct 7 at 19:16

3 Answers 3

11

Lots of questions here,

  1. There are many reason to use a UUID as a PK in your database rather than an auto incremented int. The main reason though is because you can create it before and without calling the database.

    This means you can construct a complex object with FKs in your front end, or distributed microservice without any round trips to a DB.

  2. There are many reasons not to expose a db/internally generated PK to external users. But the main reason is that usually this key is meaningless outside of the internal system

    By exposing, or using, some other, natural, key you avoid the number of ids expanding with every system the entity is saved to. This doesn't mean you don't need a key of some kind though. It also doesn't mean you should create extra keys that are not used.

For instance let's say I have an account object and each account can have multiple users. My user object would need a reference to the foreign key of the accounts table. Which ID do I reference?

On your accounts table it would be great if you have a natural key. For example an email address is unique and can serve as a PK. You don't need to add an extra UUID or int ID.

Or is this all just too much work and not worth it. I know some companies don't bother and just expose the primary key

You have to expose a unique key of some kind if you have an interface that returns a single record. The key can be:

  1. A natural key like email address
  2. An internally generated key like a UUID or auto inc Int
  3. A composite key, for example firstname + lastname + company

The advice you are reading is saying, don't just add an auto inc int id to everything and expose it. Natural keys are the best, and if the natural key isn't unique maybe there is a composite natural key. If there is no natural key, you don't have to hit the database or some other shared service and expose information with an auto inc id. You can use a random string of some kind.

9
  • 2
    There is a distinction between EXPOSED and INTERNAL natural keys. I have no issue with exposing natural keys (per @Ewans answer). However I would caution against using natural keys (or composite natural keys) as part of the internal foreign key constraints. The issue is typo's - if you have the natural key as just a field (possibly with a unique index) in a single table, it's easy to correct a typo. It's a pain in the behind to update multiple tables to correct a typo'ed natural key if it's part of the PK and FK's.
    – DavidT
    Commented Oct 6 at 22:56
  • 14
    Regarding email address as PK, the problem is that users occasionally want/need to change their email address. That becomes much harder if it is also used in various FK relations. In such a case, it would be better to have the email address as external key, but also have an internal, generated and immutable, key for the relations. Commented Oct 7 at 7:21
  • @BartvanIngenSchenau that would be a good case for exposing the email but having an additional hidden pk
    – Ewan
    Commented Oct 7 at 8:23
  • @DavidT if you can avoid them though you can save your self a million and one id,name tables and joins. In most cases you aren't relying on user input for these. ie country code, currency etc
    – Ewan
    Commented Oct 7 at 8:26
  • 5
    While I love using "natural" keys as PKs, in some database (such as MySQL), the primary key is used a a reference to the row within secondary indexes. The larger the key, the larger the index. Also as pointed out by @BartvanIngenSchenau e-mails do not make natural PKs for users, not only because the same user can change e-mail, but also because in certain cases a user can be removed and then re-added using the same e-mail address (but should still be a different user). Not to mention the case of having multiple e-mails for a single user.
    – jcaron
    Commented Oct 7 at 14:41
5

At the end of the day a UUID is just a 128 bit integer with:

  • A special formatting rule used to display it to humans.
  • A non-sequential generation rule.

Note: there are multiple UUID generation formats and nothing stopping you from inventing your own sequential one (although I would question the value of doing that).

Performance

Generally speaking processing less data (i.e. smaller 32 or 64 bit integers) is quicker than processing more data. However having non sequential data can have some advantages for example, when indexing it means that the majority of new records are not all being inserting at the end of the index.

However there is an open question of whether the extra 8 or 12 bytes per record have a significant impact on overall system performance - frankly you will have to benchmark it and weigh the pro's and con's against the other factors.

Leaking Growth/Size Information

If you use a sequential number for anything and an adversary has the ability to see newly created numbers on some frequent basis, then the adversary is able to identify how many of that thing are being created in a set time frame.

One adversarial use case would be in conjunction with the financial markets for estimating new customer or order growth - however any use case that relies on data growth numbers would also benefit.

Data Sparseness

If you had 10,000 widgets, but store the widget ID in a short (16 bit) integer, even if you distribute the ID's across the entire range (0 - 65535) it would still be practical for an attacker to brute force all of them by iterating through the entire range.

Hence with UUID's it is the combination of both the size of the range (128 bits) and the non sequential generation that provides the sparseness benefit.

Human Entry

Typically businesses use "short" numeric IDs for customer, order or account numbers as it is likely someone will telephone a call center and need to give the number verbally to a representative, who will type it into a computer.

If you expose an API to a third party that also interacts with humans they may need a lookup function, to identify a record by a "Human ID" however after the initial lookup they can probably use a UUID for all future functionality.

Unfortunately these human ID's likely violate the previous two points (Data Growth and Sparseness), however its going to be difficult to avoid that and still keep the ID's small for humans.

Summary

There is no "right" answer here its all trade offs, I typically make my life simple by using UUID's for everything I can:

  • Primary keys
  • Foreign keys between tables
  • ID's in API/URL paths.

Then supplement these with Human ID's where required for business functions.

However your milage may vary - for example you might choose to use a composite key of the OrderID and a simple line number on an OrderLines table.

10
  • 2
    Whilst you list the non-locality of inserts as a performance advantage, a lot of sources I have seen regard this as a major disadvantage in most cases where it matters at all, because it means you are much more likely to miss cache and need to fetch pages from disk. Which is a lot more significant than issues around lock contention unless your inserts are perticuarly hot. Commented Oct 6 at 22:55
  • I think the standard answer to this is benchmark multiple UUID generation functions, as long as time is the most significant component of the UUID, records that are generated around the same time will be close in the index. However that may reverse the previous benefit about not having the end of the index become hot. Trade-offs and benchmarking required.
    – DavidT
    Commented Oct 6 at 23:28
  • @user1937198, keep in mind that the considerations regarding locality of inserts can be vastly different for memory resources and database resources. The arguments on the one do not apply (automatically) to the other. Commented Oct 7 at 7:43
  • 1
    i store all my guids as varchar(50). UTF16 is best, youve got to think about international guids
    – Ewan
    Commented Oct 7 at 10:40
  • 1
    @DavidT No, you should absolutely not store UUIDs as varchars. uuid, if your dbms supports it, or some uint128 type otherwise. Afaict, JoryGeerts was trying to hint at problems when using inappropriate data types, Ewan was joking about it, and gidds didn't get the sarcasm (or was expanding on it, and I didn't get it).
    – Bergi
    Commented Oct 7 at 21:33
3

A couple of points of clarification here.

For one this has the benefit of making your URLs unguessable.

You are likely thinking of v4 UUIDs. If you plan to use UUIDs for any purpose, you should familiarize yourself with the various types of UUIDs.

Most versions of UUIDs are not fully random. Some encode information about the system that created them, timestamps, etc. On a side note, using UUIDs as a security feature is likely misguided. They are not designed for that purpose (even v4) so it's basically rolling your own security. Also, the unpredictability of an v4 Id depends on the quality of the PRNG which has some other implications that are discussed below.

I've also heard that you should never expose the primary key to your end users. This is because it exposes an implementation detail that the users don't need to know about. The problem with this is you essentially have two IDs, one public and one private and it adds complexity between swapping between the two.

I agree with the guidance to not expose primary keys. The reason is that once you expose a key, it can constrain your flexibility to make changes to your design or at least complicate them. It shouldn't matter to your clients whether your key is the actual primary key of your table or a key on a table at all. They just need an identifier. Having an Id designed for the client's use and independent of your internal identifiers can help you avoid some really thorny problems. For example, if an account is compromised and you want to issue a new Id, it's usually a lot easier if you don't have to worry about modifying primary keys or internal ids. This is a useful feature, not a burden.

Large numbers

One of the issues with using UUIDs that you don't mention is that as 128-bit numbers, UUIDs have a huge number space. I mean, really extraordinarily large. That's a big part of how they work but creates some challenges. The big one when working with DBs is that most common indexing strategies don't work well with sparse numbers, and this can lead to performance issues. Some of the UUID versions produce values which are particularly bad for many indexing strategies. If you index a UUID, make sure you are using an index that can support them efficiently. Also realize this limits your indexing options.

Another potential issue is generating good random numbers. The fact that UUIDs are such large numbers does not, by itself, preclude collisions. You must also ensure your generator(s) have a good distribution across the available number space. Often this means using a secure PRNG (SPRNG) and those generally require a source or pool of entropy. In a high-volume situation, this pool can be 'drained' and the SPRNG can become a bottleneck for Id generation. If you are using UUIDs generated outside of your system, you also have a risk that the clients do not generate good, random UUIDs.

Another issue with UUIDs is they are really hard to read. If you need to have a user or support team member saying these Ids over the phone, for example, it's a pretty miserable situation. And when comparing UUIDs, I think it's pretty common to look at the beginning and end of the UUID without checking every character. This can create opportunities for social engineering.

While UUIDs are useful in a lot of scenarios, I think they are often oversold as a panacea. They introduce their own issues. You can provide a non-PK Id to your clients without using UUIDs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.