UUID vs ULID vs Nano ID vs IDs autoincrementais

Cinco esquemas comuns de ID comparados no que importa na prática: tamanho, ordenação, chance de colisão, comportamento no banco de dados e o que um ID entrega.

A escolha do formato de ID se resume a três perguntas. Os IDs precisam ser ordenados pelo momento de criação? Vão aparecer em URLs ou em APIs públicas? E o banco de dados vai guardá-los em uma coluna nativa de 128 bits ou como texto? O UUID v7, padronizado na RFC 9562 em maio de 2024, mudou a resposta para muitos projetos: ele oferece a ordenação por tempo que tornou o ULID popular, em um formato que toda coluna UUID e toda biblioteca já aceitam.

Visão geral

AutoincrementoUUID v4UUID v7ULIDNano ID
Exemplo1042f47ac10b-58cc-4372-a567-0e02b2c3d47901926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f1001J9QKMZ1BFN79M7SVBJ6JWVRGV1StGXR8_Z5jdHi6B-myT
Tamanho em binário4 ou 8 bytes16 bytes16 bytes16 bytesGuardado como texto
Tamanho em textoAté 10 ou 19 dígitos36362621 por padrão
Bits aleatóriosNenhum122Até 7480126 por padrão
Ordena pelo momento de criaçãoSimNãoSim, até o milissegundoSim, até o milissegundoNão
Gerado sem o banco de dadosNãoSimSimSimSim
EspecificaçãoRecurso do banco de dadosRFC 9562RFC 9562Especificação da comunidadeBiblioteca
RevelaQuantidade e ordemNadaMomento de criaçãoMomento de criaçãoNada

Como cada um é construído

  • Autoincremento é um contador mantido pelo banco de dados. Um INT de 32 bits com sinal termina em 2.147.483.647, e um BIGINT com sinal, em 9.223.372.036.854.775.807. A aplicação só conhece o ID depois do insert, a menos que reserve valores de uma sequence antes.
  • UUID v4 são 122 bits aleatórios mais 6 bits fixos que marcam a versão e a variante.
  • UUID v7 começa com um timestamp Unix de 48 bits em milissegundos, seguido da versão, de 12 bits chamados rand_a, da variante e de 62 bits chamados rand_b. A RFC 9562 permite que um gerador use parte desses bits como contador, para que IDs criados no mesmo milissegundo continuem saindo em ordem. O gerador deste site faz isso com os 12 bits de rand_a.
  • ULID é um timestamp de 48 bits em milissegundos mais 80 bits aleatórios, escrito como 26 caracteres em Base32 de Crockford: 10 para o tempo e 16 para a parte aleatória. O alfabeto deixa de fora I, L, O e U e não diferencia maiúsculas de minúsculas. No modo monotônico, um ULID criado no mesmo milissegundo que o anterior pega a parte aleatória desse ULID e soma um a ela.
  • Nano ID é uma string de caracteres aleatórios, por padrão 21, tirados do alfabeto de 64 caracteres A-Z a-z 0-9 _ -. Cada caractere carrega 6 bits, o que dá 126 bits. Tanto o tamanho quanto o alfabeto podem ser alterados, e o número de bits aleatórios muda junto.

Um ULID e um UUID são, os dois, números de 128 bits escritos de formas diferentes. 01J9QKMZ1BFN79M7SVBJ6JWVRG e 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 são o mesmo valor, em Base32 e em hexadecimal. Por isso um ULID pode ser guardado em uma coluna UUID nativa, como o uuid do PostgreSQL, e mantém a sua ordenação ali. Um ULID que não nasceu como UUID v7, porém, tem bits arbitrários onde deveriam estar a versão e a variante, então um validador de UUID estrito pode rejeitá-lo.

Chance de colisão, calculada

Para IDs puramente aleatórios vale a aproximação do paradoxo do aniversário: com n IDs sorteados entre N = 2 elevado ao número de bits aleatórios, a chance de haver pelo menos um duplicado é de cerca de 1 − e^(−n²/2N), e ela chega a 50% perto de n = 1,18 × √N.

EsquemaBits aleatóriosIDs para 50% de chance de um duplicadoChance de um duplicado entre 1 bilhão de IDs
UUID v41222,7 × 10^189,4 × 10^-20
Nano ID, 21 caracteres1261,1 × 10^195,9 × 10^-21
Nano ID, 10 caracteres601,3 × 10^9Cerca de 35%

A última linha é a que vale lembrar: encurtar um Nano ID para deixar as URLs mais bonitas custa resistência a colisões muito depressa, então faça essa conta antes de escolher um tamanho.

No UUID v7 e no ULID, dois IDs só podem colidir se carregarem o mesmo milissegundo, então n é o número de IDs criados em um milissegundo por geradores que não compartilham um contador. Com 1.000 desses IDs no mesmo milissegundo, a chance de um duplicado é de cerca de 2,6 × 10^-17 com 74 bits aleatórios e de 4,1 × 10^-19 com os 80 do ULID. Na prática, o risco de todos os esquemas desta página é uma fonte de aleatoriedade fraca, como Math.random, e não a matemática.

Comportamento nos índices do banco de dados

Chaves primárias costumam ficar em um índice B-tree. Quando cada nova chave é maior que a anterior, como no autoincremento, no UUID v7 e no ULID, os inserts caem na borda direita do índice, as páginas ficam totalmente cheias e as linhas gravadas recentemente ficam próximas umas das outras. Chaves aleatórias, como UUID v4 e Nano ID, caem em qualquer lugar: as páginas se dividem, ficam parcialmente vazias e, quando o índice deixa de caber na memória, cada insert pode significar uma leitura aleatória.

O efeito é mais forte no MySQL e no MariaDB com InnoDB, que guardam as próprias linhas na ordem da chave primária, então chaves aleatórias espalham a tabela, e não só o índice. O PostgreSQL mantém as linhas em um heap separado, então a diferença fica restrita ao índice.

O formato de armazenamento importa tanto quanto o esquema:

  • Guarde UUIDs e ULIDs em um tipo nativo de 16 bytes quando ele existir: uuid no PostgreSQL, UUID no MariaDB 10.7 e posteriores, BINARY(16) no MySQL. CHAR(36) ocupa mais que o dobro do espaço e deixa todos os índices maiores.
  • No MySQL, não passe a flag de troca para UUID_TO_BIN(uuid, 1) com valores v7. Ela reorganiza os campos de tempo em benefício dos UUIDs v1 e quebraria a ordenação do v7.
  • O uniqueidentifier do SQL Server compara primeiro os últimos seis bytes, então valores v7 guardados nele não ficam ordenados por tempo. Os detalhes estão em UUID vs GUID.
  • Nano IDs são strings e são guardados como CHAR ou VARCHAR, com uma collation binária se maiúsculas e minúsculas precisarem ser diferenciadas.

URLs e o que um ID entrega

Os cinco formatos são seguros em uma URL sem codificação percentual: dígitos hexadecimais, hífens, Base32 de Crockford e o alfabeto do Nano ID são todos caracteres não reservados na RFC 3986. A diferença está no tamanho (36, 26 ou 21 caracteres) e no uso de maiúsculas. UUIDs e ULIDs não diferenciam maiúsculas de minúsculas, então sobrevivem a sistemas que mudam a caixa do texto; Nano IDs não.

O que um ID revela muitas vezes é o fator decisivo:

  • Autoincremento revela volume. Se a sua fatura é a 1042 hoje e a 1342 uma semana depois, a empresa emitiu cerca de 300 faturas naquela semana. IDs sequenciais também tornam trivial testar registros vizinhos, o que transforma qualquer verificação de acesso esquecida em vazamento de dados.
  • UUID v7 e ULID revelam quando o registro foi criado, até o milissegundo. Para pedidos isso raramente é um problema; para contas de usuário ou prontuários médicos, pode ser. Cole um UUID v7 no inspetor do gerador de UUID v7 e ele mostra o timestamp.
  • UUID v4 e Nano ID não revelam nada sobre quando ou onde foram criados.

Nenhum desses IDs é um segredo. Um ID impossível de adivinhar dificulta a sondagem, mas não substitui a verificação de que o usuário tem permissão para ver aquele registro.

Qual escolher

  • Tabelas internas em um único banco de dados, nunca mostradas aos usuários: BIGINT com autoincremento. É a chave menor e mais simples.
  • Chaves primárias criadas pela aplicação ou entre serviços, quando há uma coluna UUID nativa disponível: UUID v7.
  • Identificadores públicos em que o momento de criação deve ficar privado, IDs de correlação e nomes de arquivo: UUID v4, do gerador de UUID v4 ou de crypto.randomUUID().
  • IDs curtos em URLs, quando a ordem não importa: Nano ID no tamanho padrão ou perto dele.
  • ULID quando a sua stack já o usa ou quando você quer a forma de texto mais curta e sem diferença entre maiúsculas e minúsculas. Em projetos novos, o UUID v7 cobre o mesmo terreno em um formato padronizado.

Uma combinação comum reúne o melhor dos dois mundos: uma chave primária BIGINT ou UUID v7 para joins e índices, mais um ID público aleatório separado, com índice único, para URLs e APIs.

Perguntas Frequentes

O ULID é melhor que o UUID v7?

Os dois são construídos da mesma forma: um timestamp de 48 bits em milissegundos seguido de bits aleatórios, 128 bits no total. O UUID v7 é um padrão do IETF que se encaixa em colunas e validadores de UUID. O ULID tem uma forma de texto mais curta, sem diferença entre maiúsculas e minúsculas, e 80 bits aleatórios em vez de até 74.

O Nano ID é tão único quanto um UUID?

No tamanho padrão de 21 caracteres, sim: ele tem 126 bits aleatórios, contra 122 do UUID v4. Nano IDs mais curtos perdem essa vantagem rapidamente; com 10 caracteres, a chance de um duplicado chega a 50% depois de cerca de 1,3 bilhão de IDs.

Valores UUID v7 podem colidir?

Só IDs criados no mesmo milissegundo por geradores independentes podem colidir. Com 1.000 deles em um milissegundo, a chance é de cerca de 2,6 × 10^-17, então uma boa fonte de aleatoriedade importa muito mais que o formato.

Por que UUIDs aleatórios são lentos como chave primária?

Cada nova chave cai em um lugar aleatório do índice B-tree, o que causa divisões de página, páginas meio vazias e mais leituras de disco em tabelas grandes. IDs ordenados por tempo, como UUID v7 ou ULID, são acrescentados no final.