UUID v4 vs UUID v7: qual usar no seu projeto?
UUID v4 usa aleatoriedade pura; UUID v7 embute um timestamp ordenável. A diferença parece pequena, mas muda completamente o desempenho de índices em tabelas grandes.
Estava modelando uma tabela de eventos — alta frequência de INSERT, paginação por data, sem índice de timestamp separado — e precisei decidir entre UUID v4 e v7 para a chave primária. Fui ler a RFC 9562 para não tomar a decisão no chute. O que parecia um detalhe de implementação virou uma questão de arquitetura com impacto mensurável em performance.
Se você ainda não tem clareza sobre o que é um UUID e para que ele serve, o post O que é UUID? cobre os fundamentos antes de entrar nessa comparação.
Estrutura dos bits: onde os dois diferem de verdade
Um UUID tem 128 bits. O que muda entre as versões é como esses bits são usados.
UUID v4 destina 6 bits fixos para versão e variante; os 122 bits restantes são puramente aleatórios. O resultado é um identificador globalmente único e completamente imprevisível — sem nenhuma informação embutida, sem nenhuma ordenação implícita.
UUID v7, definido na RFC 9562 (2024), usa os primeiros 48 bits para armazenar o timestamp Unix em milissegundos. Os 74 bits restantes são aleatórios (mais os 6 bits de versão/variante). A estrutura fica assim:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rand_b |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Na prática, um UUID v7 gerado agora começa com 018fea... — o prefixo muda conforme o tempo passa, mas sempre em ordem crescente. Um UUID v4 gerado no mesmo instante pode começar com qualquer coisa: 7f3c9a, 2b0d41, e8fa12.
Por que sortabilidade importa em bancos de dados
A maioria dos bancos relacionais usa índices B-tree para chaves primárias. Um B-tree funciona melhor quando os dados são inseridos em ordem aproximadamente crescente: a página ativa fica quente no buffer pool, a fragmentação é mínima e o número de page splits é baixo.
Com UUID v4, cada INSERT vai para uma posição aleatória na árvore. Em tabelas pequenas, isso é imperceptível. Em tabelas com dezenas de milhões de linhas, os efeitos são concretos:
- Fragmentação de páginas: o banco precisa dividir páginas com frequência para acomodar as chaves fora de ordem, aumentando o espaço em disco e degradando leituras sequenciais.
- Buffer pool thrashing: como cada INSERT pode atingir uma página diferente, o banco precisa carregar páginas do disco com mais frequência. O working set efetivo cresce sem parar.
- Desempenho de INSERT: em benchmarks com PostgreSQL, a degradação com UUIDs v4 aleatórios começa a aparecer por volta dos 10–50 milhões de linhas, dependendo do hardware e da configuração.
Com UUID v7, os novos IDs são sempre maiores que os anteriores (dentro do mesmo milissegundo, a aleatoriedade ainda existe, mas o prefixo temporal garante ordem aproximada). O comportamento é similar ao de um BIGSERIAL ou IDENTITY, mas com a vantagem de ser globalmente único sem coordenação central.
Como os dois se parecem na prática
Ficional para ilustração, mas a estrutura reflete a realidade:
-- UUID v7: cronologicamente ordenável
018fea1c-27ab-7abc-8def-123456789abc
018fea1c-27ac-7def-9012-abcdef012345
018fea1c-27ad-71a2-b345-678901234567
-- UUID v4: completamente aleatório
550e8400-e29b-41d4-a716-446655440000
f47ac10b-58cc-4372-a567-0e02b2c3d479
6ba7b810-9dad-11d1-80b4-00c04fd430c8
Ordenar a primeira lista por valor retorna as linhas na ordem de criação. Ordenar a segunda retorna uma sequência sem sentido temporal.
Você pode gerar ambos os formatos diretamente no navegador com o Gerador de UUID — útil para testar o comportamento antes de commitar o schema.
Colisão: a entropia ainda é suficiente no v7?
Essa é a pergunta que todo mundo faz quando vê que o v7 "perdeu" 48 bits de aleatoriedade para o timestamp.
Com v4, você tem 122 bits de entropia. A probabilidade de colisão entre dois UUIDs gerados independentemente é aproximadamente 1 em 5,3 × 10³⁶ — um número tão grande que é inútil tentar visualizá-lo.
Com v7, dentro de um único milissegundo, você tem 74 bits de entropia aleatória. Isso dá aproximadamente 1,9 × 10²² combinações possíveis por milissegundo. Para qualquer sistema prático — mesmo os que geram milhões de eventos por segundo — o risco de colisão no mesmo milissegundo é desprezível. A RFC 9562 discute estratégias adicionais para sistemas de altíssima frequência (como usar um contador monotônico nos bits rand_a), mas para a grande maioria dos casos, 74 bits por milissegundo é mais do que suficiente.
Suporte em bancos de dados
O suporte nativo varia bastante dependendo do banco:
PostgreSQL: gen_random_uuid() gera v4 nativamente desde a versão 13. Para v7, a opção mais simples até o PostgreSQL 17 é gerar na aplicação — a extensão pgcrypto não oferece v7 ainda. O PostgreSQL armazena UUIDs no tipo nativo uuid (16 bytes), que é mais eficiente que TEXT.
MySQL / MariaDB: sem suporte nativo para nenhuma versão. A recomendação histórica era usar UUID_TO_BIN(UUID(), 1) (modo swap que reordena os bytes temporais do v1), mas com v7 gerado na aplicação você não precisa desse hack. Para performance, use BINARY(16) ao invés de CHAR(36) — a diferença de espaço e velocidade de comparação é significativa.
SQLite: armazena como TEXT ou BLOB. Para SQLite, a escolha entre v4 e v7 tem menos impacto porque o banco não tem buffer pool complexo nem fragmentação de B-tree da mesma forma que os relacionais pesados.
Sobre armazenamento: se o banco tem tipo nativo uuid, use-o. Se não tem, BINARY(16) é preferível a VARCHAR(36) — você economiza 20 bytes por linha e elimina a comparação de strings.
Suporte em linguagens e bibliotecas
JavaScript/TypeScript: crypto.randomUUID() está disponível nativamente no browser e no Node.js 19+ — mas gera apenas v4. Para v7, a biblioteca uuidv7 (menos de 2 KB minificada) é a opção mais direta:
import { uuidv7 } from 'uuidv7';
const id = uuidv7(); // "018fea1c-27ab-7abc-8def-123456789abc"
Python: a biblioteca padrão uuid suporta v4 nativamente (uuid.uuid4()). Para v7, use uuid-utils ou python-uuid7.
Go: github.com/google/uuid suportou v7 a partir da versão 1.6.
Rust: a crate uuid com feature v7 gera UUIDs v7 desde a versão 1.7.
Por que o v1 entrou em desuso
Antes do v7, quem queria sortabilidade usava UUID v1 — que também embute um timestamp. O problema é que o v1 usa o endereço MAC da máquina como parte do identificador. Isso levanta preocupações de privacidade (é possível rastrear de qual máquina o UUID foi gerado) e cria problemas em ambientes containerizados onde o MAC pode ser genérico ou repetido.
O v7 resolve o mesmo problema de ordenação sem expor nenhuma informação sobre a infraestrutura. É um substituto direto para v1 nos casos de uso onde sortabilidade era o único motivo para escolhê-lo.
Quando usar cada um
Use v4 quando:
- O identificador é um token de sessão, link de reset de senha, ou qualquer coisa onde você ativamente não quer que seja previsível ou ordenável.
- Você está integrando com sistemas legados que esperam UUIDs v4 e a ordenação não é relevante para o caso de uso.
- A tabela é pequena e nunca vai crescer para o ponto onde fragmentação de índice seja um problema real.
Use v7 quando:
- O UUID é uma chave primária de uma tabela que cresce continuamente (logs, eventos, transações, mensagens).
- Você vai paginar por data usando a PK como proxy de tempo.
- Você quer migrar de
BIGSERIAL/IDENTITYpara UUIDs sem perder a ordenação natural que facilita debugging e análise. - Está em um ambiente multi-serviço onde gerar IDs na aplicação (em vez de no banco) simplifica a arquitetura.
A opinião direta
Use v7 para chaves primárias de tabelas que crescem. O argumento de que "v4 é suficiente" ignora o custo real de fragmentação de índice em escala — um custo que aparece gradualmente e que ninguém percebe até a tabela ter 50 milhões de linhas e os INSERTs começarem a demorar o dobro.
v4 tem seu lugar: tokens de segurança, IDs que não serão usados como PK, contextos onde a imprevisibilidade é uma propriedade desejada. Mas como PK de uma tabela de alta escrita, v7 não tem desvantagem prática sobre v4 — só vantagem.
A RFC 9562 foi publicada em 2024 e substitui a RFC 4122. A mudança não foi por modismo; foi porque os padrões de uso real dos UUIDs evoluíram e a sortabilidade se mostrou importante o suficiente para merecer um slot na especificação oficial.
Perguntas frequentes
UUID v7 é mais lento de gerar que v4?
Não de forma perceptível. Ambos dependem de um gerador de números aleatórios criptograficamente seguro (CSPRNG) para a parte aleatória. A diferença é que o v7 também lê o timestamp atual — uma operação de nanosegundos. Em benchmarks, a diferença de geração é inferior a 1% e irrelevante para qualquer aplicação prática.
Posso migrar de v4 para v7 em uma tabela existente?
Não diretamente. As PKs existentes continuam sendo v4 e as novas seriam v7 — o índice ficaria com os dados v4 misturados aos v7, sem ganhar a vantagem de ordenação. A migração prática envolve criar uma nova tabela com v7 como PK e migrar os dados, ou aceitar um período de transição onde o índice fica parcialmente desordenado. Para tabelas novas, a decisão é simples: comece com v7 desde o início.
UUID v7 é um padrão reconhecido ou ainda é experimental?
É um padrão. A RFC 9562 foi publicada pelo IETF em maio de 2024 e é o documento oficial que define as versões de UUID de 1 a 8. O v7 está completamente especificado e estável — não é uma proposta ou draft.
Qual a diferença entre UUID v7 e ULID?
ULID (Universally Unique Lexicographically Sortable Identifier) também usa timestamp + aleatoriedade e foi popular antes do UUID v7 ser padronizado. A diferença principal é o formato: ULID usa base32 (26 caracteres), enquanto UUID v7 usa o formato UUID padrão com hifens (36 caracteres). Com o v7 padronizado na RFC 9562, ULID perdeu boa parte do argumento de existência — UUID v7 resolve o mesmo problema dentro do ecossistema de ferramentas que já suportam UUIDs.
- 01 O que é JSON e como esse formato funciona Entenda o que é JSON, seus seis tipos de dados, sintaxe obrigatória e onde o formato é usado — com exemplos reais em JavaScript, Python e Go.
- 02 Como os LLMs geram respostas: tokens, predição e sampling explicados Tokenização, predição autorregressiva, temperatura e Top-P: a mecânica interna de como modelos de linguagem transformam um prompt em texto.