Ir para o conteúdo
Toova
Todas as Ferramentas

UUID v4 vs v7 — Diferenças e Quando Usar Cada Um

Toova

O UUID v4 tem sido a escolha padrão para chaves primárias distribuídas por mais de uma década. Gere um valor aleatório de 128 bits, formate-o como oito grupos hex, pronto. Sem coordenação necessária, probabilidade de colisão praticamente zero, funciona em todo lugar. Então por que o UUID v7 — padronizado na RFC 9562 em 2024 — está se espalhando rapidamente em sistemas de produção em 2026?

A resposta curta: desempenho de banco de dados. O UUID v4 destrói a localidade do índice B-tree. O UUID v7 corrige isso mantendo tudo que os devs amam nos UUIDs. Este artigo explica o que cada versão realmente é, por que a diferença importa em escala, quando escolher cada um e como migrar sem downtime.

Precisa gerar UUIDs agora mesmo? Experimente o gerador UUID do Toova, que suporta tanto v4 quanto v7 em lote. Para outros identificadores aleatórios, o gerador de strings aleatórias e o gerador de senhas cobrem tokens mais curtos.

UUID v4 — Aleatoriedade Pura

O UUID versão 4 usa um gerador de números pseudoaleatórios criptograficamente seguro (CSPRNG) para preencher 122 dos 128 bits. Os seis bits restantes são fixos: quatro bits codificam a versão (0100) e dois bits codificam a variante (10). O resultado parece assim:

f47ac10b-58cc-4372-a567-0e02b2c3d479
              ^^^^
              versão = 4 (aleatório)

A aleatoriedade é a funcionalidade. Dois sistemas independentes podem gerar UUIDs sem coordenação e nunca colidir — a probabilidade de colisão em um conjunto de 2,71 quatrilhões de UUIDs v4 é de aproximadamente 50%, o que significa que para qualquer aplicação prática o risco é negligenciável. Você não precisa de um servidor de ID centralizado, uma sequência de banco de dados ou um lock distribuído.

Como o v4 é gerado

import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// => 'f47ac10b-58cc-4372-a567-0e02b2c3d479'

A biblioteca uuid (JavaScript), o uuid.uuid4() do Python, o google/uuid do Go e todos os principais runtimes de linguagem delegam para o CSPRNG do SO — /dev/urandom no Linux, CryptGenRandom no Windows. O custo de geração é efetivamente zero.

O problema: inserção aleatória destrói os índices B-tree

Um índice B-tree mantém os dados ordenados. Quando você insere uma nova linha, o banco de dados encontra onde a nova chave se encaixa na ordem classificada e a coloca lá. Se cada nova chave é aleatória, ela cai em uma posição aleatória no índice — o que significa que cada inserção precisa carregar uma página diferente do disco para o pool de buffer. Em baixo volume isso é invisível. Em alto volume (milhões de linhas, alta taxa de INSERT), cria um padrão chamado fragmentação de índice: o índice fica cheio de páginas meio-vazias porque cada inserção atinge um local diferente, e o pool de buffer muda constantemente porque o conjunto de trabalho ativo abrange o índice inteiro em vez de uma fatia recente previsível.

Os sintomas em produção: a latência de INSERT aumenta, a sobrecarga do autovacuum sobe (PostgreSQL), a pressão de checkpoint cresce e o desempenho de leitura para registros recentes degrada porque "recente" não mapeia mais para nenhuma localidade no índice. Isso não é hipotético — é um ponto de dor bem documentado em grandes tabelas PostgreSQL e MySQL com chaves primárias UUID v4.

UUID v7 — Aleatoriedade Ordenada por Tempo

O UUID v7 foi projetado explicitamente para resolver o problema de fragmentação B-tree. Codifica um timestamp Unix de 48 bits em milissegundos nos bits mais significativos, seguido por bits de versão, 12 bits aleatórios, bits de variante e mais 62 bits aleatórios. Aleatoriedade total: 74 bits — ainda muito mais do que o necessário para prevenir colisões.

018f4e6b-a23c-7d45-9abc-0e02b2c3d479
 ^^^^^^^^^^^^^^
 timestamp Unix de 48 bits (precisão em ms)
                   ^
                   versão = 7

Como o timestamp ocupa os bits altos, os UUIDs gerados mais tarde são classificados após os UUIDs gerados mais cedo. As inserções são sempre anexadas à borda direita do índice. O banco de dados só precisa manter a página de índice mais recente quente no pool de buffer, não o índice inteiro. Em altas taxas de INSERT, isso sozinho pode reduzir a latência de escrita em 30–60% e reduzir o I/O em uma ordem de magnitude em tabelas com dezenas de milhões de linhas.

Como o v7 é gerado

import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// => '018f4e6b-a23c-7d45-9abc-0e02b2c3d479'

A mesma biblioteca uuid adicionou suporte ao v7 na versão 10. A biblioteca padrão do Python o adicionou no 3.14. O PostgreSQL 17 inclui uuidv7() como função nativa. Se você está em uma stack mais antiga, várias bibliotecas pequenas fornecem geração v7 sem dependências.

Monotonicidade sub-milissegundo

O que acontece quando dois UUIDs v7 são gerados dentro do mesmo milissegundo? A RFC 9562 permite que as implementações incrementem um contador monotônico nos bits aleatórios para garantir a ordenação dentro do mesmo milissegundo. A biblioteca uuid faz isso por padrão. O resultado: mesmo que 10.000 IDs sejam gerados em um milissegundo, eles ainda são classificados corretamente.

O Problema de Fragmentação do Índice B-Tree em Detalhes

Para entender por que isso importa, considere o que acontece a 10.000 inserções por segundo com uma chave primária UUID v4 em uma tabela com 100 milhões de linhas existentes:

  • Cada inserção gera uma chave de 128 bits aleatória que cai em uma posição aleatória entre as 100 milhões de entradas existentes.
  • O banco de dados deve carregar a página B-tree específica contendo aquela posição para o pool de buffer.
  • Com 100 milhões de linhas e páginas de 8 KB, o índice abrange aproximadamente 100.000 páginas. A cada segundo, 10.000 páginas diferentes são necessárias — muito mais do que um typical shared_buffers de 8 GB pode manter apenas para esse índice.
  • Cada falta de cache resulta em uma leitura de disco. A 10.000 inserções/seg, isso pode gerar milhares de leituras aleatórias de disco por segundo, saturando até mesmo SSDs em tabelas grandes.

Com UUID v7, todas as 10.000 inserções por segundo caem na página folha mais à direita (ou um pequeno número de páginas recentes). O pool de buffer só precisa manter essas poucas páginas quentes. A taxa de acerto de cache para gravações se aproxima de 100%. A amplificação de escrita cai dramaticamente.

O mesmo benefício se aplica a varreduras de intervalo: WHERE created_at BETWEEN x AND y em uma tabela v4 requer uma varredura completa do índice ou um índice de timestamp separado. Em uma tabela v7, a própria chave primária é o índice de timestamp — a query pode buscar diretamente no intervalo certo.

Como Usar UUID v7 no PostgreSQL

-- Funciona nativamente com o tipo UUID no PostgreSQL 16+
CREATE TABLE events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4 (aleatório)
  -- Mude para UUIDv7 via extensão ou geração no lado da aplicação
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Com UUIDv7: o timestamp já está embutido, então
-- essa coluna created_at separada frequentemente é redundante.

O PostgreSQL 17 vem com uuidv7() nativamente. Para o PostgreSQL 14–16, a extensão pg_uuidv7 fornece a mesma função. O tipo de dados UUID armazena v4 e v7 de forma idêntica — 16 bytes, sem sobrecarga. A única diferença é o padrão de bits que determina a ordem de classificação.

Uma consequência útil: como o timestamp está embutido, muitas tabelas não precisam mais de uma coluna created_at separada para ordenação ou exibição. Você pode extrair o timestamp de um UUID v7 com uma única chamada de função. Isso reduz a complexidade do schema e elimina uma escrita por inserção.

UUID v4 vs v7 — Trade-Offs

Propriedade UUID v4 UUID v7
Bits de aleatoriedade 122 74
Ordem de classificação Aleatória Cronológica
Desempenho de INSERT B-tree Ruim (fragmentação) Excelente (sequencial)
Vazamento de timestamp Nenhum Sim (precisão de ms)
Padrão RFC RFC 4122 (2005), RFC 9562 (2024) RFC 9562 (2024)
Suporte de biblioteca Universal Crescendo rapidamente (2024–2026)
created_at embutido Não Sim

Guia de Migração — v4 para v7

Migrar uma tabela existente de chaves primárias UUID v4 para v7 é uma operação de múltiplas etapas. A restrição principal: você não pode alterar o valor de uma chave primária que é referenciada por chaves estrangeiras sem atualizar todas as tabelas referenciadas também. Planeje uma janela de manutenção ou use uma abordagem de escrita dupla.

-- 1. Adicionar nova coluna
ALTER TABLE orders ADD COLUMN id_v7 UUID;

-- 2. Preencher as linhas existentes (usar o created_at existente como
--    fonte do timestamp; usar função de lib UUIDv7 na sua aplicação)
UPDATE orders SET id_v7 = generate_uuidv7(created_at);

-- 3. Verificar se não há NULLs restantes
SELECT COUNT(*) FROM orders WHERE id_v7 IS NULL;

-- 4. Trocar colunas (requer breve janela de manutenção)
ALTER TABLE orders ALTER COLUMN id_v7 SET NOT NULL;
ALTER TABLE orders ALTER COLUMN id_v7 SET DEFAULT generate_uuidv7(now());
ALTER TABLE orders RENAME COLUMN id TO id_v4_old;
ALTER TABLE orders RENAME COLUMN id_v7 TO id;
ALTER TABLE orders ADD PRIMARY KEY (id);

Para migrações sem downtime em tabelas de alto tráfego, a abordagem recomendada é:

  1. Adicione a nova coluna id_v7 com um padrão de servidor que gera UUIDs v7 daqui para frente.
  2. Preencha linhas antigas em lotes durante períodos de baixo tráfego, usando o timestamp created_at existente como semente para a porção de timestamp v7.
  3. Atualize todas as colunas de chave estrangeira nas tabelas referenciadas para apontar para id_v7.
  4. Renomeie colunas e remova a antiga constraint de chave primária durante uma breve janela de manutenção.

A etapa de preenchimento é a mais longa. Com 10.000 linhas por lote e um sleep de 50 ms entre os lotes, uma tabela de 100 milhões de linhas leva aproximadamente 8 horas. Comece cedo.

Impacto no Mundo Real

Várias equipes de engenharia publicaram benchmarks comparando UUID v4 e v7 em conjuntos de dados de tamanho de produção. As descobertas consistentes:

  • Taxa de transferência de INSERT: melhoria de 2–5x em tabelas com 50M+ linhas ao mudar de v4 para v7, com o ganho aumentando conforme o tamanho da tabela cresce.
  • Latência de escrita p99: cai de centenas de milissegundos (v4, sob carga) para dígitos únicos de milissegundos (v7) no mesmo hardware.
  • Tamanho do índice: os índices B-tree em colunas v7 são 15–30% menores que índices equivalentes v4 nos mesmos dados porque a fragmentação deixa menos páginas meio-vazias.
  • Eficiência do pool de buffer: a taxa de acerto de shared buffers para o índice de chave primária vai de ~40% (v4, tabela grande) para ~99% (v7), porque apenas as páginas recentes precisam ficar quentes.

Os ganhos são negligenciáveis abaixo de aproximadamente 1 milhão de linhas. Se sua tabela permanece pequena, fique com v4 pela simplicidade. Acima de 10 milhões de linhas com qualquer taxa de INSERT significativa, o v7 é o melhor padrão.

Quando Usar Cada Um

Use UUID v7 quando:

  • Você está projetando um novo schema e a tabela crescerá bastante (10M+ linhas).
  • A tabela tem uma alta taxa de INSERT — eventos, logs, pedidos, mensagens, notificações.
  • Você quer uma chave primária que também serve como timestamp de criação (elimina uma coluna).
  • Você está no PostgreSQL 17, MySQL 8.0+ ou um ORM moderno que suporta geração v7.
  • Ordenar por ID é semanticamente equivalente a ordenar por tempo de criação — o que será para a maioria das tabelas pesadas em inserção.

Use UUID v4 quando:

  • O identificador é exposto a usuários e não deve revelar o tempo de criação (códigos de convite, links de compartilhamento, handles de faturamento).
  • A tabela é pequena e estável — nenhum benefício de desempenho justifica o custo de migração.
  • Você está gerando IDs em um contexto onde o timestamp de criação é sensível (registros privados de usuário em um produto que compete em métricas de crescimento).
  • Você precisa de algo como credencial ou token de uso único — use um gerador de segredos dedicado, não qualquer versão de UUID.

Resumo

O UUID v4 é aleatório, privado e universalmente suportado. O UUID v7 é ordenado por tempo, amigável ao banco de dados e agora padrão nas principais bibliotecas e bancos de dados. Para novos schemas com tabelas grandes ou de crescimento rápido, o v7 é o melhor padrão. Para IDs expostos a usuários onde o vazamento de timestamp é uma preocupação, o v4 continua sendo a escolha certa.

Gere ambas as versões instantaneamente com o gerador UUID do Toova — sem conta necessária. Para tokens mais curtos, o gerador de strings aleatórias produz IDs alfanuméricos em qualquer comprimento.