Desenvolvimento

UUID, ULID e IDs: quando usar cada um

"É só um id aleatório" é onde muita decisão de arquitetura morre cedo. Um UUID v4 é ótimo para não coincidir, mas péssimo como chave primária que o banco precisa manter ordenada. Um ULID resolve a ordenação, mas expõe o horário de criação. Escolher entre UUID v1, v4, v7 e ULID é escolher o que você aceita vazar e o que precisa garantir. Este guia abre a anatomia de cada formato, bits, versão, variante,, percorre as oito versões da RFC 9562, calcula a chance real de colisão, mostra como extrair a data de um id ordenável e compara as alternativas de mercado. Gere e valide os seus no [gerador de UUID](tool:gerador-uuid) e leia o carimbo de tempo de um id ordenável no [conversor de timestamp](tool:conversor-timestamp).

J-Kit18 min de leituraIntermediário
  • UUID
  • ULID
  • Banco de dados
  • Chave primária
  • Desenvolvimento

Resumo rápido

  • Um UUID tem 128 bits; o v4 gasta 122 deles em aleatoriedade, o resto marca versão e variante. A RFC 9562 define oito versões, da v1 à v8.
  • v7 e ULID são ordenáveis por tempo, o que dá localidade de índice; v4 é aleatório e provoca page splits que fragmentam o B-tree.
  • IDs ordenáveis vazam o horário de criação; v4 é opaco. Escolha pelo que aceita expor.
  • Fora do UUID há Snowflake, KSUID e NanoID; ids numéricos grandes precisam viajar como string em JSON para não perder precisão.

Anatomia de um UUID

Um UUID (Universally Unique Identifier) são 128 bits escritos como 32 dígitos hexadecimais em cinco grupos: 8-4-4-4-12, totalizando 36 caracteres com os hífens. Dentro desses bits, seis não são livres: quatro marcam a versão e dois marcam a variante (o esquema do padrão). Na prática, o primeiro dígito do terceiro grupo indica a versão, e os bits mais altos do quarto grupo indicam a variante. Tome 550e8400-e29b-41d4-a716-446655440000: o dígito "4" antes de "1d4" diz que é versão 4, e o "a" que abre "a716" carrega os bits de variante RFC 4122, exatamente o que a aba de validação do gerador de UUID reporta ao colar esse valor.

128 bitstamanho total de qualquer UUID
36caracteres na forma canônica (com hífens)
6 bitsfixos: 4 de versão + 2 de variante
  1. 2005RFC 4122

    A primeira especificação padronizada de UUID, com as versões v1, v3, v4 e v5. Nenhuma delas é ordenável por tempo de forma útil para bancos de dados.

  2. 2016ULID

    Alizain Feerasta publica a especificação do ULID: 128 bits com o tempo na frente e ordenação lexicográfica, resolvendo o que o UUID ainda não resolvia.

  3. Maio de 2024RFC 9562

    Obsoleta a RFC 4122 e acrescenta v6, v7 e v8, trazendo a ordenação por tempo para dentro do próprio padrão UUID.

Como a versão e a variante ocupam posições fixas, você pode reconhecer um UUID por regex, o padrão pronto da biblioteca do construtor de regex exige justamente o dígito de versão e os bits de variante. Se quiser entender essa expressão símbolo a símbolo, veja o guia de regex do zero. E, quando quiser inspecionar um id de verdade, use o gerador logo abaixo: cole qualquer UUID e ele mostra a versão e a variante que aqueles seis bits declaram.

Gere UUIDs v4 com Web Crypto ou cole um id na aba de validação para ler versão e variante, tudo no navegador, sem rede.Abrir a ferramenta em página inteira

As oito versões, de v1 a v8

A RFC 9562 define oito versões; algumas você usa toda semana, outras quase nunca. A v1 combina o horário (em intervalos de 100 ns desde 1582) com o endereço MAC da máquina, é ordenável no tempo, mas vaza o hardware. A v4 é praticamente toda aleatória: 122 bits de acaso, sem tempo nem máquina, o que a torna opaca e imprevisível. A v7, mais nova, coloca um timestamp Unix de 48 bits em milissegundos no começo e preenche o resto com aleatoriedade, unindo o melhor dos dois mundos: ordem temporal sem revelar o MAC. Cada versão tem uma seção própria na RFC, o que facilita conferir a fonte primária.

As oito versões de UUID da RFC 9562, cada uma com a sua seção.
VersãoConteúdoSeçãoOrdenável?
v1tempo (desde 1582) + MAC§5.1Parcial
v2DCE Security (raro)§5.2Não
v3hash MD5 de namespace + nome§5.3Não
v4122 bits aleatórios§5.4Não
v5hash SHA-1 de namespace + nome§5.5Não
v6v1 com o tempo reordenado§5.6Sim
v7timestamp Unix 48 bits + aleatório§5.7Sim
v8livre / experimental (você define os bits)§5.8Depende

As versões v3 e v5 são diferentes de todas as outras: elas não sorteiam nada. Você fornece um namespace (um UUID que representa um domínio, como DNS ou URL) e um nome (uma string qualquer), e a versão calcula um hash, MD5 na v3, SHA-1 na v5, dessa combinação. O resultado é determinístico: o mesmo namespace com o mesmo nome sempre produz o mesmo UUID. A RFC 9562 (§6.5) é explícita: "UUIDs gerados em momentos diferentes a partir do mesmo nome, no mesmo namespace, DEVEM ser iguais". Isso é ouro para idempotência, você deriva o id de um dado estável (o e-mail do usuário, a URL de um recurso) e reencontra o mesmo id sem consultar o banco. Se a ideia de derivar um valor de tamanho fixo a partir de um texto ainda soa nebulosa, o guia de hashing vs. criptografia vs. encoding fecha o conceito, e você pode ver um SHA-1 nascer no gerador de hash.

As três novidades da RFC 9562, v6, v7 e v8, existem por um motivo declarado: a v4 tem, nas palavras da própria RFC, "péssima localidade de índice em banco de dados". A v6 é para quem já usava v1 e quer ordenação: pega os mesmos campos da v1 e reordena os bits de tempo para que a ordenação por bytes vire ordenação por data. A v7 é a recomendação para novos sistemas: timestamp Unix em milissegundos na frente, aleatoriedade atrás. A v8 é o coringa, o padrão reserva a versão para layouts experimentais ou proprietários, em que você controla todos os bits menos os de versão e variante. Foi a v8 que deu ao ecossistema um lugar oficial para encaixar formatos como o ULID dentro do envelope UUID.

ULID: ordenável e compacto

O ULID (Universally Unique Lexicographically Sortable Identifier) também tem 128 bits, mas é escrito em 26 caracteres usando o Base32 de Crockford, um alfabeto de 32 símbolos (0123456789ABCDEFGHJKMNPQRSTVWXYZ) que descarta as letras I, L, O e U para evitar confusão visual e é seguro em URLs. Os primeiros 48 bits são um timestamp em milissegundos desde 1970; os 80 bits restantes são aleatórios. Como o tempo vem na frente e o Base32 preserva a ordem, ordenar ULIDs como texto é ordená-los por data de criação, sem índice extra.

UUID v4

  • 36 caracteres, hexadecimal com hífens.
  • 122 bits aleatórios; nenhuma ordem.
  • Não revela quando foi criado.

ULID

  • 26 caracteres, Base32 de Crockford, sem hífen.
  • 48 bits de tempo + 80 aleatórios; ordenável.
  • Expõe o horário de criação no prefixo.
01ARZ3NDEKTSV4RRFFQ69G5FAV
└──────────┘└───────────────┘
  48 bits       80 bits
  timestamp     aleatoriedade / randomness
ULID de exemplo da especificação: tempo à esquerda, acaso à direita.

Há um detalhe fino que separa um ULID bem-feito de uma imitação: a monotonicidade. Se dois ULIDs nascem no mesmo milissegundo, os 48 bits de tempo são idênticos, e sortear os 80 bits de acaso de forma independente poderia inverter a ordem dentro daquele milissegundo. A especificação resolve isso por regra: quando o mesmo milissegundo é detectado, o gerador não sorteia de novo, ele incrementa o componente aleatório em 1 no bit menos significativo, com carry. Assim, dois ULIDs do mesmo milissegundo ficam a uma unidade de distância e continuam estritamente ordenados. O limite é gerar mais de 2^80 ids em um único milissegundo, quando o incremento estoura e a geração falha, um cenário que nenhum sistema real alcança.

A chance real de colisão do v4

A pergunta que sempre aparece: dois v4 aleatórios podem coincidir? Em teoria sim, mas os números tornam a preocupação irrelevante. Com 122 bits de aleatoriedade, existem 2^122 ≈ 5,3 × 10^36 valores possíveis. Pelo paradoxo do aniversário, a chance de 50% de haver uma única colisão só surge por volta da raiz quadrada do espaço total, e é essa raiz que a fórmula abaixo calcula.

n ≈ 1.177 × sqrt(2^b)
n
quantidade de ids gerados para 50% de chance de uma colisão
b
bits de aleatoriedade do formato (122 no UUID v4)
1.177
a constante √(2 · ln 2), do paradoxo do aniversário
Número aproximado de ids para 50% de chance de uma colisão. Para b = 122: sqrt(2^122) = 2^61 ≈ 2,3 × 10^18; multiplicado por 1,177, dá n ≈ 2,7 × 10^18.
5,3 × 10³⁶valores v4 possíveis (2^122)
2,7 × 10¹⁸gerados para 50% de chance de 1 colisão
~86 anosgerando 1 bilhão por segundo

Vamos fazer a conta com números reais. Para o v4, b = 122, então a raiz quadrada de 2^122 é 2^61 ≈ 2,3 × 10^18. Multiplicando pela constante 1,177, chega-se a n ≈ 2,7 × 10^18, os 2,7 quintilhões que aparecem em todo lugar. Agora traduza para tempo: gerando 1 bilhão (10^9) de UUIDs por segundo, seriam 2,7 × 10^18 ÷ 10^9 = 2,7 × 10^9 segundos, ou cerca de 86 anos ininterruptos, para chegar a uma única chance em duas de um par repetido. Na prática, a colisão de v4 não é uma preocupação de engenharia, desde que a fonte de aleatoriedade seja criptográfica, como a Web Crypto usada pelo gerador. O risco real mora em geradores fracos (Math.random) ou sementes previsíveis, não na matemática.

Quando usar cada um e como ler a data

A escolha se resume a três eixos: localidade de índice, necessidade de ordenação e o que você aceita expor. IDs aleatórios como o v4 caem em posições espalhadas de um índice B-tree; a cada inserção o banco mexe em páginas diferentes, o que fragmenta o índice e piora o cache. IDs com tempo na frente (v7, ULID) chegam sempre "no fim", mantendo as inserções contíguas, é a tal localidade de índice que a própria RFC 9562 cita como motivação para o v7.

  • Chave primária em banco de alto volume: prefira v7 ou ULID pela localidade de índice.
  • Token público, id de sessão ou algo que não pode revelar quando foi criado: use v4, opaco por natureza.
  • Precisa ordenar por criação sem coluna de data: ULID resolve com a ordenação lexicográfica.
  • Precisa de um id determinístico a partir de um dado estável (idempotência): use v5 baseado em nome.
  • Nunca use o id sequencial do banco em URL pública, ele vaza a contagem de registros e permite enumeração.

Um bônus prático dos ids ordenáveis: os 48 bits iniciais são o instante de criação, então você lê quando o registro nasceu sem consultar o banco. O passo a passo abaixo funciona tanto para ULID quanto para UUID v7, e o conversor de timestamp faz a última etapa por você. Se o horário sair "errado", provavelmente é fuso: o valor é sempre UTC, o guia de fusos horários e horário de verão explica por que o mesmo instante muda de rótulo conforme a zona.

  1. Isole o campo de tempoNo ULID, são os 10 primeiros caracteres; no UUID v7, os 12 primeiros dígitos hexadecimais (os dois primeiros grupos, sem o hífen).
  2. Decodifique para inteiroULID: converta de Base32 de Crockford. v7: interprete os 12 hex como um número. O resultado é o tempo em milissegundos desde 1970.
  3. Converta para dataCole os milissegundos no conversor de timestamp; ele detecta que valores acima de 10^12 estão em milissegundos e devolve a data UTC e local.
  4. Confirme a versãoUm v4 não tem tempo embutido, se você tentar isto num v4, vai ler ruído, não uma data.
ULID   01ARZ3NDEK TSV4RRFFQ69G5FAV
              │         │
       10 chars (48 bits)   16 chars (80 bits)

Base32 Crockford: 0 1 A R Z 3 N D E K
           valor: 0 1 10 24 31 3 21 13 14 19
              ms: 1469922850259
            data: 2016-07-30T23:54:10.259Z (UTC)

v7     01563e3a-b5d3-7... (mesmos 48 bits em hexadecimal)
Extraindo a data: os 10 primeiros caracteres do ULID viram 1469922850259 ms; o mesmo instante em um v7 seria 01563e3a-b5d3 nos 12 primeiros hex.

Exemplo trabalhado: pegue o ULID de exemplo da especificação, 01ARZ3NDEKTSV4RRFFQ69G5FAV. Os 10 primeiros caracteres, 01ARZ3NDEK, são o timestamp. Decodificando de Base32 de Crockford, cada caractere vira um valor (0, 1, 10, 24, 31, 3, 21, 13, 14, 19) e o conjunto forma o inteiro 1.469.922.850.259, que é o instante em milissegundos desde 1970. Cole esse número no conversor de timestamp e leia a data: 30 de julho de 2016, 23:54:10 UTC. Quando esses ids viajam dentro de um payload, vale tratá-los como string para não perder precisão; o guia de conversão entre JSON e YAML explica por que inteiros muito grandes se corrompem em JavaScript.

Alternativas e armadilhas

UUID e ULID não são as únicas opções. O Snowflake, criado no Twitter, empacota um id ordenável em apenas 64 bits: 1 bit de sinal (sempre 0), 41 bits de timestamp em milissegundos a partir de uma época própria (cerca de 69 anos de alcance), 10 bits de identificação de máquina (5 de datacenter + 5 de worker, até 1.024 nós) e 12 bits de sequência (4.096 ids por milissegundo por nó). É por isso que ids do Twitter e do Discord são números longos e ordenáveis. O KSUID, da Segment, vai na direção oposta do tamanho: 160 bits (timestamp de segundos + 128 bits de acaso), 27 caracteres em Base62, para quem quer ainda mais entropia. E o NanoID troca o padrão UUID por uma string curta e configurável, 21 caracteres por padrão, de um alfabeto URL-safe de 64 símbolos, o que dá 126 bits de aleatoriedade, colisão comparável à do v4 num formato bem menor.

Os formatos lado a lado: bits, ordenação, opacidade, tamanho textual e uso típico.
FormatoBitsOrdenávelOpacoTextoUso típico
UUID v4128 (122 aleat.)NãoSim36id genérico, token público
UUID v7128SimNão36chave primária ordenável
ULID128SimNão26chave/URL ordenável e curta
Snowflake64SimParcialaté 19 dígitosid distribuído (Twitter, Discord)
KSUID160SimNão27id ordenável com mais entropia
NanoID~126 (padrão)NãoSim21id curto em URL
bigint / bigserial64Sim (sequencial)Nãoaté 19 dígitosPK sequencial local
bigint8 B
UUID binário(16)16 B
NanoID (21)21 B
ULID (26)26 B
UUID texto (36)36 B
Bytes de armazenamento por identificador. Os formatos de texto assumem ASCII (1 byte por caractere); binário(16) e bigint são o tamanho bruto. Mesma unidade em toda a barra: bytes.
Ver os dados
CategoriaValor
bigint8 B
UUID binário(16)16 B
NanoID (21)21 B
ULID (26)26 B
UUID texto (36)36 B

O gráfico revela a primeira armadilha silenciosa: guardar um UUID como texto custa 36 bytes, mais que o dobro dos 16 bytes da forma binária. Isso não parece muito até você lembrar que a chave primária se repete em cada índice secundário e em cada chave estrangeira, o desperdício multiplica. As duas armadilhas mais comuns, o armazenamento em texto e a perda de precisão numérica, estão detalhadas abaixo, ao lado do mecanismo pelo qual o v4 fragmenta o índice.

Page splits e amplificação de escrita

Mecanismo: um índice B-tree guarda as chaves em páginas ordenadas. Quando você insere um valor que cai no meio de uma página já cheia, o banco precisa dividir aquela página em duas e copiar metade das linhas para a nova, é o page split. Um id sequencial ou com tempo na frente (v7, ULID) sempre entra na página mais à direita, então os splits são raros e ocorrem só na ponta. Um v4 aleatório cai em qualquer lugar, provocando splits espalhados por toda a árvore.

Efeito: cada inserção aleatória suja uma página diferente, então um único commit acaba reescrevendo muito mais páginas do que as linhas que você inseriu, é a amplificação de escrita. E como as páginas quentes ficam dispersas, o buffer pool (o cache de páginas em memória) não consegue segurar o conjunto de trabalho, e o banco vai buscar mais páginas no disco. Com id ordenável, o conjunto quente é só a cauda do índice, que cabe no cache. A RFC 9562 resume isso ao dizer que a v4 tem "péssima localidade de índice"; não há um percentual universal porque o impacto depende do motor, do tamanho da página e do volume, mas o mecanismo é sempre este.

v5 e idempotência: o id determinístico

Mecanismo: v3 e v5 derivam o id de um namespace mais um nome via hash (MD5 na v3, SHA-1 na v5). Como o hash é determinístico, o mesmo par entrada sempre gera o mesmo UUID. Isso permite calcular o id de um recurso a partir de um dado estável, por exemplo, o UUID v5 de "https://exemplo.com/pedido/42" no namespace URL, sem precisar de uma tabela de correspondência.

Uso: idempotência. Se um cliente reenvia a mesma requisição, você recalcula o mesmo id e detecta a duplicata sem consultar o banco antes. MD5 e SHA-1 são frágeis para segurança, mas aqui não há segredo em jogo, só derivação estável de um rótulo,, então servem bem. A diferença entre hash, cifra e codificação está no guia de hashing vs. criptografia vs. encoding.

UUID como texto vs. binário(16)

Guardar um UUID em uma coluna de texto (CHAR(36) ou VARCHAR) usa 36 bytes por valor; guardá-lo na forma binária de 16 bytes usa menos da metade. O PostgreSQL tem um tipo uuid nativo de 16 bytes; no MySQL, o padrão clássico é BINARY(16). Em uma tabela grande, com o id repetido em índices secundários e chaves estrangeiras, essa diferença de 20 bytes por linha vira gigabytes de índice a mais e mais páginas para caber no cache. Se você precisa da forma textual na API, converta na borda, armazene em binário, exiba em texto.

Perguntas frequentes

UUID v4 pode repetir?
Matematicamente sim, mas a chance é desprezível: com 122 bits aleatórios, seriam necessários cerca de 2,7 × 10^18 UUIDs para uma probabilidade de 50% de uma única colisão. O risco prático só aparece com geradores fracos; use uma fonte criptográfica como a Web Crypto.
O que é o UUID v7 e por que ele é recomendado?
É a versão da RFC 9562 (§5.7) que coloca um timestamp Unix de 48 bits em milissegundos na frente e aleatoriedade atrás. Isso o torna ordenável por tempo, o que dá localidade de índice e melhora inserções em banco, resolvendo a "péssima localidade de índice" do v4 sem sair do formato UUID padrão.
Dá para saber quando um UUID foi criado?
Depende da versão. Um v4 é puro acaso e não guarda tempo nenhum. Já v1, v6, v7 e o ULID trazem o instante de criação nos bits iniciais: extraia o campo de tempo, decodifique para milissegundos e converta para data. É por isso que um id ordenável nunca é anônimo quanto ao horário.
ULID é melhor que UUID?
Não em absoluto, depende do objetivo. ULID vence quando você precisa de ordenação por tempo e uma string curta e URL-safe. UUID v4 vence quando o id não pode revelar o horário de criação. Para chave primária, o UUID v7 traz a ordenação do ULID no formato UUID padrão.
UUID ou Snowflake: qual usar?
Snowflake cabe em 64 bits e é ordenável, ótimo quando o tamanho do id importa e você controla os nós geradores (como Twitter e Discord). UUID v7 e ULID têm 128 bits e não exigem coordenação de nós, qualquer processo gera sem risco de colidir. Se você não precisa espremer o id em 8 bytes, o v7 costuma ser mais simples de operar.
Devo guardar UUID como texto ou binário?
Binário, quando possível. A forma textual usa 36 bytes; a binária, 16. Como a chave primária se repete em índices e chaves estrangeiras, os 20 bytes extras por linha viram muito espaço de índice. Use o tipo uuid nativo (PostgreSQL) ou BINARY(16) (MySQL) e converta para texto só na borda da API.
Quantos caracteres tem cada formato?
Um UUID canônico tem 36 caracteres (32 hexadecimais mais 4 hífens) para 128 bits. Um ULID tem 26 caracteres em Base32 de Crockford para os mesmos 128 bits. O KSUID tem 27, o NanoID 21 por padrão, e um Snowflake ou bigint chega a 19 dígitos.

Todo UUID tem 128 bits; a versão decide o que fica lá dentro, e a RFC 9562 vai da v1 à v8. Use v4 quando o id precisa ser opaco e imprevisível, v7 ou ULID quando precisa ser ordenável e amigo do índice, e v5 quando precisa ser determinístico. A colisão de v4 não é problema com uma fonte criptográfica, o que importa é escolher entre expor o tempo de criação ou não, guardar em binário para poupar índice e tratar ids numéricos grandes como string em JSON.

Fontes e referências

  1. RFC 9562, Universally Unique IDentifiers (UUIDs), maio de 2024 (obsoleta a RFC 4122)
  2. ULID, Especificação oficial (github.com/ulid/spec)
  3. MDN, Crypto.randomUUID() (gera v4 com RNG criptográfico)
  4. Twitter Snowflake, implementação original (github.com/twitter-archive/snowflake)
  5. KSUID, K-Sortable Unique IDs (github.com/segmentio/ksuid)
  6. NanoID, gerador de ids (github.com/ai/nanoid)
  7. PostgreSQL, tipo de dados uuid (16 bytes)