UUID v7 The New Default.

A evolução inevitável das chaves primárias.

Mateus Bosquetti
Mateus Bosquetti
5 minutos de leitura

Geralmente, quando vamos criar ou planejar uma tabela no banco de dados, começamos pelo ID. Parece uma escolha simples, mas na verdade uma decisão equivocada sobre a chave primária pode trazer várias dores de cabeça ao longo do tempo.

O Início Sequencial

O tipo mais tradicional e amplamente conhecido é o AUTO_INCREMENT (ou BIGINT / BIGSERIAL). Ele foi ensinado por décadas em faculdades e cursos por ser extremamente intuitivo e funcionar de forma nativa.

Suas vantagens técnicas são indiscutíveis para bases relacionais tradicionais. Ele ocupa apenas 8 bytes, garantindo alta performance em disco e árvores B-Tree do MySQL e PostgreSQL. As consultas e junções (JOIN) são também muito rápidas, pois o processador executa comparações entre números inteiros nativamente na memória com eficiência máxima de cache.

Agora imagina você acessar o seu pedido em um e-commerce e ver a seguinte URL:

minhaloja.com/pedidos/1004

Ao expor essa chave sequencial, qualquer pessoa descobre na hora a quantidade exata de transações da empresa, permitindo inclusive que bots raspem todo o banco iterando a sequência.

Outro problema crítico é o acoplamento distribuído, onde microsserviços ou sistemas offline não conseguem gerar IDs sem consultar a base central primeiro, tornando a gravação no banco um gargalo obrigatório.

A Era Distribuída

Conforme as aplicações evoluíram de monólitos para arquiteturas distribuídas e microsserviços, o AUTO_INCREMENT virou um gargalo grande. Para resolver o acoplamento centralizado e o vazamento de métricas nas URLs, a indústria migrou em massa para o UUID v4.

A grande vantagem foi permitir a geração descentralizada, onde qualquer microsserviço cria chaves universais sem consultar a base central. Por ser praticamente 100% aleatório e baseado em entropia criptográfica, ele garante sigilo absoluto, tornando impossível adivinhar a próxima chave ou extrair qualquer metadado do ID.

-
-
-
-
Aleatoriedade Pura (122-bit) v4 (Fixo '4') Variante (8, 9, a, b)

O problema é que o UUID v4 é muito ruim em questões de performance para bancos de dados relacionais.

Por ser totalmente aleatório, inserir um UUID v4 destrói o índice B-Tree. O banco tenta gravar registros em páginas de disco randômicas, provocando constantes Page Splits (divisões físicas de páginas), o que gera alta fragmentação e I/O de disco excessivo.

Veja na simulação abaixo como a aleatoriedade do UUID v4 fragmenta a estrutura do banco a cada inserção.

B-Tree Index Simulator

Simulação interativa de alocação de páginas de disco e page splits em índices B-Tree.

Páginas de Disco
0
Page Splits
0
Fragmentação de Disco
0%
Nenhum dado gravado. Clique em "Inserir Lote" para simular escritas.

SQL CONSOLE

READONLY

UUID v7

Após algumas pesquisas atrás da chave perfeita, o UUID v7 foi o que mais me chamou atenção. Ele entrega o melhor dos dois mundos ao combinar a unicidade universal do UUID v4 com a ordenação por tempo (ordered by time) utilizando timestamp.

Ele codifica a data e hora atual em seus primeiros 48 bits, seguidos por bits de versão e variante, e 74 bits de entropia aleatória. Na prática, IDs gerados cronologicamente mantêm-se sempre em ordem sequencial lexicográfica.

-
-
-
-
Timestamp (48-bit) v7 (Fixo '7') Sub-ms Counter (12-bit) Variante Entropia (62-bit)

Essa ordenação temporal resolve o problema de performance no banco de dados. Como os primeiros bits progridem linearmente no tempo, cada nova chave inserida é naturalmente maior que a anterior. O banco grava novos registros no final do índice B-Tree de forma ordenada e contínua, eliminando as divisões de páginas caóticas.

Para visualizar esse ganho na prática, você pode comparar a escrita fluida e sequencial do UUID v7 contra a fragmentação caótica do UUID v4 diretamente no simulador de índice B-Tree abaixo.

B-Tree Index Simulator

Simulação interativa de alocação de páginas de disco e page splits em índices B-Tree.

Páginas de Disco
0
Page Splits
0
Fragmentação de Disco
0%
Nenhum dado gravado. Clique em "Inserir Lote" para simular escritas.

SQL CONSOLE

READONLY

Veja abaixo a declaração de tabelas utilizando UUID v7 no PostgreSQL 18+ (suporte nativo) e no MySQL (InnoDB com tipo BINARY(16)):

SQL SCRIPT

readonly
-- PostgreSQL 18+ (Suporte nativo)
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuidv7()
);

-- MySQL (InnoDB)
CREATE TABLE users (
id BINARY(16) PRIMARY KEY
);

No entanto, nenhuma escolha de arquitetura vem sem contrapartidas. O UUID v7 ocupa 16 bytes, o dobro de um BIGINT (8 bytes). Em tabelas com bilhões de linhas no MySQL (InnoDB), esse tamanho propaga-se por todas as chaves estrangeiras e índices secundários, exigindo mais memória RAM para manter o cache aquecido.

Existe também a questão da privacidade, já que os primeiros 48 bits do UUID v7 registram o timestamp em milissegundos e qualquer chave exposta em uma URL pública carrega o momento de sua criação. Passe o cursor sobre o trecho destacado na URL abaixo para ver a data decodificada.

socialnetwork.com/profiles/
0190b4d2-f125
-7b5a-93a8-429be9752f40

Na prática, qualquer usuário ou bot consegue decodificar esse trecho inicial e descobrir o instante exato com precisão de milissegundos em que o registro foi criado no sistema.

Outras Abordagens

Além dos UUIDs tradicionais, existem duas alternativas derivadas criadas para resolver cenários bem específicos de alta performance e interoperabilidade, o ULID e o Snowflake ID.

O ULID (Universally Unique Lexicographically Sortable Identifier) foi concebido para ser uma alternativa visualmente mais amigável ao UUID. Composto por 128 bits, ele é codificado em 26 caracteres em Base32 (Crockford) (ex: 01ARZ3NDEKTSV4RRFFQ69G5FAV). Ele remove caracteres ambíguos (como I, L, O, 0), tornando-o perfeito para rotas de APIs REST. No entanto, se a base de dados não tiver suporte nativo ao tipo ULID, ele acaba sendo salvo como texto puro (VARCHAR), perdendo eficiência espacial.

Timestamp Base32 (10 chars / 48-bit) Entropia Base32 (16 chars / 80-bit)

Por outro lado, os Snowflake IDs (arquitetados originalmente pelo Twitter) combinam a compacidade de um número inteiro de apenas 8 bytes (64 bits) com a capacidade distribuída. Eles codificam o timestamp, a identificação da máquina (Worker ID) e um contador local, suportando taxas massivas de ingestão por segundo sem colisão. A grande contrapartida é a complexidade operacional, exigem infraestrutura externa de orquestração (como o Apache ZooKeeper) para gerenciar os Worker IDs e evitar colisões entre nós.

Número Decimal em Disco (64 bits / BIGINT)
1541815603606036480

Na Base 10 não é possível separar os caracteres por texto. A verdadeira fragmentação do Snowflake ocorre a nível binário:

0000000000000000000000000000000000000000000000000000000000000000
Bit 1: Sinal (0) Bits 2-42: Timestamp (41 bits) Bits 43-52: Worker ID (10 bits) Bits 53-64: Contador (12 bits)

Qual Chave Escolher?

Para POCs ou projetos monolíticos simples, o AUTO_INCREMENT continua vantajoso pela sua extrema simplicidade e eficiência de 8 bytes.

Mas quando falamos de profissionalismo, microsserviços e escalabilidade, o UUID v7 consolida-se como o padrão ouro inegociável. Ele resolve o dilema entre geração descentralizada e performance em B-Trees. Deixe o ULID para APIs que exigem IDs curtos em texto, o Snowflake para ingestão massiva de eventos, e descarte o UUID v4 completamente em novas aplicações devido à degradação severa de índices e fragmentação física.

Neste artigo, optei por focar a análise exclusivamente em chaves primárias artificiais e independentes, deixando de lado chaves compostas ou naturais por possuírem dinâmicas próprias de modelagem.