Multi-tenacidade, bancos de dados, chaves primárias e ULID

ULID - DevSpace
ULID - DevSpace

Última atualização em 3 de junho de 2026 por Willian Tuttoilmondo

Quando escrevi este artigo sobre multi-tenacidade, bancos de dados e chaves primárias, usei como exemplo um modelo que está disponível em quase todos os bancos de dados mais usados do mercado: o UUID. Como tudo evolui quando se fala em desenvolvimento e arquitetura, o UUID acabou revelando suas desvantagens e para que elas fossem superadas, outro modelo foi criado: o ULID. Mas, o que é o ULID e como usá-lo no meu dia a dia?

ULID (Universally Unique Lexicographically Sortable Identifier) é um identificador de 128 bits que combina timestamp e aleatoriedade para produzir chaves únicas, distribuídas e naturalmente ordenáveis. Diferentemente do UUID tradicional, ULIDs preservam a ordem cronológica dos registros, favorecendo a organização de índices e a escalabilidade de aplicações modernas.

O que vamos aprender com este artigo

Ao longo deste artigo você aprenderá:

  • Por que identificadores tradicionais podem se tornar um problema em aplicações distribuídas e bancos de dados de grande volume.
  • Quais são as limitações do UUID tradicional e como elas impactam índices, ordenação e desempenho.
  • O que é um ULID e por que ele vem ganhando espaço em arquiteturas modernas.
  • Como a estrutura interna do ULID combina timestamp e aleatoriedade para produzir identificadores únicos e ordenáveis.
  • Como implementar um gerador completo de ULIDs em Delphi utilizando Object Pascal.
  • Como codificar e decodificar valores utilizando Base32 Crockford.
  • Como gerar identificadores sequenciais mantendo compatibilidade com ambientes distribuídos.
  • Como utilizar ULIDs em bancos de dados relacionais como PostgreSQL.
  • Quando utilizar ULID, UUID ou UUIDv7 em novos projetos.
  • Como reduzir fragmentação de índices e melhorar a organização dos dados através de identificadores temporalmente ordenáveis.
  • Quais cuidados devem ser considerados ao implementar algoritmos de geração de identificadores em aplicações corporativas.
  • Como aplicar ULIDs em APIs RESTful, microsserviços, arquiteturas orientadas a eventos e sistemas escaláveis.

Desvendando o ULID

ULID, do Inglês Universally Unique Lexicographically Sortable Identifier, é um identificador único e ordenável formado por duas informações distintas: 48 bits que indicam os milissegundos de um Unix Epoch Timestamp e 80 bits de dados aleatórios, codificadas como uma string de 26 caracteres em Base32 Crockford, tornando-a mais compacta e eficiente para armazenamento e leitura do que um UUID tradicional. Num exemplo muito simpes, vamos criar sete identificadores ULID e ver como eles se ordenam.

ULID gerado em lote - DevSpace
ULID gerado em lote – DevSpace

Como é possível ver, todos os identificadores gerados exatamente no mesmo milissegundo possuem o mesmo início, o que nos permite ordenar o registro pelo momento de sua criação. Ao compararmos com o UUID veremos que essa vantagem não se mantém.

UUID gerado em lote - DevSpace
UUID gerado em lote – DevSpace

A primeira vantagem já se mostra aí. Enquanto o UUID não pode ser ordenado, o ULID nos permite fazer esta ordenação. Isso garante que os registros, independentemente da origem, possam ser visualmente ordenados pela chave primária quando nenhum outro ordenador for usado. Vale lembrar que, assim como o UUID, o ULID pode ser gerado pela aplicação.

Entendendo a estrutura interna de um ULID

Um ULID possui 128 bits, exatamente a mesma quantidade utilizada por um UUID tradicional. A diferença está na forma como essas informações são organizadas. Enquanto um UUIDv4 é composto essencialmente por dados aleatórios, o ULID divide sua estrutura em duas partes bem definidas.

Os primeiros 48 bits armazenam um timestamp em milissegundos desde a época Unix. Essa informação permite que os identificadores sejam naturalmente ordenados de acordo com o momento em que foram gerados, tornando operações de classificação e indexação muito mais eficientes.

Os 80 bits restantes são utilizados como fonte de entropia, garantindo unicidade mesmo quando múltiplos identificadores são gerados simultaneamente em diferentes processos, máquinas ou serviços distribuídos.

Essa combinação entre informação temporal e aleatoriedade faz com que o ULID reúna duas características extremamente desejáveis em sistemas modernos: a capacidade de gerar identificadores únicos sem dependência de um banco de dados centralizado e a possibilidade de manter uma ordenação cronológica natural dos registros.

Na prática, isso significa que registros criados em sequência tendem a permanecer próximos dentro dos índices do banco de dados, reduzindo fragmentação e melhorando o desempenho de consultas e inserções quando comparados a identificadores completamente aleatórios.

Multi-tenacidade, bancos de dados, chaves primárias e ULID -
Diagrama de dados ULID – DevSpace

A receita do bolo

A ideia por trás do ULID é simples: usar o momento da criação do valor para codificar seu valor. Os dez primeiros caracteres compõem a parcela codificada do timestamp do momento da geração. Por isso, no exemplo, como vários identificadores foram gerados no mesmo milissegundo, todos eles têm o mesmo início: 01K9SFSMXV. Vale lembrar que esta codificação é feita em Base32 Crockford, que utiliza apenas os caracteres 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, G, H, J, K, M, N, P, Q, R, S, T, V, W, X, Y e Z. Isso garante que o caractere O não seja confundido com o caracter 0, por exemplo. O restante, aleatório, é gerado da mesma forma, usando a mesma base.

O segredo, no entanto, está na codificação da data.

Para efetuarmos a codificação, vamos precisar definir duas constantes: um vetor com os caracteres permitidos e a largura deste vetor. Para isso, vamos defini-las da seguinte forma:

Depois disso, precisamos codificar o valor em uma string de 10 bytes com base no Unix Epoch Timestamp que definirmos como base. Para garantir a ordenação pelo horário da criação do valor, usaremos como base o valor de data e hora correntes.

Já para o restante, basta que usemos dados aleatórios determinados pela constante de caracteres válidos.

A desvantagem que temos, nesse caso, é que não existe uma estrutura nativa na linguagem Delphi – na verdade, em nenhuma linguagem de programação – que trate ULID. Para isso, precisamos criar uma estrutura e implementá-la. Para resolver esta questão, criei um tipo chamado TULID que cria e manipula ULIDs de maneira simples e rápida.

Implementando ULID no banco de dados

Assim como as linguagens de programação, os bancos de dados também não suportam nativamente o tipo ULID. Contudo, graças à comunidade, as plataformas livres vão sendo abastecidas com extensões que ajudam a tratar os dados de maneira simples e rápida.

Com gosto muito de usar o PostgreSQL para meus projetos, encontrei uma extensão muito boa, que me permite trabalhar com ULIDs de maneira simples e natural: a pgx_ulid. É ela quem me permite fazer o exercício lá do início, gerando as chaves em lote. Sua instalação é simples e rápida e pode ser feita tranquilamente. Como meu servidor PostgreSQL está instalado em uma máquina Linux, baixei o pacote para a versão do banco de dados – 18, no meu caso – aqui e fiz a instalação. Depois disso é só criar a extensão com o comando abaixo e voilà, teremos o tipo de dado e as funções básicas para operação disponíveis.

Isso cria as funções abaixo:

Funções PostgreSQL para ULID - DevSpace
Funções PostgreSQL para ULID – DevSpace

Para mais informações, a página do projeto no GitHUb conta com uma boa documentação.

Mas, enfim, quais são as vantagens mesmo?

Além da vantagem de ser uma chave única, como já vimos aqui, se compararmos com o UUID, temos o seguinte:

Monotonicidade é a capacidade de gerar múltiplos ULIDs dentro do mesmo milissegundo mantendo a ordem sequencial entre eles. Dessa forma, mesmo quando diversos identificadores são criados simultaneamente, sua ordenação cronológica continua preservada, eliminando ambiguidades e garantindo consistência em sistemas de alta concorrência.

  • Classificável por tempo – Os primeiros 10 bytes contêm o Unix Epoch, que é classificado automaticamente;
  • Base32 de Crockford – Codificação mais eficiente, sem distinção entre maiúsculas e minúsculas, sem caracteres especiais;
  • Opção de monotonicidade – Pode gerar valores monotonicamente crescentes ou decrescentes para o mesmo timestamp e;
  • Legível por humanos – Mais fácil de depurar e ler em logs e bancos de dados.

ULID vs Sequence: qual abordagem escolher?

Durante décadas, sequences e campos auto incrementais foram a principal estratégia utilizada para geração de chaves primárias em bancos de dados relacionais. Simples, eficientes e amplamente suportadas, essas estruturas permitem que cada novo registro receba automaticamente um identificador numérico sequencial, facilitando consultas, indexação e manutenção dos dados.

Em aplicações monolíticas e bancos de dados centralizados, essa abordagem continua sendo extremamente eficiente. Entretanto, à medida que sistemas evoluem para arquiteturas distribuídas, APIs RESTful, microsserviços e ambientes em nuvem, surgem limitações que tornam o uso exclusivo de sequences menos atrativo.

A principal delas é a dependência de uma fonte central de geração de identificadores. Como o próximo valor da sequência precisa ser obtido diretamente do banco de dados, componentes externos não conseguem gerar identificadores de forma autônoma antes da persistência do registro.

O ULID foi criado justamente para eliminar essa dependência. Diferentemente das sequences, ele pode ser gerado localmente por qualquer aplicação, serviço ou processo, sem necessidade de comunicação prévia com o banco de dados.

Os identificadores podem ser gerados instantaneamente pela própria aplicação, permitindo que entidades, eventos, mensagens e objetos de domínio sejam criados já contendo seu identificador definitivo antes mesmo de serem persistidos.

Essa característica se torna particularmente importante em arquiteturas modernas. Em um microsserviço, por exemplo, um pedido pode gerar eventos, mensagens e registros de auditoria antes mesmo de ser armazenado no banco de dados. Com sequences, essa coordenação exige etapas adicionais. Com ULIDs, todos os componentes já compartilham o mesmo identificador desde sua criação.

Outro aspecto importante envolve a exposição de dados. Sequences revelam informações sobre o crescimento do sistema e a quantidade aproximada de registros armazenados. Com ULIDs, essa previsibilidade desaparece.

Apesar de continuarem ordenáveis internamente, os identificadores deixam de expor informações relevantes sobre o crescimento da base de dados.

Isso não significa que sequences devam ser abandonadas. Em aplicações corporativas tradicionais, sistemas internos, ERPs e bancos de dados centralizados, elas continuam sendo uma das soluções mais simples e eficientes disponíveis.

Por outro lado, quando a arquitetura exige geração distribuída de identificadores, independência do banco de dados, integração entre múltiplos serviços ou suporte a sistemas altamente escaláveis, o ULID oferece vantagens significativas sem abrir mão da ordenação natural dos registros.

Em resumo, sequences continuam sendo uma excelente escolha para sistemas centralizados, enquanto ULIDs se destacam em ambientes distribuídos, APIs RESTful, arquiteturas orientadas a eventos e aplicações modernas que precisam crescer além dos limites de um único banco de dados.

ULID vs UUIDv4: entendendo as diferenças

Durante muitos anos o UUIDv4 foi a solução mais utilizada para geração de identificadores únicos em aplicações distribuídas. Sua principal característica é a utilização de números aleatórios para compor praticamente toda a estrutura do identificador, garantindo uma probabilidade extremamente baixa de colisão sem depender de um banco de dados centralizado.

Embora essa abordagem funcione muito bem para garantir unicidade, ela apresenta uma limitação importante em sistemas modernos: a ausência de ordenação temporal. Como os identificadores são gerados de forma completamente aleatória, registros criados em sequência não possuem qualquer relação entre si quando armazenados em índices de banco de dados.

O ULID foi criado justamente para resolver esse problema. Em vez de utilizar apenas aleatoriedade, ele incorpora um timestamp de 48 bits em sua estrutura, permitindo que os identificadores sejam naturalmente ordenados de acordo com o momento em que foram gerados.

A tabela a seguir resume as principais diferenças entre as duas abordagens:

Observe os exemplos abaixo:

UUIDv4:
550e8400-e29b-41d4-a716-446655440000
3f9f98d2-b56d-4fd6-9f7e-3d9f1e7c1f25
91c5d58d-cad5-4d2d-b3d0-4f28bfa8f0c1

Nos exemplos acima é possível perceber que os ULIDs mantêm uma sequência visual associada ao instante de criação. Já os UUIDs apresentam uma distribuição completamente aleatória. Essa diferença impacta diretamente a forma como os bancos de dados organizam seus índices.

Quando um índice baseado em UUIDv4 recebe novas inserções, os registros tendem a ser distribuídos aleatoriamente pelas páginas de dados, aumentando a fragmentação e a necessidade de reorganização interna. Em tabelas com milhões de registros, esse comportamento pode gerar impactos perceptíveis em operações de inserção, manutenção e crescimento dos índices.

Por outro lado, os ULIDs preservam uma ordem cronológica natural. Como registros gerados em momentos próximos também produzem identificadores próximos, os índices tendem a crescer de forma mais organizada, reduzindo page splits e melhorando a localidade dos dados.

Isso não significa que o UUIDv4 seja uma tecnologia obsoleta. Pelo contrário. Ele continua sendo uma excelente escolha para aplicações que dependem do padrão UUID, possuem forte integração com ferramentas que utilizam tipos UUID nativos ou simplesmente não necessitam de ordenação temporal.

Entretanto, para sistemas modernos baseados em APIs RESTful, microsserviços, arquiteturas orientadas a eventos, Event Sourcing e aplicações de alta escalabilidade, o ULID oferece uma combinação extremamente interessante de unicidade, ordenação temporal e eficiência operacional, tornando-se uma alternativa cada vez mais adotada em projetos que precisam crescer de forma sustentável.

ULID vs UUIDv7: qual escolher?

Durante muitos anos o UUID foi a principal alternativa para geração de identificadores únicos em aplicações distribuídas. Entretanto, uma de suas maiores limitações sempre foi a ausência de ordenação temporal. Como os valores são gerados de forma essencialmente aleatória, registros inseridos em sequência acabam sendo distribuídos aleatoriamente pelos índices do banco de dados, aumentando a fragmentação e reduzindo a eficiência de operações de inserção.

Para resolver esse problema, surgiram duas abordagens modernas: o ULID e o UUIDv7. Ambos incorporam informações temporais ao identificador, permitindo que os registros sejam naturalmente ordenados de acordo com o momento em que foram criados. Essa característica melhora significativamente o desempenho de índices em bancos de dados relacionais e facilita a análise cronológica de eventos e registros.

Embora possuam objetivos semelhantes, existem diferenças importantes entre eles.

A principal vantagem do UUIDv7 é sua compatibilidade com o ecossistema UUID já existente. Bancos de dados como PostgreSQL, SQL Server e MySQL já possuem suporte nativo a UUIDs, permitindo que aplicações adotem UUIDv7 sem alterações significativas na estrutura de dados.

Por outro lado, o ULID oferece uma representação muito mais amigável para leitura humana. Seu formato utiliza a codificação Base32 Crockford, eliminando caracteres ambíguos e reduzindo o tamanho do identificador. Além disso, a ordenação lexicográfica permite que registros sejam classificados corretamente utilizando simples operações de ordenação textual.

Observe os exemplos a seguir:

UUIDv7:
0198e2e4-f39d-7e6f-a95e-bf22f0b8e123
0198e2e5-08f4-79b4-b74c-8c2b8f0f92ab

Embora ambos sejam ordenáveis, o ULID apresenta uma estrutura mais compacta e facilmente identificável visualmente, característica que costuma ser valorizada em logs, auditorias, APIs RESTful e sistemas orientados a eventos.

Na prática, a escolha entre ULID e UUIDv7 depende do contexto da aplicação. Quando existe forte dependência do ecossistema UUID e dos tipos nativos do banco de dados, UUIDv7 tende a ser uma excelente escolha. Entretanto, quando legibilidade, ordenação textual e portabilidade entre diferentes tecnologias são fatores importantes, o ULID continua sendo uma alternativa extremamente elegante e eficiente.

Onde utilizar ULIDs

Agora que compreendemos como os ULIDs funcionam internamente, suas vantagens em relação a UUIDs tradicionais e sua integração com bancos de dados relacionais, surge uma questão importante: em quais cenários essa tecnologia realmente faz diferença?

Embora o ULID possa ser utilizado como substituto direto de qualquer identificador único, seu verdadeiro potencial aparece quando a aplicação precisa combinar escalabilidade, distribuição, rastreabilidade e ordenação temporal. Nesses contextos, a capacidade de gerar identificadores únicos sem depender de uma fonte centralizada se transforma em uma vantagem arquitetural significativa.

APIs RESTful

Uma das aplicações mais comuns para ULIDs está na construção de APIs RESTful. Em vez de expor identificadores sequenciais previsíveis, a aplicação passa a utilizar chaves únicas e ordenáveis que podem ser geradas pela própria camada de domínio antes mesmo da persistência dos dados.

Além de evitar a exposição direta do volume de registros armazenados, essa abordagem facilita a correlação de recursos distribuídos e reduz a dependência de mecanismos de geração de chaves fornecidos pelo banco de dados.

Microsserviços

Em arquiteturas baseadas em microsserviços, é comum que diferentes aplicações precisem compartilhar uma mesma identidade de negócio.

Imagine um processo de criação de pedidos envolvendo múltiplos serviços independentes:

Quando um novo pedido é criado, o sistema gera um único ULID:

Esse identificador passa então a acompanhar toda a operação entre os diversos serviços da arquitetura.

Embora cada microsserviço possua sua própria responsabilidade e até mesmo seu próprio banco de dados, todos permanecem vinculados à mesma operação através do identificador compartilhado.

Essa estratégia simplifica significativamente atividades de observabilidade, auditoria e troubleshooting. Ao localizar um único ULID em logs, dashboards ou ferramentas de monitoramento, torna-se possível reconstruir toda a jornada da operação através dos diferentes componentes da arquitetura.

Além disso, como o identificador é gerado pela própria aplicação e não pelo banco de dados, cada serviço pode criar novas entidades, eventos e mensagens sem depender de uma fonte centralizada de geração de chaves. Esse desacoplamento é um dos fatores que tornam o ULID particularmente interessante para arquiteturas baseadas em microsserviços.

Arquiteturas orientadas a eventos

Sistemas orientados a eventos costumam produzir uma grande quantidade de mensagens, notificações e eventos de domínio. Nesses cenários, a correlação entre eventos é tão importante quanto a própria informação transportada.

Imagine o fluxo completo de um pedido em uma aplicação de comércio eletrônico.

No momento em que o pedido é criado, um ULID é gerado pela aplicação:

Esse identificador passa então a acompanhar todos os eventos produzidos ao longo do processo.

Visualmente, o fluxo poderia ser representado da seguinte forma:


OrderId:
01K9V2S1D4N8A6TX6S4QG7M1ZJ

 

Quando um Event Bus é utilizado, cada evento pode ser consumido por múltiplos componentes simultaneamente.

Todos esses consumidores recebem o mesmo identificador de negócio:

Isso permite correlacionar facilmente logs, mensagens, métricas e eventos produzidos por diferentes partes da aplicação.

Em uma implementação Delphi baseada em Event Bus, o identificador poderia ser transportado diretamente pelos eventos de domínio.

Dessa forma, qualquer assinante do evento passa a compartilhar o mesmo contexto de negócio, simplificando rastreamento, auditoria e observabilidade.

Essa capacidade de correlacionar eventos distribuídos é uma das razões pelas quais ULIDs são frequentemente utilizados em arquiteturas modernas baseadas em Event Bus, Domain Events, mensageria e microsserviços.

Domain Events e rastreabilidade

Outro cenário extremamente interessante envolve a utilização de ULIDs em Domain Events.

Ao registrar eventos de domínio contendo identificadores temporalmente ordenáveis, torna-se possível reconstruir facilmente a linha do tempo de uma operação de negócio.

Como os identificadores preservam a ordem cronológica, muitas vezes é possível analisar a sequência dos acontecimentos apenas observando os próprios ULIDs.

Sistemas distribuídos e processamento assíncrono

Processos assíncronos, filas de mensagens, workers e tarefas em segundo plano também se beneficiam dessa abordagem. Como o identificador não depende de uma conexão ativa com o banco de dados, qualquer componente da arquitetura pode gerar novos registros ou mensagens sem criar gargalos centralizados.

Essa característica se torna especialmente relevante em aplicações de alta disponibilidade, ambientes em nuvem e sistemas que precisam escalar horizontalmente.

Aplicações corporativas modernas

Os benefícios do ULID tornam-se ainda mais evidentes quando observamos aplicações corporativas reais, onde uma única operação de negócio costuma atravessar diversos componentes da arquitetura.

Imagine uma plataforma de comércio eletrônico responsável por processar pedidos, pagamentos, faturamento, notificações e logística.

No momento em que o pedido é criado, a aplicação gera um único ULID:

Esse identificador passa então a acompanhar toda a jornada da operação.

Visualmente, a operação poderia ser representada da seguinte forma:

OrderId:
01K9V2S1D4N8A6TX6S4QG7M1ZJ

Observe que todos os componentes compartilham exatamente o mesmo identificador. Isso simplifica drasticamente tarefas de monitoramento, auditoria, troubleshooting e observabilidade.

Caso uma falha seja reportada por um cliente, basta localizar o ULID correspondente para reconstruir toda a operação através de logs, eventos, registros de banco de dados e mensagens processadas pelos diversos serviços da plataforma.

É justamente por esse motivo que ULIDs têm sido cada vez mais adotados em aplicações corporativas modernas, especialmente em cenários que combinam:

  • APIs RESTful;
  • Microsserviços;
  • Arquiteturas orientadas a eventos;
  • Event Bus e mensageria;
  • Domain Events;
  • Processamento assíncrono;
  • Sistemas SaaS multi-tenant;
  • Plataformas distribuídas em nuvem.

Nesses ambientes, o ULID deixa de ser apenas uma chave primária e passa a atuar como um elemento central de correlação, rastreabilidade e integração entre todos os componentes da arquitetura.

Por que considerar ULIDs em novos projetos?

Durante muitos anos o UUIDv4 foi a escolha natural para aplicações que precisavam gerar identificadores únicos sem depender de sequências ou auto incrementos do banco de dados. Sua capacidade de produzir valores praticamente impossíveis de colidir tornou-o uma solução extremamente popular em sistemas distribuídos, APIs RESTful e aplicações corporativas.

Entretanto, a evolução das arquiteturas modernas trouxe novos desafios que vão além da simples unicidade. Atualmente, aplicações precisam lidar com grandes volumes de dados, múltiplos serviços independentes, processamento assíncrono, arquiteturas orientadas a eventos e ambientes altamente escaláveis. Nesse contexto, a forma como os identificadores são gerados passou a influenciar diretamente aspectos como desempenho, rastreabilidade e manutenção da solução.

É justamente nesse cenário que o ULID se destaca.

Ao contrário do UUIDv4, que é composto essencialmente por dados aleatórios, o ULID incorpora um timestamp em sua estrutura. Isso permite que os identificadores sejam naturalmente ordenados de acordo com o momento em que foram criados, sem abrir mão da unicidade global.

Essa característica produz benefícios imediatos.

Melhor organização dos índices

Em bancos de dados relacionais, UUIDs aleatórios tendem a espalhar registros por diferentes regiões dos índices, aumentando fragmentação, page splits e reorganizações internas.

Com ULIDs, registros criados em sequência também tendem a permanecer próximos fisicamente dentro dos índices.

O resultado é uma estrutura de dados mais organizada e um comportamento mais previsível à medida que a base cresce.

Rastreabilidade nativa

Outro benefício importante está relacionado à observabilidade da aplicação.

Ao analisar logs, eventos ou registros de auditoria, um UUIDv4 não fornece qualquer informação adicional além da unicidade.

Já um ULID carrega implicitamente o momento de sua geração.

Isso permite identificar rapidamente a ordem cronológica dos acontecimentos sem necessidade de consultas adicionais, simplificando investigações, auditorias e diagnósticos operacionais.

Melhor integração com arquiteturas modernas

Aplicações modernas raramente são compostas por um único sistema executando em um único servidor.

Hoje é comum encontrar arquiteturas formadas por APIs RESTful, microsserviços, filas de mensagens, Event Bus, processamento assíncrono, workers e múltiplos bancos de dados. Nesses cenários, a simples geração de identificadores únicos deixa de ser suficiente. É necessário que esses identificadores também sejam facilmente compartilhados, rastreados e correlacionados entre diferentes componentes.

Imagine uma operação simples de criação de pedidos.

No momento em que a requisição chega à API, a aplicação gera um ULID:

Esse identificador passa então a acompanhar toda a operação.

Visualmente, o fluxo poderia ser representado da seguinte forma:

OrderId:
01K9V2S1D4N8A6TX6S4QG7M1ZJ

Observe que nenhum componente precisou consultar o banco de dados para obter um identificador. O ULID foi criado logo no início da operação e propagado naturalmente por toda a arquitetura.

Essa característica traz benefícios importantes:

  • Elimina dependência de sequences centralizadas;
  • Permite geração de identificadores antes da persistência;
  • Facilita a correlação de logs e eventos;
  • Simplifica integrações entre microsserviços;
  • Melhora observabilidade e auditoria;
  • Favorece arquiteturas distribuídas e escaláveis.

Além disso, como o ULID incorpora um componente temporal, torna-se possível analisar a evolução das operações apenas observando os próprios identificadores, característica particularmente útil em sistemas orientados a eventos e ambientes de alta concorrência.

Por esse motivo, ULIDs são frequentemente adotados em arquiteturas modernas que combinam APIs RESTful, Event Bus, Domain Events, mensageria e microsserviços, onde a capacidade de gerar identificadores distribuídos e rastreáveis representa uma vantagem significativa em relação aos UUIDs tradicionais.

Maior legibilidade

Embora identificadores não sejam criados para serem interpretados por usuários finais, eles aparecem constantemente em logs, dashboards, sistemas de monitoramento, ferramentas de observabilidade, tickets de suporte e processos de auditoria.

É justamente nesses cenários que a legibilidade do identificador passa a ter valor.

Considere um ambiente corporativo onde uma equipe de suporte precisa localizar uma operação específica reportada por um cliente.

O sistema registra o seguinte identificador utilizando UUIDv4:

Agora observe o mesmo cenário utilizando ULID:

Embora ambos representem identificadores únicos, o ULID apresenta algumas características que facilitam sua utilização operacional:

  • Possui menos caracteres;
  • Não utiliza separadores;
  • É naturalmente ordenável;
  • Utiliza Base32 Crockford, eliminando caracteres ambíguos;
  • Permite identificar aproximadamente quando foi gerado.

Essa diferença torna-se mais evidente quando analisamos registros de log.

Agora observe o mesmo fluxo utilizando ULID:

Em um ambiente com milhares de operações por minuto, localizar e correlacionar eventos torna-se significativamente mais simples quando os identificadores mantêm uma relação temporal natural.

Considere os seguintes registros:

Mesmo sem consultar o banco de dados, é possível perceber que os identificadores foram gerados em sequência.

Com UUIDv4 isso não acontece:

A ordem visual dos valores não fornece qualquer indicação sobre a cronologia dos eventos.

Essa característica faz com que ULIDs sejam particularmente interessantes para:

  • Logs distribuídos;
  • Ferramentas de observabilidade;
  • Dashboards operacionais;
  • Auditoria corporativa;
  • Event Bus e mensageria;
  • Correlation IDs;
  • Microsserviços.

Portanto, quando falamos em legibilidade, não estamos nos referindo apenas à aparência do identificador. Estamos falando da capacidade de localizar, correlacionar e compreender operações complexas de forma mais rápida, característica que se torna extremamente valiosa em aplicações corporativas modernas.

Pensando no futuro

Isso não significa que UUIDv4 seja uma tecnologia ultrapassada. Ele continua sendo uma solução sólida e amplamente utilizada em inúmeras aplicações.

No entanto, quando um novo projeto está sendo iniciado, vale a pena questionar se a simples geração de identificadores aleatórios é suficiente para atender às necessidades futuras da arquitetura.

Se o sistema possui potencial de crescimento, integração com múltiplos serviços, processamento distribuído ou necessidade de rastreabilidade avançada, o ULID oferece vantagens que vão muito além da unicidade. Ele combina ordenação temporal, portabilidade, escalabilidade e simplicidade operacional em uma única solução.

Por esse motivo, muitos arquitetos e desenvolvedores têm passado a enxergar o ULID não apenas como uma alternativa ao UUIDv4, mas como uma evolução natural para aplicações modernas que precisam crescer de forma sustentável e preparada para os desafios da computação distribuída.

Demonstrando na prática as vantagens do ULID no PostgreSQL

Até aqui vimos que o ULID combina unicidade global com ordenação temporal. Entretanto, essa vantagem fica ainda mais clara quando observamos seu comportamento dentro de um banco de dados relacional como o PostgreSQL.

Para isso, vamos comparar dois cenários simples: uma tabela utilizando UUIDv4 e outra utilizando ULID como chave primária textual.

No primeiro exemplo, criaremos uma tabela utilizando UUIDv4. Para gerar os identificadores, podemos utilizar a extensão pgcrypto, que disponibiliza a função gen_random_uuid().

Código SQL para criação da tabela utilizando UUIDv4:

Agora criaremos uma tabela equivalente utilizando ULID. Como o ULID possui 26 caracteres, podemos armazená-lo em uma coluna CHAR(26)

.

Código SQL para criação da tabela utilizando ULID:

Para fins didáticos, vamos inserir alguns registros simulando identificadores gerados em sequência pela aplicação.

Código SQL para inserção de registros utilizando ULID:

Ao ordenar os registros pela chave primária, o PostgreSQL tende a preservar a ordem cronológica dos identificadores gerados, uma vez que o componente temporal está presente no início da estrutura do ULID.

Código SQL para consulta ordenada por ULID:

O resultado será semelhante a este:

Resultado esperado da consulta ordenada por ULID:

Essa característica é extremamente útil porque o identificador passa a carregar uma relação natural com o tempo. Mesmo sem utilizar diretamente a coluna created_at, a ordenação textual dos ULIDs preserva a sequência cronológica dos registros.

Em uma tabela baseada em UUIDv4, esse comportamento não existe. Como os identificadores são gerados aleatoriamente, ordenar pela chave primária não reflete a ordem de criação dos registros.

Código SQL para consulta ordenada por UUIDv4:

O resultado será ordenado alfabeticamente pelo UUID, mas essa ordenação não terá relação direta com o momento da inserção.

Na prática, isso afeta também a forma como os índices crescem. Índices B-Tree, utilizados por padrão em chaves primárias no PostgreSQL, funcionam melhor quando novos valores tendem a ser inseridos próximos ao final da estrutura. Com UUIDv4, os novos registros são distribuídos aleatoriamente pelo índice. Com ULID, os valores gerados em sequência tendem a permanecer agrupados.

Para observar isso em um volume maior de dados, podemos criar uma função simples para inserir registros de teste. No caso do UUIDv4:

Código SQL para geração de massa de dados utilizando UUIDv4:

Para a tabela com ULID, considerando que os identificadores seriam gerados pela aplicação Delphi, podemos inserir uma sequência simulada apenas para demonstrar a ordenação textual:

Código SQL para geração de massa de dados simulando ULIDs sequenciais:

Após as inserções, podemos consultar o tamanho dos índices.

Código SQL para análise do tamanho dos índices:

Também podemos verificar o plano de execução de uma consulta ordenada.

Código SQL para análise do plano de execução:

Em cenários reais, a diferença entre UUIDv4 e ULID tende a aparecer com mais clareza em tabelas grandes, com alto volume de escrita e índices muito utilizados. O ganho não está apenas na consulta em si, mas principalmente na forma como o índice cresce ao longo do tempo.

Outro benefício prático aparece em APIs e logs. Ao utilizar ULIDs como identificadores públicos, a aplicação consegue expor chaves não sequenciais sem perder a capacidade de ordenação.

Exemplo de utilização de ULIDs em endpoints RESTful:

Dessa forma, o ULID oferece três benefícios práticos ao mesmo tempo: evita a previsibilidade das sequences, preserva a geração distribuída dos UUIDs e mantém uma ordenação temporal que favorece bancos de dados relacionais.

É importante observar que o exemplo acima é propositalmente didático. Em uma aplicação real, os ULIDs devem ser gerados pela aplicação ou por uma extensão específica no PostgreSQL, como pgx_ulid, garantindo que o formato siga corretamente a especificação.

Quando aplicado corretamente, o ULID se torna uma alternativa muito interessante para chaves primárias em sistemas modernos, especialmente em cenários que combinam PostgreSQL, APIs RESTful, microsserviços, processamento assíncrono e arquiteturas orientadas a eventos.

Quando NÃO utilizar ULID

Embora o ULID seja uma excelente alternativa para sistemas modernos que exigem identificadores únicos, distribuídos e ordenáveis, isso não significa que ele seja a melhor escolha para todos os cenários. Como qualquer decisão arquitetural, sua adoção deve considerar os requisitos reais da aplicação e o ecossistema tecnológico em que ela será utilizada.

Uma das situações em que o ULID pode não trazer benefícios significativos é em aplicações pequenas ou monolíticas que utilizam bancos de dados centralizados e possuem baixo volume de registros. Nesses casos, chaves inteiras auto incrementais continuam sendo uma solução simples, eficiente e amplamente suportada, sem a complexidade adicional associada à geração de identificadores distribuídos.

Outro cenário importante envolve aplicações que dependem fortemente de tipos UUID nativos fornecidos pelo banco de dados. PostgreSQL, SQL Server e diversos frameworks ORM já oferecem suporte completo a UUIDs, incluindo funções de geração, operadores específicos e otimizações de armazenamento. Quando a interoperabilidade com essas ferramentas é um requisito essencial, a adoção de UUIDv7 pode representar uma alternativa mais natural, já que mantém compatibilidade com o padrão UUID e, ao mesmo tempo, oferece ordenação temporal.

Também é importante considerar ambientes altamente regulados ou que possuam requisitos rigorosos de segurança e rastreabilidade. Embora o ULID não exponha informações sensíveis, parte de sua estrutura contém um timestamp embutido, permitindo inferir aproximadamente o momento em que o identificador foi gerado. Em determinados contextos, essa característica pode não ser desejável, tornando identificadores completamente aleatórios uma opção mais adequada.

Da mesma forma, projetos legados amplamente consolidados raramente justificam uma migração exclusiva para ULIDs. Alterar o formato das chaves primárias pode exigir mudanças em bancos de dados, integrações externas, APIs, sistemas de auditoria e processos de replicação. Nesses casos, os benefícios obtidos podem não compensar o custo e o risco da migração.

Por fim, é importante lembrar que o ULID não substitui automaticamente todas as estratégias de identificação existentes. Seu principal objetivo é resolver problemas relacionados à geração distribuída de identificadores únicos com ordenação temporal. Quando esses requisitos não existem, sua utilização pode apenas adicionar complexidade desnecessária à solução.

Em outras palavras, o ULID deve ser encarado como uma ferramenta arquitetural especializada. Quando aplicado aos cenários corretos — como microsserviços, APIs RESTful, sistemas distribuídos, arquiteturas orientadas a eventos e aplicações de alta escalabilidade — seus benefícios são evidentes. Fora desses contextos, soluções mais simples podem continuar sendo a escolha mais adequada.

Utilizando ULIDs como Correlation IDs em arquiteturas distribuídas

Além de servir como chave primária para entidades de negócio, o ULID também pode ser utilizado como um excelente Correlation ID em arquiteturas distribuídas.

Um Correlation ID é um identificador compartilhado entre múltiplos componentes da aplicação com o objetivo de rastrear uma mesma operação de ponta a ponta. Em sistemas compostos por APIs, microsserviços, filas de mensagens, Event Bus e processos assíncronos, essa prática é fundamental para observabilidade, auditoria e diagnóstico de problemas.

Imagine um processo simples de criação de pedidos.

Um cliente realiza uma compra através de uma API RESTful e o sistema gera o seguinte ULID:

A partir desse momento, o mesmo identificador pode acompanhar toda a jornada da operação.

Embora cada evento possua suas próprias informações, todos permanecem vinculados à mesma operação de negócio através do identificador compartilhado.

Esse conceito se torna ainda mais valioso em arquiteturas orientadas a eventos. Quando um Event Bus é utilizado para desacoplar componentes da aplicação, múltiplos consumidores podem processar o mesmo evento simultaneamente.

Ao propagar o mesmo Correlation ID entre todos os eventos gerados, torna-se possível reconstruir facilmente o fluxo completo da operação, mesmo quando ela atravessa diferentes processos, serviços ou servidores.

Uma implementação simples poderia ser representada pela seguinte interface:

Os eventos concretos passam então a transportar o mesmo identificador.

No momento da criação da operação, um único ULID é gerado:

E esse mesmo valor pode ser propagado para todos os eventos subsequentes.

Essa abordagem oferece diversas vantagens operacionais:

A utilização de ULIDs como Correlation IDs é particularmente interessante porque o identificador já possui ordenação temporal embutida. Isso significa que, além de correlacionar eventos relacionados, ele também fornece uma referência temporal natural para análise e investigação de fluxos de negócio.

Em sistemas modernos baseados em APIs RESTful, Event Bus, Domain Events, mensageria e microsserviços, essa prática costuma representar um dos maiores ganhos operacionais obtidos com a adoção de identificadores distribuídos, tornando o ULID muito mais do que uma simples chave primária: ele passa a ser um elemento central de rastreabilidade em toda a arquitetura.

Beleza, e agora?

ULID é algo novo e que muita gente ainda não conhece. Como sempre gosto de ressaltar, é importante ter em mente que, independente do projeto, as escolhas feitas no seu início determinam o rumo que ele toma e como podemos agir para ampliar e manter sua escalabilidade. Aplicar ULID em um novo projeto não requer muito, porém, na maioria dos casos, se a equipe não for madura o suficiente ou não estiver disposta a entender coisas novas, de nada adinatará.

Mas, confiem em mim quando digo, este é um caminho sem volta. Uma vez que dominamos essa técnica para criar chaves primárias e utiliza-las para manter nossos dados com chaves únicas, independente de sua origem, nunca mais usaremos sequences

Sobre Willian Tuttoilmondo 16 Artigos
Arquiteto de software com mais de 25 anos de experiência em desenvolvimento de software, especialista em desenvolvimento multicamadas utilizando Embarcadero Delphi e NodeJS para o back-end e o próprio Delphi para o front-end. Usuário Linux desde 1998, é evangelista PostgreSQL, banco de dados no qual é especialista. Ocupa hoje a posição de arquiteto de software na TOTVS, a maior empresa de tecnologia do Brasil, além de ser sócio fundador da LT Digital Labs, empresa especializada em desenvolvimento e treinamentos.

Seja o primeiro a comentar

Faça um comentário

Seu e-mail não será publicado.


*


Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.