UUID e ULID: Identificadores Únicos para Sistemas Distribuídos

A crescente necessidade de identificadores únicos em sistemas distribuídos tem impulsionado a evolução dos mecanismos de geração de IDs. Enquanto os UUIDs (Universally Unique Identifiers) consolidaram-se como padrão ao longo de décadas, os ULIDs (Universally Unique Lexicographically Sortable Identifiers) emergiram recentemente como alternativa promissora, oferecendo ordenação léxica nativa e melhor legibilidade humana. Vamos examinar em profundidade a história, especificações técnicas, implementações e trade-offs entre essas duas abordagens para identificação única.​

A principal vantagem dos UUIDs reside na sua capacidade de garantir unicidade global sem coordenação centralizada, característica essencial em arquiteturas distribuídas modernas. Por outro lado, os ULIDs combinam essa mesma garantia de unicidade com ordenação temporal, reduzindo fragmentação de índices em bancos de dados e facilitando rastreamento cronológico de eventos. A escolha entre UUID e ULID — ou entre as diversas versões de UUID — depende de requisitos específicos de performance, segurança, ordenação e compatibilidade com infraestrutura existente.

UUID

Origens na Computação Distribuída

A história dos UUIDs remonta à década de 1980, quando a Apollo Computer desenvolveu o conceito para o Network Computing System (NCS), um sistema pioneiro de computação distribuída. O design original foi inspirado pelos identificadores únicos de 64 bits utilizados no sistema operacional Domain/OS da Apollo Computer, que já demonstravam a necessidade de identificação descentralizada em ambientes de rede. Posteriormente, a Open Software Foundation (OSF) adotou e expandiu essa arquitetura para o Distributed Computing Environment (DCE) no final da década de 1980.​

O design dos UUIDs manteve a filosofia de identificação sem autoridade central. Essa herança técnica estabeleceu princípios fundamentais que persistem até hoje: identificadores de 128 bits, geração independente em múltiplos nós, e probabilidade extremamente baixa de colisão. Na década de 1990, a Microsoft adotou esse design para suas plataformas Windows sob a nomenclatura GUIDs (Globally Unique Identifiers), ampliando significativamente a adoção do padrão.​

Trajetória de Padronização pela IETF

A padronização formal dos UUIDs pela Internet Engineering Task Force (IETF) consolidou sua posição como padrão universal. Em julho de 2005, a publicação da RFC 4122 estabeleceu as especificações técnicas para as versões 1 a 5 dos UUIDs, definindo formatos, algoritmos de geração e casos de uso. Esta RFC registrou um namespace URN (Uniform Resource Name) para UUIDs e recapitulou especificações anteriores do OSF DCE, mantendo compatibilidade com sistemas legados.​

Antes mesmo da RFC 4122, os UUIDs já haviam sido padronizados por outros organismos. A ISO/IEC 11578:1996 documentou o padrão como parte da especificação “Information technology – Open Systems Interconnection – Remote Procedure Call (RPC)”, enquanto a ITU-T Rec. X.667 (posteriormente atualizada para ISO/IEC 9834-8) também adotou o formato UUID. Essa multiplicidade de padronizações reflete a importância crítica dos UUIDs na infraestrutura de sistemas distribuídos globais.​

Modernização

Em 7 de maio de 2024, a IETF publicou a RFC 9562, uma revisão abrangente que introduziu três novas versões de UUID (v6, v7 e v8) e clarificou ambiguidades existentes na especificação anterior. Esta atualização foi motivada pela necessidade de endereçar casos de uso modernos, particularmente o uso de UUIDs como chaves primárias em bancos de dados relacionais, onde as versões 1-5 apresentavam deficiências significativas de performance.​

A RFC 9562 estabeleceu o “UUID Subtypes Registry” para catalogar padrões UUID amplamente utilizados, documentando versões desde a original Gregorian Time-based (v1) até as novas especificações. Entre as novas versões, a UUID v7 destaca-se como a mais relevante para aplicações modernas, incorporando timestamp Unix de 48 bits como componente mais significativo, permitindo ordenação temporal enquanto mantém 74 bits de aleatoriedade. A RFC explicitamente recomenda que “sistemas que não envolvam UUID v1 legado DEVEM usar UUID v7 ao invés disso”, sinalizando uma mudança de paradigma nas melhores práticas.

Estrutura Fundamental dos UUIDs

Todos os UUIDs consistem em identificadores de 128 bits (16 bytes), dos quais 2 a 4 bits são utilizados para indicar a variante do formato, e 4 bits adicionais identificam a versão específica.

A variante mais comum em uso atualmente é a OSF DCE, também conhecida como RFC 4122/DCE 1.1 ou “Leach–Salz UUIDs” (nomeada em homenagem aos autores do Internet Draft original).

A representação canônica (visual) de um UUID utiliza encoding hexadecimal no formato 8-4-4-4-12 (exemplo: 20354d7a-e4fe-47af-8ff6-187bca92f3f9).

A geração de UUIDs v4, a versão mais utilizada atualmente, envolve criação de 16 bytes de dados aleatórios, seguida pela aplicação de máscaras de bits para inserir marcadores de versão e variante. O processo específico requer operações bitwise AND e OR para substituir bits em posições determinadas sem alterar a aleatoriedade dos bits adjacentes.

Versões

As versões de UUID apresentam características distintas que as tornam adequadas para casos de uso específicos.

UUID v1 combina timestamp de 60 bits (contando intervalos de 100 nanossegundos desde 15 de outubro de 1582, data da reforma gregoriana do calendário), clock sequence e endereço MAC do nó gerador. Esta abordagem garante unicidade através da combinação de dimensões temporais e espaciais, mas expõe informações sensíveis sobre o sistema gerador, incluindo o endereço MAC da interface de rede, levantando preocupações de privacidade.​ Seu uso se encontra em sistemas legados, logs que precisam de ordenação temporal implícita (não lexicográfica), ambientes internos em que a exposição do MAC não é crítica.

UUID v2 permanece reservada para identificadores de segurança DCE e é raramente implementada em sistemas modernos.

UUID v3 utiliza hash MD5 de um namespace concatenado com um nome fornecido pelo usuário, sendo assim deterministico. É considerado obsoleto em favor da versão 5, já que MD5 é criptograficamente fraco, embora para simples identificação ainda funcione.

UUID v5 aprimora a v3 substituindo MD5 por SHA-1 para geração do hash, embora SHA-1 também não seja mais considerado ideal para aplicações criptográficas modernas. Útil para gerar IDs estáveis para URLs, nomes de recursos, chaves em bancos de dados derivadas de um identificador lógico.

UUID v4 gera identificadores através de dados completamente aleatórios, oferecendo máxima imprevisibilidade porém sem capacidade de ordenação.​ É a versão mais usada em APIs, bancos e frameworks (por exemplo, UUID.randomUUID() em Java gera v4).

UUID v6 mantém a ideia de v1 (baseado em tempo, informação do host), mas rearranja os bits para que a ordenação lexicográfica coincida com a ordem temporal, melhorando comportamento em índices. Muitos consideram obsoleto em favor do v7 e não é tão popular em bibliotecas modernas.

UUID v7, introduzida na RFC 9562 em 2024, utiliza 48 bits de timestamp Unix em milissegundos, 4 bits de versão, 2 bits de variância e 74 bits de aleatoriedade, fornecendo IDs monotonicamente ordenáveis no tempo.​ Acaba sendo um substituto direto do v4 quando se quer ganhar ordenação temporal amigável mantendo o padrão UUID.

Essa versão foi adotada nativamente pelo PostgreSQL 18 em 2025, posiciona-se como recomendação default para novas aplicações, combinando ordenação temporal com maturidade de ecossistema.

-- PostgreSQL 18+ native function
SELECT uuidv7();
-- Output: 01976408-e525-78fb-889c-818826fc412f
 
-- Optional time parameter for historical UUIDs
SELECT uuidv7('2024-01-01 00:00:00'::timestamp);
 
-- Extract timestamp from any UUIDv7
SELECT uuid_extract_timestamp('01976408-e525-78fb-889c-818826fc412f'::uuid);
-- Output: 2024-12-06 10:30:45.637+00

Finalmente, a UUID v8 oferece formato customizável onde apenas os campos de versão e variância são obrigatórios, permitindo implementações experimentais ou específicas de fornecedores. Usado quando se quer um layout sob medida mas ainda compatível com o ecossistema UUID.

Curiosidades

Um UUID v4 sempre apresentará o dígito 4 no terceiro bloco e um dígito entre 8 e b no início do quarto bloco, indicando respectivamente a versão 4 e a variante OSF DCE (bits 10xx). Esta convenção permite identificação visual imediata da versão e variante de qualquer UUID.

O uso de UUIDs v4 como chaves primárias em bancos de dados relacionais apresenta desafios significativos de performance relacionados à estrutura de árvores B+ (B-tree) utilizadas para indexação. Como UUIDs v4 são completamente aleatórios, cada inserção tem probabilidade igual de ocorrer em qualquer posição da árvore, forçando o banco de dados a localizar e modificar páginas aleatórias no índice ao invés de simplesmente anexar novos registros no final.

UUIDs v7 e ULIDs abordam diretamente o problema de fragmentação através de ordenação temporal baseada em timestamp. Como o componente de tempo sempre avança monotonicamente, cada novo identificador gerado é numericamente maior que os anteriores (em comparação binária começando pelo bit mais significativo). Esta propriedade permite que o banco de dados anexe novos registros próximo ao final do índice, reduzindo drasticamente divisões de página e mantendo localidade de dados.

Comparação de throughput (operações por segundo) entre UUID v4, UUID v7 e ULID em PostgreSQL, demonstrando a superioridade do UUID v7 em inserções sequenciais

Comparação de tamanho de índice B-tree entre UUID v4 e UUID v7 após 10 milhões de inserções, mostrando uma redução de 22% no UUID v7 devido à menor fragmentação

Mesmo quando se usa UUID como string, é recomendável usar colunas do tipo nativo (uuid) ou binário de 16 bytes quando o banco oferece, para reduzir armazenamento e melhorar comparação/índice.

ULID

Estrutura e Especificação

O ULID mantém compatibilidade de tamanho com UUIDs através de seus 128 bits, mas divide essa estrutura de forma radicalmente diferente: 48 bits dedicados ao timestamp (representando UNIX time em milissegundos) e 80 bits de aleatoriedade criptograficamente segura. Esta partição permite que ULIDs funcionem até o ano 10889 d.C., quando o espaço de timestamp de 48 bits será esgotado (representando 2^48 - 1 ou 281.474.976.710.655 em época Unix).​

A principal inovação técnica do ULID reside no uso do Base32 de Crockford para encoding, ao invés do hexadecimal tradicional dos UUIDs.

Este alfabeto — 0123456789ABCDEFGHJKMNPQRSTVWXYZ — foi criado por Douglas Crockford (também criador do JSON) e exclui deliberadamente caracteres que podem ser confundidos visualmente: I, L, O e U são omitidos para evitar confusão com 1, 1, 0 e V respectivamente. Esta escolha resulta em identificadores mais legíveis para humanos e menos propensos a erros de transcrição, particularmente útil em cenários onde IDs precisam ser comunicados verbalmente ou copiados manualmente.​

A representação de um ULID consiste em 26 caracteres (ao invés dos 36 do UUID), oferecendo eficiência de 25% superior ao hexadecimal em bits por caractere (5 bits versus 4).

O formato visual distingue claramente os componentes: 01AN4Z07BY79KA1307SR9X4MV3, onde os primeiros 10 caracteres representam o timestamp e os 16 restantes a porção aleatória. Esta compactação não apenas economiza espaço de armazenamento mas também melhora a legibilidade, pois identificadores mais curtos são cognitivamente mais fáceis de processar.​

ULID e UUIDv7 são ambos time-ordered e lexicograficamente ordenáveis, projetados para se comportar bem em B‑tree, então o padrão geral de page splits e fragmentação é semelhante. A escolha é mais por compatibilidade de tooling (UUID) contra legibilidade (ULID) do que por diferença significativa de performance.

Padrões arquiteturais

Para sistemas distribuídos de larga escala, adote estratégia de chaves primárias duais: utilize inteiros auto-incrementais (32 ou 64 bits) para relações internas de tabela e UUIDs/ULIDs para identificadores de nível de aplicação e chaves naturais.

Evite usar UUIDs como foreign keys em JOINs internos frequentes. Após filtro inicial para encontrar item desejado na tabela principal da consulta, JOINs para coletar dados de outras tabelas devem usar chaves mais eficientes (INT/ROWID). UUID raramente é usado como referência de foreign key neste arranjo, sendo importante quase exclusivamente como filtro inicial. Este padrão reduz overhead de índice enquanto previne exposição de valores de ID interno a serviços externos.​

Implemente planejamento para colisões: assegure que aplicações e servidores comportem-se de forma segura e consistente se encontrarem UUIDs/ULIDs idênticos para registros ou objetos diferentes. Embora probabilidade seja extremamente baixa, sistemas críticos devem ter detecção e recuperação de colisões. Nunca use UUIDs/ULIDs para identificadores de sessão ou outros tokens de segurança — use mecanismos criptográficos dedicados projetados especificamente para esses propósitos.

Para adoção de ULID, avalie maturidade de bibliotecas disponíveis para seu stack tecnológico. Priorize implementações que sigam estritamente especificação oficial, incluindo tratamento apropriado de overflow e geração monotônica. Considere estratégia de encoding: ULIDs nativamente usam Base32 de Crockford, mas podem ser convertidos para formato UUID ou armazenados como binário (16 bytes) para compatibilidade com sistemas existentes. Decida se benefícios de legibilidade humana justificam potencial lock-in de formato proprietário comparado a padrão formalmente especificado como UUID v7.

Em ambientes de data warehouse, a escolha entre UUIDs e identificadores sequenciais apresenta considerações distintas das aplicações transacionais OLTP. Índices clusterizados são otimizados especificamente para workloads analíticos com schemas em estrela ou floco de neve, além de cargas de trabalho que inserem grandes volumes de dados com atualizações e deleções mínimas (ex: IoT).

A escolha impacta diretamente na eficiência de compressão e tamanho de arquivo. Algoritmos de compressão tais como Snappy, Gzip e Brotli, comprimem menos eficientemente identificadores completamente aleatórios (UUID v4) do que identificadores com componente temporal ordenado (UUID v7, ULID).

Além disso, escolher uma chave de índice ordenado que seja frequentemente consultada e possa se beneficiar de segment elimination melhora significativamente performance. Por exemplo, se apenas os dados analíticos mais recentes precisam ser consultados (últimos 15 segundos), índices ordenados fornecem eliminação de segmento eficaz para dados antigos, realizando o processamento em um escopo menor de dados.

Para tabelas de fatos em data warehouses, considere estratégia de chave inteira com coluna UUID/ULID separada para rastreamento distribuído. Tabelas de fatos frequentemente contêm dezenas ou centenas de milhões de registros onde economia de espaço é crítica, menos por storage (que é relativamente barato) e mais por eficiência de consumo do processamento distribuído.

Para particionamento e clustering (ordenação física):

  • Nunca use UUID/ULID como chave de partição devido à cardinalidade extremamente alta.
  • Particione por dimensões temporais (date, month, year) ou dimensões de baixa cardinalidade (country, region, product_category).​
  • Use UUID v7 ou ULID como clustering/Z-order column secundária apenas se pesquisas por ID são críticas e volumes justificam o custo. Priorize sempre timestamp ou dimensões mais seletivas como primeira coluna de clustering.​
  • Armazene UUIDs em formato binário (16 bytes) ao invés de CHAR(36) para economizar 20 bytes por registro mais overhead em todos índices secundários.

Para pipelines de streaming e ingestão de eventos, ULID oferece vantagens específicas em datastores não-ordenados. A ordenação léxica natural permite queries eficientes por intervalos temporais sem índices adicionais, e identificadores podem ser gerados em qualquer nó da rede sem coordenação centralizada. A compacidade de 26 caracteres versus 36 para UUID também reduz overhead de rede para workloads de alta taxa de transferência.