Skip to content
Voltar ao blog
Tutoriais

O que é um ULID? Guia do identificador ordenável

O que é um ULID? Como funciona o identificador ordenável de 128 bits: estrutura de timestamp mais aleatoriedade, codificação Base32 e quando preferi-lo a um UUID.

12 min de leitura

O que é um ULID? O identificador ordenável, explicado

Todo UUIDv4 aleatório que você insere como chave primária cai em um ponto imprevisível do índice do banco de dados. Faça isso alguns milhões de vezes e o índice se fragmenta, o cache começa a brigar por espaço e as gravações desaceleram. Um ULID resolve isso sem abrir mão do que você gostava nos UUIDs: você ainda pode gerar um em qualquer lugar, sem coordenador central, mas ele cai em ordem cronológica em vez de se espalhar.

Então como uma string de 26 caracteres se ordena sozinha por tempo? É o truque central do formato, e compensa entendê-lo antes de adotar um.

Um ULID (Universally Unique Lexicographically Sortable Identifier) é um identificador de 128 bits escrito como 26 caracteres em Crockford Base32. Os primeiros 10 caracteres codificam um timestamp em milissegundos e os últimos 16 codificam bits aleatórios, de modo que ULIDs criados depois sempre se ordenam após os anteriores quando comparados como strings simples. É um identificador único ordenável que você pode gerar offline.

Este guia desmonta tudo isso: a anatomia decodificada caractere por caractere, a prova de que ele realmente ordena, a matemática de B-tree por trás da vitória no banco de dados e um olhar honesto sobre o que o timestamp embutido revela. Você pode acompanhar com um valor ao vivo no gerador de ULID — gere um, decodifique-o, converta-o em um UUID — enquanto lê.

O que é um ULID?

Um ULID (Universally Unique Lexicographically Sortable Identifier) é um identificador de 128 bits projetado como uma alternativa mais ordenável e mais compacta ao UUID. Ele é escrito como 26 caracteres em Crockford Base32: os primeiros 10 guardam um timestamp de 48 bits em milissegundos desde o Unix epoch, e os 16 restantes guardam 80 bits de aleatoriedade. Como o tempo vem primeiro, a string se ordena cronologicamente.

Essa última propriedade é a razão pela qual o formato existe. O UUIDv4 é totalmente aleatório, o que é ótimo para unicidade mas significa que dois IDs criados com um segundo de diferença não têm relação alguma entre si. Os ULIDs mantêm o modelo de geração em qualquer lugar, sem coordenação, e acrescentam ordenação por tempo por cima, de modo que uma coluna deles fica naturalmente ordenada por data de criação sem nada extra.

O formato, em resumo:

PropriedadeValor
Bits128
Codificação26 caracteres em Crockford Base32
Estruturatimestamp de 48 bits + aleatoriedade de 80 bits

O restante deste artigo explica como cada parte funciona. A codificação e a ordenabilidade têm suas próprias seções mais adiante; a estrutura vem primeiro.

Anatomia de um ULID: 48 bits de tempo + 80 bits de aleatoriedade

Os 26 caracteres de um ULID se dividem de forma limpa em duas metades. Os primeiros 10 caracteres são o timestamp; os últimos 16 são a parte aleatória. Coloque o exemplo canônico e o limite fica óbvio:

01ARYZ6S41   TSV4RRFFQ69G5FAV
└────────┘   └──────────────┘
 10 chars        16 chars
48-bit ms      80-bit random
timestamp

Cada metade tem um trabalho: uma registra quando o ULID foi criado, a outra garante a unicidade. Veja como decodificar cada uma.

O timestamp de 48 bits (primeiros 10 caracteres)

Os 10 caracteres iniciais codificam um inteiro de 48 bits: o número de milissegundos desde o Unix epoch no momento em que o ULID foi criado. Pegue o exemplo canônico direto da especificação:

01ARYZ6S41  ->  1469918176385 ms  ->  2016-07-30T22:36:16.385Z

Essa é uma decodificação real e reversível: cole 01ARYZ6S41TSV4RRFFQ69G5FAV em um decodificador e você obtém exatamente 2016-07-30T22:36:16.385Z de volta. O componente de tempo é dado puro, não um hash, então lê-lo não custa nada.

Um pequeno detalhe que confunde as pessoas: o primeiro caractere de um ULID fica sempre entre 0 e 7. Um caractere Crockford guarda 5 bits, e 48 bits não é múltiplo de 5. O timestamp ocupa os 48 bits inferiores dos 50 bits que 10 caracteres conseguem carregar, deixando os 2 bits superiores do primeiro caractere permanentemente em zero. Dois bits zero limitam o valor desse caractere a 7. Se você ver um ULID começando com 8 ou mais, ele está malformado.

Os 80 bits de aleatoriedade (últimos 16 caracteres)

Os 16 caracteres restantes carregam 80 bits de aleatoriedade, e é nesta metade que mora a unicidade. Os bits devem vir de uma fonte criptograficamente segura — crypto.getRandomValues no navegador, não Math.random. A diferença importa: Math.random é previsível o suficiente para um atacante adivinhar ou colidir valores, ao passo que um CSPRNG não é.

Quanto espaço são 80 bits? Cerca de 1.2 × 10²⁴ valores possíveis, e isso é por milissegundo. Mesmo que você gere milhões de ULIDs dentro de um único milissegundo, as chances de dois sortearem os mesmos 80 bits permanecem ínfimas. Diferentemente do timestamp, esta metade não carrega nenhum significado decodificável — é ruído cujo único propósito é tornar cada ULID distinto.

Crockford Base32: por que os ULIDs descartam I, L, O e U

Os ULIDs são codificados com o Crockford Base32, um alfabeto de 32 símbolos: os dígitos 09 e as letras AZ com quatro removidas.

0123456789ABCDEFGHJKMNPQRSTVWXYZ

As letras que faltam são I, L, O e U. Três são descartadas porque parecem dígitos — I e L lembram 1, O lembra 0 — então uma pessoa lendo um ULID na tela não confunde uma letra com um número. O outro lado da moeda é uma entrada tolerante: um decodificador conforme mapeia I e L de volta para 1 e O para 0, e trata a string inteira sem distinção entre maiúsculas e minúsculas. O U é excluído à parte, para evitar que se formem palavras ofensivas por acidente.

A matemática dos bits é o outro motivo. Cada caractere Base32 codifica 5 bits, enquanto um caractere hexadecimal codifica apenas 4. Empacote 128 bits a 5 bits por caractere e você precisa de 26; empacote os mesmos 128 bits a 4 bits cada — do jeito que um UUID faz — e você precisa de 32, mais quatro hifens, totalizando 36 caracteres. Portanto, um ULID é significativamente mais curto do que um UUID e, sem hifens, encaixa direto em uma URL, um nome de arquivo ou um cabeçalho sem precisar de escape.

O Crockford Base32 é um alfabeto de 32 símbolos (09 e AZ menos I, L, O, U) que codifica 5 bits por caractere. Os ULIDs o usam para empacotar 128 bits em 26 caracteres sem distinção de maiúsculas/minúsculas e seguros para URL, e — crucialmente — o alfabeto está em ordem ascendente, que é o que faz a string codificada ordenar-se da mesma forma que os bits brutos.

Por que os ULIDs se ordenam por tempo

Muitos artigos dizem que os ULIDs se ordenam por tempo. Poucos mostram por quê. A explicação se apoia em dois fatos que você já tem: o timestamp é a parte mais significativa do valor, e o alfabeto de Crockford está disposto em ordem ascendente.

Junte os dois e você obtém uma cadeia de equivalências:

string compare  ==  128-bit integer compare  ==  creation-time compare

Comparar dois ULIDs caractere por caractere (do jeito que uma ordenação de string funciona) dá a mesma resposta que comparar seus inteiros de 128 bits subjacentes, porque o alfabeto preserva a ordem: um caractere “maior” sempre significa um valor maior. Comparar os inteiros de 128 bits dá a mesma resposta que comparar datas de criação, porque o timestamp ocupa os bits mais significativos, então domina a comparação; a cauda aleatória só desempata dentro do mesmo milissegundo. Ordem de string, ordem de bits e ordem de tempo são a mesma ordem.

Uma rápida demonstração. Dois ULIDs gerados com um milissegundo de diferença:

01ARYZ6S41...   (created at T)
01ARYZ6S42...   (created at T + 1 ms)

O décimo caractere avança de 1 para 2, e uma ordenação de texto simples coloca o segundo depois do primeiro, sem precisar de uma coluna de timestamp nem de um comparador especial. O ganho prático, que a próxima seção detalha, cabe em uma linha: ORDER BY id retorna as linhas em ordem cronológica sem nenhum índice extra.

ULIDs como chaves primárias de banco de dados: localidade na B-tree

É aqui que os ULIDs mostram seu valor. A maioria dos bancos de dados relacionais armazena o índice de chave primária como uma B-tree, e onde uma nova chave cai nessa árvore decide o quanto a inserção custa.

Um UUIDv4 aleatório cai em algum lugar imprevisível a cada inserção:

UUIDv4: cada nova chave mira uma página folha aleatória. A página costuma estar cheia, então o mecanismo a divide, copia metade das linhas para outro lugar e suja páginas por toda a árvore. Ao longo de milhões de linhas, isso fragmenta o índice, expulsa páginas úteis do buffer cache e derruba a vazão de inserção. (Para os números concretos de divisão de páginas do índice — tipicamente uma diferença de 2–10× em tabelas com muita gravação — veja o guia de comparação.)

Um ULID com prefixo de tempo cai no fim toda vez:

ULID: como os bits altos são um timestamp, cada nova chave é maior que a anterior, então ela é anexada na borda direita do índice, ou perto dela. As inserções permanecem sequenciais, as divisões de página quase desaparecem, o índice se mantém compacto e uma varredura de intervalo sobre uma janela de tempo lê uma sequência contígua de páginas.

Você ganha a geração sem coordenação de um UUID com a localidade de inserção de um inteiro auto-incrementável, sem expor um contador sequencial adivinhável, já que a cauda aleatória ainda esconde o próximo valor exato.

Dica de armazenamento: guarde os 128 bits como 16 bytes binários — uma coluna uuid no PostgreSQL, BINARY(16) no MySQL — e não como um campo de texto de 26 caracteres, que desperdiça espaço e incha o índice. Codifique para a string Base32 só nas bordas onde uma pessoa ou uma URL vê o valor. A aba Converter do gerador vai converter um ULID em um UUID exatamente para isso, já que as duas formas são os mesmos 128 bits.

ULIDs monotônicos: ordem estrita dentro de um milissegundo

A prova da ordenabilidade tem uma lacuna honesta: dentro de um único milissegundo, ULIDs simples não ficam estritamente ordenados. Eles compartilham o mesmo prefixo de tempo de 10 caracteres, mas suas caudas aleatórias de 80 bits são sorteadas de forma independente, então qual de dois ULIDs do mesmo milissegundo ordena primeiro é essencialmente cara ou coroa. Para a maioria dos usos, isso não é problema. Quando você precisa de ordem estrita mesmo em taxas inferiores a um milissegundo, é.

A geração monotônica fecha a lacuna. A regra é simples: o primeiro ULID em um dado milissegundo recebe aleatoriedade nova como de costume, e cada ULID posterior nesse mesmo milissegundo é produzido pegando o valor aleatório anterior de 80 bits e incrementando-o em um (tratado como um inteiro big-endian, propagando o vai-um para bits superiores conforme necessário). Cada valor é, portanto, estritamente maior que o anterior.

Você pode ver isso em um lote gerado dentro de um milissegundo — só o caractere final muda:

01KVT0F720ZK9N4T2QX7VR8WMC
01KVT0F720ZK9N4T2QX7VR8WMD
01KVT0F720ZK9N4T2QX7VR8WME

…WMC < …WMD < …WME, garantido. Isso importa sempre que linhas podem ser criadas mais rápido do que o relógio de milissegundos avança: inserções de alta vazão, logs de eventos, IDs de mensagens em um laço apertado. Quando o relógio avança para o próximo milissegundo, a geração volta para aleatoriedade nova e o ciclo se repete.

ULID vs UUID: quando usar cada um

A pergunta com que a maioria das pessoas realmente chega é ULID vs UUID. A comparação a seguir é focada: ULID contra as duas versões de UUID que você de fato pesaria contra ele. (Para a matriz completa de decisão entre cinco opções, incluindo Snowflake e NanoID, veja a comparação completa de ULID, UUID e Snowflake.)

PropriedadeULIDUUIDv4UUIDv7
Comprimento26 chars36 chars36 chars
CodificaçãoCrockford Base32Hex com hifensHex com hifens
Ordenável por tempo?SimNãoSim
Embute timestamp?Sim (48-bit ms)NãoSim (48-bit ms)
Padronizado?Spec da comunidadeRFC 9562RFC 9562
Melhor paraIDs curtos e ordenáveisIDs opacos e aleatóriosIDs ordenáveis no formato UUID

Em prosa: recorra a um ULID quando quiser a string mais curta, segura para URL e ordenável. Recorra ao UUIDv4 quando quiser um identificador opaco, totalmente aleatório, sem tempo embutido — por exemplo, um token público em que você prefere não revelar quando ele foi criado. Recorra ao UUIDv7 quando precisar de ordenação por tempo mas tiver que permanecer dentro do formato UUID padrão, com os bits de versão e variante em suas posições fixas e uma coluna uuid nativa onde encaixá-lo.

Os três têm 128 bits, então a conversão ULID ↔ UUID é sem perdas nos dois sentidos. A relação entre ULID e ulid vs uuid v7 é mais próxima do que parece: o UUIDv7 é essencialmente a versão padronizada pelo IETF da mesma ideia de prefixo de tempo que o ULID inaugurou. Se você é novo nos UUIDs de modo geral, comece pelos fundamentos primeiro, depois volte a esta comparação.

O dilema de privacidade: ULIDs revelam sua data de criação

O timestamp embutido é tanto um recurso quanto um vazamento, dependendo de quem lê o ID. Qualquer pessoa que tenha um ULID pode decodificar o timestamp em um passo e descobrir o milissegundo exato em que o registro foi criado — sem precisar de acesso ao seu banco de dados.

Dentro dos seus próprios sistemas isso é só vantagem: dá para auditar e depurar registros pelo horário de criação sem nenhuma coluna extra. Em um identificador exposto ao público, vira uma divulgação real. A data de criação pode ser sensível para o negócio por si só, e um punhado de ULIDs amostrados ao longo do tempo revela sua taxa de criação (quantos pedidos, contas ou mensagens você gera por segundo), justamente o tipo de coisa que concorrentes e scrapers gostam de estimar.

Para ser justo, esse é um vazamento mais estreito do que o do UUIDv1, que historicamente embutia o endereço MAC da máquina geradora; um ULID expõe apenas o tempo, nunca a identidade do hardware. Ainda assim, pondere. A mitigação simples: mantenha os ULIDs internos e entregue um UUIDv4 totalmente aleatório para os IDs expostos ao público, onde a ordenação não importa.

Armadilhas comuns com ULIDs

A maioria dos problemas com ULID é um punhado de decisões de engenharia evitáveis, não bugs no formato. As recorrentes:

  • Supor que ULIDs simples do mesmo milissegundo estão ordenados. Eles compartilham um prefixo de tempo mas têm caudas aleatórias independentes, então sua ordem é indefinida. Solução: use o modo monotônico quando precisar de ordem estrita em taxas inferiores a um milissegundo.
  • Armazenar um ULID como texto de 26 caracteres. Isso desperdiça espaço e infla o índice. Solução: guarde os 128 bits como 16 bytes (uuid / BINARY(16)) e codifique para Base32 só nas bordas.
  • Esperar que uma conversão ULID→UUID se reporte como v4 ou v7. A conversão recodifica os mesmos bits; ela não define os campos de versão e variante do UUID, então uma biblioteca que os inspecionar não verá uma versão marcada. Solução: trate o resultado como um valor opaco de 128 bits, ou gere um UUIDv7 de verdade quando precisar da marcação.
  • Preencher a aleatoriedade com Math.random. Ela é previsível e pode colidir. Solução: use sempre um CSPRNG como crypto.getRandomValues.
  • Expor ULIDs publicamente sem ponderar o vazamento do timestamp. Veja a seção de privacidade acima. Solução: ULIDs internos, UUIDv4 aleatório para IDs públicos.
  • Digitar I, L, O ou U à mão em um ULID. Essas letras não estão no alfabeto, e redigitar convida a erros. Solução: copie os ULIDs, não os redigite.

FAQ

O ULID é um padrão oficial como o UUID?

Não. O ULID é uma especificação comunitária publicada no GitHub, não uma RFC do IETF. Ele é amplamente implementado e estável, mas não há um órgão de padronização por trás dele. Se você precisa de um identificador padronizado e ordenado por tempo, o UUIDv7 (RFC 9562) aplica a mesma ideia dentro do formato UUID oficial.

Quantos caracteres tem um ULID, e por que ele é mais curto que um UUID?

26 caracteres, contra os 36 de um UUID. O ULID usa Crockford Base32, que empacota 5 bits por caractere; o hexadecimal de um UUID empacota apenas 4 bits e adiciona quatro hifens. Os mesmos 128 bits, portanto, precisam de menos caracteres em Base32, e nenhum deles precisa de escape de URL.

Dois ULIDs podem chegar a colidir?

Praticamente nunca. Dentro de um milissegundo, um ULID tem 80 bits aleatórios (cerca de 1.2 × 10²⁴ possibilidades), então, mesmo gerando milhões por milissegundo, as chances de colisão permanecem ínfimas. A única exigência é que um RNG criptograficamente seguro preencha a aleatoriedade; Math.random anula a garantia.

Posso armazenar ULIDs no PostgreSQL ou no MySQL?

Sim. Um ULID tem 128 bits, então converta-o para a forma UUID e armazene-o em uma coluna uuid (PostgreSQL) ou BINARY(16) (MySQL), depois renderize a string Base32 só nas bordas. Não existe um tipo de coluna ULID nativo, mas a representação em UUID custa os mesmos 16 bytes e mantém o índice compacto.

Os ULIDs diferenciam maiúsculas de minúsculas?

A forma canônica é em maiúsculas, mas o Crockford Base32 não diferencia maiúsculas de minúsculas na entrada: um decodificador lê letras minúsculas da mesma forma, e mapeia I/L para 1 e O para 0. Para evitar surpresas em verificações de igualdade e índices, normalize para um único caso antes de armazenar ou comparar.

O timestamp de 48 bits vai se esgotar algum dia?

Não por muito, muito tempo. 48 bits de milissegundos alcançam o ano 10889 antes de o contador estourar, então o componente de timestamp é, na prática, à prova de futuro para qualquer aplicação real. Você vai trocar o sistema, a linguagem e o banco de dados muito antes de o formato ficar sem espaço.

Posso gerar ULIDs no navegador ou no celular sem um servidor?

Sim, e esse é um dos principais benefícios. Os ULIDs não precisam de coordenador central, então qualquer nó, edge worker, navegador ou dispositivo pode gerar um a partir do seu relógio mais um RNG seguro. Valores criados em máquinas diferentes ainda se ordenam juntos por tempo depois, porque o timestamp vive dentro do próprio ID.

Conclusão

Os ULIDs resolvem um problema específico e real, o de chaves aleatórias fragmentando seu índice, sem abrir mão da geração descentralizada. Os pontos a guardar:

  • Um ULID é um timestamp de 48 bits em milissegundos + 80 bits de aleatoriedade, codificado como 26 caracteres em Crockford Base32.
  • Ele se ordena por tempo porque o timestamp é o componente mais significativo e o alfabeto preserva a ordem, de modo que ordem de string é igual a ordem de tempo.
  • Essa ordenação dá a uma B-tree a localidade de inserção que falta a um UUIDv4 aleatório, mantendo as gravações rápidas e o índice compacto.
  • Use o modo monotônico quando precisar de ordem estrita para IDs gerados no mesmo milissegundo.
  • Pondere o vazamento do timestamp antes de expor ULIDs em identificadores voltados ao público.
  • Escolha o UUIDv7 quando tiver que permanecer dentro do formato UUID padrão.

Quando estiver pronto para colocá-lo em prática, abra o gerador de ULID para gerar, decodificar e converter ULIDs inteiramente no seu navegador — sem servidor, sem upload, nada armazenado.

Tags: ulid uuid unique-identifier database primary-key

Artigos relacionados

Ver todos os artigos