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.
| Faixa | Bits | Conteúdo |
|---|---|---|
| Timestamp | 48 | Milissegundos desde 1970 |
| Versão | 4 | Constante 0111 |
| Sequência | 12 | Desempate dentro do mesmo milissegundo |
| Variante | 2 | Constante 10 |
| Aleatório | 62 | Entropia 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.
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
| Critério | BIGINT sequencial | UUID v4 | UUID v7 |
|---|---|---|---|
| Tamanho | 8 bytes | 16 bytes | 16 bytes |
| Localidade de escrita | Excelente | Ruim | Excelente |
| Geração pelo cliente | Não | Sim | Sim |
| Revela volume ao expor | Sim | Não | Não |
| Revela instante de criação | Não diretamente | Não | Sim |
| Ordenação por criação | Sim | Não | Aproximada |
| Fusão de bases distintas | Conflita | Segura | Segura |
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.
- 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.
- 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.
- 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.