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.
- 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.
- 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.
- 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.
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.
| Versão | Conteúdo | Seção | Ordenável? |
|---|---|---|---|
| v1 | tempo (desde 1582) + MAC | §5.1 | Parcial |
| v2 | DCE Security (raro) | §5.2 | Não |
| v3 | hash MD5 de namespace + nome | §5.3 | Não |
| v4 | 122 bits aleatórios | §5.4 | Não |
| v5 | hash SHA-1 de namespace + nome | §5.5 | Não |
| v6 | v1 com o tempo reordenado | §5.6 | Sim |
| v7 | timestamp Unix 48 bits + aleatório | §5.7 | Sim |
| v8 | livre / experimental (você define os bits) | §5.8 | Depende |
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 / randomnessHá 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
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.
- 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).
- 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.
- 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.
- 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)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.
| Formato | Bits | Ordenável | Opaco | Texto | Uso típico |
|---|---|---|---|---|---|
| UUID v4 | 128 (122 aleat.) | Não | Sim | 36 | id genérico, token público |
| UUID v7 | 128 | Sim | Não | 36 | chave primária ordenável |
| ULID | 128 | Sim | Não | 26 | chave/URL ordenável e curta |
| Snowflake | 64 | Sim | Parcial | até 19 dígitos | id distribuído (Twitter, Discord) |
| KSUID | 160 | Sim | Não | 27 | id ordenável com mais entropia |
| NanoID | ~126 (padrão) | Não | Sim | 21 | id curto em URL |
| bigint / bigserial | 64 | Sim (sequencial) | Não | até 19 dígitos | PK sequencial local |
Ver os dados
| Categoria | Valor |
|---|---|
| bigint | 8 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?
O que é o UUID v7 e por que ele é recomendado?
Dá para saber quando um UUID foi criado?
ULID é melhor que UUID?
UUID ou Snowflake: qual usar?
Devo guardar UUID como texto ou binário?
Quantos caracteres tem cada formato?
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
- RFC 9562, Universally Unique IDentifiers (UUIDs), maio de 2024 (obsoleta a RFC 4122)
- ULID, Especificação oficial (github.com/ulid/spec)
- MDN, Crypto.randomUUID() (gera v4 com RNG criptográfico)
- Twitter Snowflake, implementação original (github.com/twitter-archive/snowflake)
- KSUID, K-Sortable Unique IDs (github.com/segmentio/ksuid)
- NanoID, gerador de ids (github.com/ai/nanoid)
- PostgreSQL, tipo de dados uuid (16 bytes)