UUID v4 vs UUID v7: Por Que o Novo Padrão Revolucionou Bancos de Dados
Por quase duas décadas, engenheiros de software enfrentaram um clássico dilema ao modelar chaves primárias (PKs) em bancos de dados relacionais como PostgreSQL, MySQL e MariaDB: usar inteiros sequenciais auto-incrementais (BIGINT) ou identificadores universais aleatórios (UUID v4)?
Apesar de o UUID v4 oferecer vantagens inestimáveis para sistemas distribuídos e microsserviços — como geração sem colisão descentralizada e impossibilidade de enumeração de dados por atacantes via URL —, sua total aleatoriedade cobra um preço catastrófico em escala: a fragmentação descontrolada de páginas em árvores B-Tree.
Em maio de 2024, a IETF oficializou a RFC 9562 (que substituiu a histórica RFC 4122), padronizando novas versões de UUIDs. Entre elas, o UUID v7 tornou-se a escolha definitiva para arquiteturas modernas de alta vazão.
1. O Problema Oculto do UUID v4: Fragmentação de Índices B-Tree
Bancos de dados relacionais organizam suas chaves primárias e índices em estruturas de árvore balanceada chamadas B-Trees. Quando você insere dados sequenciais (como um ID 1, 2, 3...), o banco de dados simplesmente anexa novos registros no final da última página de disco da árvore, mantendo as páginas anteriores intocadas e o cache da memória RAM altamente eficiente.
Entretanto, como o UUID v4 possui 122 bits de pura aleatoriedade criptográfica, cada nova inserção atinge uma posição completamente arbitrária da árvore:
Os 3 Sintomas da Degradação por UUID v4 em Tabelas com Milhões de Linhas:
- Page Splits (Divisões de Página): Quando uma página de 8 KB está cheia e recebe um novo registro no meio dela, o banco precisa dividi-la em duas páginas semicheias, reescrevendo blocos no disco.
- Buffer Cache Thrashing: Como as inserções são em posições aleatórias, o banco precisa carregar constantemente páginas antigas do disco para a memória RAM, expulsando dados úteis de cache.
- Bloat de Índice: Índices em UUID v4 costumam ocupar de 30% a 50% mais espaço em disco do que o necessário devido às páginas deixadas parcialmente vazias após divisões.
2. A Anatomia do UUID v7 (RFC 9562)
O UUID v7 resolve esse gargalo combinando o melhor dos dois mundos: ordenação temporal monotônica na raiz com alta entropia aleatória nas pontas. Ele continua tendo exatamente os mesmos 128 bits (16 bytes) e a mesma representação textual de 36 caracteres do padrão UUID:
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 | (32 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unix_ts_ms | ver | rand_a | (16 + 4 + 12 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var| rand_b | (2 + 62 bits)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
A estrutura divide-se em três seções críticas:
- unix_ts_ms (48 bits): Armazena o timestamp Unix atual em milissegundos. Permite ordenação cronológica precisa até aproximadamente o ano 10.889.
- version (4 bits): Valor fixo
0111em binário (versão 7). - rand_a + rand_b (74 bits de entropia): Garante que centenas de milhares de UUIDs gerados no exato mesmo milissegundo nunca colidam entre si.
3. Comparativo de Performance: UUID v4 vs UUID v7
| Característica | UUID v4 | UUID v7 |
|---|---|---|
| Ordenação Natural | Não (100% caótico) | Sim (Ordenado por timestamp) |
| Desempenho em B-Tree | Queda de até 75% em 50M+ linhas | Quase idêntico a BIGINT sequencial |
| Fragmentação de Páginas | Alta (constantes page splits) | Mínima (append nas folhas finais) |
| Privacidade de Enumeração | Total (impossível adivinhar IDs) | Total (74 bits de entropia aleatória) |
| Vazamento de Timestamp | Nenhum | Expõe o milissegundo de criação |
4. Implementação Prática: Como Gerar UUID v7
Em JavaScript (Node.js nativo com Web Crypto API)
function gerarUUIDv7() {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
const timestamp = Date.now();
// Escreve os 48 bits de timestamp Unix (Big Endian)
bytes[0] = (timestamp / 0x10000000000) & 0xff;
bytes[1] = (timestamp / 0x100000000) & 0xff;
bytes[2] = (timestamp / 0x1000000) & 0xff;
bytes[3] = (timestamp / 0x10000) & 0xff;
bytes[4] = (timestamp / 0x100) & 0xff;
bytes[5] = timestamp & 0xff;
// Ajusta a versão (0111 = 7) nos 4 bits superiores do byte 6
bytes[6] = (bytes[6] & 0x0f) | 0x70;
// Ajusta a variante RFC 4122/9562 (10xx) nos 2 bits superiores do byte 8
bytes[8] = (bytes[8] & 0x3f) | 0x80;
// Converte para a string hexadecimal formatada com hífens
return [...bytes].map((b, i) => {
const hex = b.toString(16).padStart(2, '0');
return (i === 4 || i === 6 || i === 8 || i === 10) ? '-' + hex : hex;
}).join('');
}
console.log(gerarUUIDv7());
// Exemplo: 018e6973-7721-72f1-9cb6-17b5e4070a84
Em Python 3
import time
import os
import uuid
def gerar_uuid_v7() -> uuid.UUID:
unix_ts_ms = int(time.time() * 1000)
rand_bytes = bytearray(os.urandom(10))
# Converte timestamp em 6 bytes
ts_bytes = unix_ts_ms.to_bytes(6, byteorder='big')
raw = bytearray(ts_bytes + rand_bytes)
# Configura versão 7
raw[6] = (raw[6] & 0x0F) | 0x70
# Configura variante RFC 9562 (10xx xxxx)
raw[8] = (raw[8] & 0x3F) | 0x80
return uuid.UUID(bytes=bytes(raw))
print(gerar_uuid_v7())
Perguntas Frequentes (FAQ)
UUID do PostgreSQL armazena 128 bits e funciona perfeitamente com UUID v7. Na versão 17, foram introduzidas discussões avançadas de funções de geração embutidas, mas até lá qualquer biblioteca de backend (como TypeORM, Prisma, SQLAlchemy) pode gerar o UUID v7 no momento da inserção.Editorial Técnico Caixa do Dev
Artigos originais e guias técnicos produzidos com rigor matemático, código aberto e conformidade com especificações da IETF (RFCs), W3C e ISO/IEC para desenvolvedores.