ULID vs UUID v7: identificadores ordenados por tempo para bancos de dados
1. Introdução aos identificadores ordenáveis por tempo
Identificadores únicos universais (UUIDs) são onipresentes em sistemas modernos, mas a escolha do formato impacta diretamente a performance de bancos de dados. O UUID v4, amplamente utilizado, gera valores aleatórios que causam fragmentação severa em índices B-Tree — a estrutura de dados padrão da maioria dos bancos relacionais. Quando milhares de registros são inseridos por segundo, cada novo UUID v4 é inserido em uma posição aleatória da árvore, forçando rebalanceamentos frequentes e degradando o desempenho de escrita.
A ordenação temporal resolve esse problema: se os identificadores forem monotonicamente crescentes no tempo, os novos registros são inseridos no final do índice, eliminando a fragmentação. Duas soluções modernas atendem a esse requisito: ULID (Universal Unique Lexicographically Sortable Identifier) e UUID v7 (definido pela RFC 9562). Ambas incorporam timestamps em sua estrutura, mas diferem em formato, implementação e casos de uso ideais.
2. Anatomia do ULID: estrutura e propriedades
O ULID é um identificador de 26 caracteres codificado em base32 Crockford, dividido em duas partes:
- Timestamp (10 caracteres): 48 bits representando milissegundos desde a época Unix (1970-01-01). Isso permite ordenação lexicográfica precisa — um ULID gerado antes sempre será "menor" que um gerado depois.
- Componente aleatório (16 caracteres): 80 bits de entropia, fornecendo 1.21e+24 identificadores únicos por milissegundo.
Exemplo de ULID:
01ARZ3NDEKTSV4RRFFQ69G5FAV
A ordenação lexicográfica funciona porque o timestamp ocupa os primeiros 10 caracteres. Em uma string, "01ARZ3NDEK" (timestamp mais antigo) é lexicograficamente menor que "01ARZ3NDEL" (timestamp mais recente). Isso permite ordenação direta como string, sem necessidade de conversão.
O ULID é gerado puramente no lado da aplicação, sem dependência de banco de dados. Bibliotecas populares existem para Node.js (ulid), Python (python-ulid) e Go (oklog/ulid).
3. Anatomia do UUID v7: estrutura e propriedades
O UUID v7, padronizado pela RFC 9562 em maio de 2024, é um formato de 128 bits com layout específico:
ttttttttttttttttmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
Onde:
- 48 bits de timestamp Unix (milissegundos) — ocupando os bits mais significativos
- 74 bits aleatórios — incluindo 2 bits de variante e 4 bits de versão
- Formato de saída: 36 caracteres hexadecimais com hífens (8-4-4-4-12)
Exemplo de UUID v7:
018f3a6b-7c8d-4e5f-a1b2-c3d4e5f6a7b8
A grande vantagem do UUID v7 é o suporte nativo em bancos de dados modernos. PostgreSQL 16+ oferece gen_random_uuid() que pode gerar UUID v7 com a extensão pg_uuidv7. MySQL 9.0 introduziu UUID_TO_BIN() com suporte a ordenação temporal. SQLite e Microsoft SQL Server também estão adicionando suporte.
4. Comparação técnica: ULID vs UUID v7
Formato e legibilidade
| Característica | ULID | UUID v7 |
|---|---|---|
| Tamanho (string) | 26 caracteres | 36 caracteres |
| Codificação | Base32 Crockford | Hexadecimal |
| Hífens | Não | Sim (padrão) |
| Case-insensitive | Sim | Não (hex maiúsculo/minúsculo) |
Performance de geração
Ambos os formatos são extremamente rápidos, mas o ULID tende a ser ligeiramente mais rápido em implementações otimizadas devido à codificação base32 mais simples. Em testes de benchmark com Go:
ULID: ~180 ns/op
UUID v7: ~220 ns/op (com geração criptográfica segura)
Ordenação em índices
O ULID ordena como string — o banco compara os 26 caracteres sequencialmente. O UUID v7, quando armazenado como BINARY(16), ordena pelo valor numérico de 128 bits. Ambos funcionam bem, mas o UUID v7 como binário ocupa menos espaço:
ULID como TEXT: 26 bytes
UUID v7 como BINARY: 16 bytes
Compactação e armazenamento
Em uma tabela com 10 milhões de registros e índice primário:
ULID (TEXT): ~260 MB de dados de índice
UUID v7 (BINARY): ~160 MB de dados de índice
5. Casos de uso e trade-offs práticos
Sistemas distribuídos
O ULID é ideal quando você precisa de identificadores ordenáveis em sistemas sem suporte nativo a UUID v7. Por exemplo, em microsserviços que usam MongoDB ou Redis como armazenamento primário:
// Geração de ULID em Node.js
const ulid = require('ulid');
const id = ulid.ulid(); // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
Bancos de dados relacionais modernos
O UUID v7 elimina a necessidade de funções customizadas. No PostgreSQL:
-- PostgreSQL com extensão pg_uuidv7
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
CREATE TABLE pedidos (
id UUID DEFAULT uuid_generate_v7() PRIMARY KEY,
data_criacao TIMESTAMPTZ DEFAULT NOW()
);
-- Consultas ordenadas por tempo sem ORDER BY explícito
SELECT * FROM pedidos WHERE id > '018f3a6b-7c8d-4e5f-a1b2-c3d4e5f6a7b8';
Aplicações legadas e migrações
Migrar de UUID v4 para ULID ou UUID v7 requer reparticionamento de tabelas. A estratégia recomendada é:
1. Adicionar nova coluna com o novo formato
2. Preencher registros existentes com valores retroativos (timestamp da criação original)
3. Criar índice na nova coluna
4. Gradualmente migrar as consultas
Segurança e previsibilidade
Identificadores ordenados por tempo são previsíveis — um atacante pode estimar quando um registro foi criado. Para mitigar:
- ULID: usar entropia adicional (mais bits aleatórios)
- UUID v7: a RFC permite substituir bits aleatórios por contadores monotônicos, mas isso reduz entropia
6. Implementação e integração em projetos reais
Geração de ULID em Python
import ulid
id = ulid.new() # "01ARZ3NDEKTSV4RRFFQ69G5FAV"
Geração de UUID v7 em Go
import "github.com/google/uuid"
id := uuid.Must(uuid.NewV7()) // "018f3a6b-7c8d-4e5f-a1b2-c3d4e5f6a7b8"
Armazenamento otimizado no MySQL
-- MySQL 9.0 com suporte nativo
CREATE TABLE eventos (
id BINARY(16) DEFAULT (UUID_TO_BIN(UUID(), TRUE)) PRIMARY KEY,
payload JSON
);
-- Consulta ordenada por tempo
SELECT BIN_TO_UUID(id) FROM eventos ORDER BY id;
Indexação em PostgreSQL
-- Índice B-Tree funciona naturalmente com UUID v7
CREATE INDEX idx_pedidos_tempo ON pedidos (id);
-- Consulta paginada por tempo
SELECT * FROM pedidos
WHERE id > '018f3a6b-7c8d-4e5f-a1b2-c3d4e5f6a7b8'
ORDER BY id
LIMIT 100;
7. Considerações finais e recomendações
Quando escolher ULID
- Sistemas legados que já usam strings como chaves
- Bancos NoSQL (MongoDB, Cassandra) onde ordenação lexicográfica é nativa
- Necessidade de IDs curtos e legíveis para logs ou URLs
- Ambientes sem suporte a UUID v7 no banco de dados
Quando escolher UUID v7
- Projetos novos em bancos relacionais modernos (PostgreSQL 16+, MySQL 9.0+)
- Necessidade de padronização e interoperabilidade entre sistemas
- Preocupação com espaço de armazenamento (16 bytes vs 26 bytes)
- ORMs modernos que já oferecem suporte nativo (Prisma, TypeORM, SQLAlchemy)
Checklist de decisão (5 perguntas)
- O banco suporta UUID v7 nativamente? Sim → UUID v7; Não → ULID
- Precisa de IDs legíveis para humanos? Sim → ULID; Não → UUID v7
- O espaço de armazenamento é crítico? Sim → UUID v7 (16 bytes); Não → ULID
- Sistema é distribuído sem relógio sincronizado? Considere Snowflake ou KSUID como alternativas
- Precisa de máxima entropia para segurança? Ambos são equivalentes; adicione entropia extra se necessário
Tendências futuras
A indústria está convergindo para UUID v7 como padrão. A RFC 9562 já é adotada por PostgreSQL, MySQL e SQLite. ORMs como Prisma 5+ e TypeORM 0.3+ oferecem suporte nativo. O ULID permanece relevante para nichos específicos, especialmente em ecossistemas JavaScript e Go.
Referências
- RFC 9562 - UUID Version 7 — Especificação oficial do UUID v7 pela IETF, com detalhes do formato de 128 bits e layout de timestamp
- ULID Specification — Repositório oficial da especificação ULID, incluindo algoritmo de geração e exemplos em múltiplas linguagens
- PostgreSQL UUID v7 Extension (pg_uuidv7) — Extensão PostgreSQL para geração nativa de UUID v7 com funções SQL
- UUID v7 no MySQL 9.0 — Documentação oficial MySQL sobre funções UUID_TO_BIN e BIN_TO_UUID com suporte a ordenação temporal
- ULID vs UUID: Performance Comparison — Artigo técnico comparando performance de geração, armazenamento e indexação entre ULID e UUID v4/v7
- UUID v7: The New Standard for Database Identifiers — Blog da Percona explicando benefícios do UUID v7 para performance de bancos de dados
- Crockford Base32 Encoding — Especificação original da codificação base32 usada pelo ULID, por Douglas Crockford