Banco de dados9 min de leitura

UUID v4 ou v7 como chave primária: o que muda no índice do banco

Por que UUID v4 fragmenta índices B-tree, como o v7 resolve isso com prefixo temporal, e quando um inteiro sequencial ainda é a escolha certa. Com números, trade-offs e código.

Publicado em

AL

Por André Leitão

Desenvolvedor de software

A escolha da chave primária é uma daquelas decisões que parecem triviais na primeira semana e caras no terceiro ano. Inteiro sequencial é rápido e revela volume; UUID v4 é seguro de expor e degrada o índice; UUID v7 promete resolver o segundo problema sem reintroduzir o primeiro.

Vale entender por que essa diferença existe, porque ela não está no tamanho do identificador — está na forma como o banco organiza as páginas do índice.

Por que a aleatoriedade custa caro

Um índice B-tree mantém as chaves ordenadas em páginas de tamanho fixo. Quando as chaves chegam em ordem crescente, toda inserção vai para a página mais à direita: essa página fica no cache, enche, é dividida uma vez e a próxima assume o lugar. O padrão de escrita é praticamente sequencial.

Com chaves aleatórias, cada inserção cai numa página diferente e imprevisível. Três consequências se acumulam.

  • A página de destino provavelmente não está em memória e precisa ser lida do disco antes de ser escrita.
  • Páginas parcialmente cheias se dividem por toda a árvore, e não só na borda, o que aumenta o número de páginas e a altura efetiva do índice.
  • O conjunto de páginas tocadas por unidade de tempo cresce, então o cache do banco passa a segurar uma fração cada vez menor do índice.

O que o UUID v7 faz de diferente

O UUID v7 reorganiza o conteúdo dos mesmos 128 bits. Os 48 bits iniciais guardam o número de milissegundos desde a época Unix; em seguida vêm a versão, alguns bits opcionais de sequência para desempatar dentro do mesmo milissegundo, e o restante é aleatório.

Distribuição dos bits no UUID v7
FaixaBitsConteúdo
Timestamp48Milissegundos desde 1970
Versão4Constante 0111
Sequência12Desempate dentro do mesmo milissegundo
Variante2Constante 10
Aleatório62Entropia para unicidade

Como os bits mais significativos são temporais e a ordenação de um UUID em formato binário é lexicográfica sobre os bytes, dois valores gerados com um segundo de diferença ficam adjacentes na árvore. O índice volta a crescer pela direita, e o padrão de escrita se aproxima do de um inteiro sequencial.

Gerando UUID v7 sem dependência externa
export function uuidV7() {
  const agora = Date.now();
  const bytes = new Uint8Array(16);

  // 48 bits de timestamp, do byte mais significativo para o menos.
  for (let i = 5; i >= 0; i--) {
    bytes[i] = agora / Math.pow(2, (5 - i) * 8) & 0xff;
  }

  crypto.getRandomValues(bytes.subarray(6));

  // Versão 7 nos quatro bits altos do byte 6.
  bytes[6] = (bytes[6] & 0x0f) | 0x70;
  // Variante RFC nos dois bits altos do byte 8.
  bytes[8] = (bytes[8] & 0x3f) | 0x80;

  const hex = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
  return [
    hex.slice(0, 8),
    hex.slice(8, 12),
    hex.slice(12, 16),
    hex.slice(16, 20),
    hex.slice(20)
  ].join("-");
}

Comparando as três opções

Chave primária: inteiro sequencial, UUID v4 e UUID v7
CritérioBIGINT sequencialUUID v4UUID v7
Tamanho8 bytes16 bytes16 bytes
Localidade de escritaExcelenteRuimExcelente
Geração pelo clienteNãoSimSim
Revela volume ao exporSimNãoNão
Revela instante de criaçãoNão diretamenteNãoSim
Ordenação por criaçãoSimNãoAproximada
Fusão de bases distintasConflitaSeguraSegura

A linha mais importante para quem já usa UUID é a de localidade de escrita: ela é a única em que v4 e v7 divergem de forma relevante, e é a que decide o comportamento do sistema sob carga.

Detalhes de armazenamento que fazem diferença

Duas decisões de esquema anulam boa parte do ganho e passam despercebidas em revisão.

Guardar UUID como texto

Uma coluna VARCHAR(36) ocupa mais que o dobro do tipo nativo de 16 bytes, torna a comparação uma operação de string e infla cada entrada do índice. Em PostgreSQL, use o tipo uuid; em MySQL, BINARY(16). O formato com hífens é representação de saída, não de armazenamento.

Índice sobre uma coluna que já é chave

A chave primária já cria um índice. Criar outro sobre a mesma coluna dobra o custo de escrita sem benefício de leitura — um erro comum quando a migração é gerada por ferramenta e ninguém revisa o resultado.

Como escolher

A decisão cabe em três perguntas, respondidas nesta ordem.

  1. 1O identificador precisa ser gerado fora do banco, ou precisa ser exposto publicamente sem revelar volume? Se não, o inteiro sequencial resolve com o menor custo.
  2. 2O instante de criação do registro é informação sensível ou de valor competitivo? Se sim, use v4 e aceite o custo no índice, mitigando com particionamento se necessário.
  3. 3Nos demais casos, use v7. Ele mantém as propriedades que levaram você a escolher UUID e devolve a localidade de escrita que o v4 tirou.

Para quem já está em v4, a migração é indolor: os dois formatos ocupam a mesma coluna e convivem sem conversão. Basta trocar o gerador para as linhas novas.

Gerando UUID para testes

Durante o desenvolvimento é comum precisar de um punhado de identificadores para fixtures, exemplos de documentação ou mocks de API. O gerador de UUID do Codigio Labs produz v4 e v7 no navegador, o que também serve para observar na prática a diferença entre os dois: gere alguns v7 seguidos e repare que os primeiros caracteres se repetem, enquanto os v4 não têm nenhum padrão.

Resumo

  • A aleatoriedade do v4 espalha as inserções pelo índice e degrada quando o índice deixa de caber em memória.
  • O v7 põe um timestamp nos bits mais significativos e restaura a localidade de escrita.
  • O v7 revela o instante de criação; é o único trade-off real em relação ao v4.
  • Guarde UUID no tipo nativo, nunca como VARCHAR(36).
  • Inteiro sequencial continua sendo a melhor escolha quando o identificador não precisa sair do banco.

Perguntas frequentes

UUID v4 realmente é lento como chave primária?
O problema não é o UUID em si, é a aleatoriedade. Cada inserção cai numa página aleatória do índice B-tree, o que espalha as escritas, aumenta a divisão de páginas e derruba o aproveitamento do cache. Em tabelas pequenas isso é irrelevante; a partir de alguns milhões de linhas, aparece.
O que o UUID v7 muda exatamente?
Ele coloca um timestamp de milissegundos nos 48 bits mais significativos. Como a ordenação de um UUID é lexicográfica sobre esses bytes, valores gerados em sequência ficam próximos — as inserções voltam a acontecer na borda direita do índice, como acontece com um inteiro sequencial.
O v7 vaza informação por conter o horário?
Vaza o instante da criação do registro com precisão de milissegundos. Na maioria dos casos isso é irrelevante ou já é público. Quando o instante de criação for sensível, ou quando o identificador for exposto publicamente e a taxa de criação for um dado de negócio, prefira v4.
Posso migrar de v4 para v7 sem parar o sistema?
Sim. Ambos são UUID de 128 bits e cabem na mesma coluna. Basta mudar o gerador para as novas linhas; as antigas continuam funcionando. O ganho aparece progressivamente, à medida que as inserções novas passam a se concentrar no fim do índice.
Quando o inteiro sequencial ainda ganha?
Quando o identificador não sai do banco, não precisa ser gerado pelo cliente e não vai ser fundido com dados de outra instância. Ele ocupa metade do espaço, é mais rápido de comparar e produz o índice mais compacto possível.

Ferramentas relacionadas

AL

Sobre o autor

Desenvolvedor de software no Brasil. Mantém o Codigio Labs desde a primeira ferramenta, escreve os artigos do site e revisa os textos quando a informação técnica muda.

Encontrou um erro ou tem uma correção a sugerir? Fale com o Codigio Labs. Correções relevantes são aplicadas com atualização da data de revisão do texto.

Continue lendo