Contents

Public IDs (External IDs)


Um resumo sobre alguns tipos de IDs, principalmente os que podem ser expostos de forma segura, sem previsibilidade e preferencialmente que suportem ordenação.

Os principais que serão abordados aqui são: ULID, UUIDv7 e TSID.

Características principais

  • Previsibilidade ou não: se é possível facilmente “chutar” e acertar um outro ID
  • Ordenável ou não: ordenável é bom para índices que utilizam árvores balanceadas
  • Armazenamento: como binário ou texto ou número e o tamanho de cada

ULID

Características

  • Tamanho: 128 bits
  • Ordenável pelo timestamp de geração
  • Ordenável lexicograficamente (“alfabeticamente”)
  • Pode ser armazenado como UUID/GUID por bancos que suportam
  • Pode ser armazenado como uma string de 26 caracteres codificada em Base32 Crockford
  • Pode ser armazenado como um array de 16 bytes (binário)
  • URL-safe, não diferencia maiúsculas de minúsculas e não possui hifens

Anatomia

/posts/2025-05-17-public-ids/_resources/24d62145172bb0db524a3d7067d20337.png

128 bits ao todo. Divididos em:

Timestamp

  • 48 bits
  • Total de milissegundos desde Unix time, ou seja, desde 01/01/1970 em UTC
  • Consegue contar até até o ano de 10889 d.C.

Aleatório

  • 80 bits aleatórios

Depoimentos na internet

  • Aqui no C6 adotamos o ULID, que já resolve o problema do rebalancameanto da btree, já que ele é ordenavel. Além de permitir busca por períodos de data usando a PK, já que ele usa timestamp como parte do identificador

UUIDv7

Características

  • Tamanho: 128 bits
  • Ordenável pelo timestamp de geração
  • Ordenável lexicograficamente (“alfabeticamente”)
  • Pode ser armazenado como UUID/GUID por bancos que suportam (ideal)
  • Pode ser armazenado como uma string de 36 caracteres (288 bits = 36 bytes)
  • Pode ser armazenado como um array de 16 bytes (binário)
  • URL-safe

Anatomia

/posts/2025-05-17-public-ids/_resources/c506dd5554bcf5c62ac0c58a8cd0c06b.png

  • 128 bits ao todo.
  • O timestamp é o total de milissegundos desde Unix time, ou seja, desde 01/01/1970 em UTC
  • O 13º caractere indica o tipo de UUID, no caso 7

Anatomia binária:

/posts/2025-05-17-public-ids/_resources/84249cbc0f962ea4da3a0a384f6c2790.png

Depoimentos na internet

  • Atualmente estou trabalhando em um SaaS multi tenant com milhares de dados e os benchmakrs de escrita diferem bastante quando movemos de uuid v4 para v7

UUID de forma geral

Características

  • Tamanho: 128 bits
  • Pode ser criado na camada da aplicação
  • O 13º caractere indica a versão do UUID (atualmente são 8 versões)
  • Pode ser armazenado como UUID/GUID por bancos que suportam (ideal)
  • Pode ser armazenado como uma string de 36 caracteres (288 bits = 36 bytes)
  • Pode ser armazenado como um array de 16 bytes (binário)
  • Especificação: https://datatracker.ietf.org/doc/html/rfc9562

Depoimentos na internet

  • Uma desvantagem: UUID sendo maior, vc gasta mais storage, o q nao eh mt relevante, mas o seus indexes ficam maiores (pq em geral vc vai ter index em ids), e ae vao caber menos no cache de memoria, e isso pode sim ter um impacto relevante em performance (pq memoria nao eh tao barato assim)

Recomendações

Para novos projetos

  • 🏆 UUIDv7 (if you need time ordering + security)
  • 🎯 UUIDv4 (if pure randomness suffices)

Guia de migração

  • 🔄 v1 → v6/v7 (better privacy/ordering)
  • ⚠️ v3/v5 → v7 + external hashing (security upgrade)

TSID

Características

  • Tamanho: 64 bits
  • Ordenável pelo timestamp de geração
  • Ordenável lexicograficamente (“alfabeticamente”)
  • Pode ser armazenado como um inteiro de 64 bits
  • Pode ser armazenado como uma string de 13 caracteres codificada em Base32 Crockford
  • URL-safe, não diferencia maiúsculas de minúsculas e não possui hifens
  • Menor que UUID, ULID e KSUID

Anatomia

/posts/2025-05-17-public-ids/_resources/19dd2ca2a08046d3d110602b794f2938.png

  • Timestamp de 42 bits ao nível de milissegundos
  • Node + counter sempre tem que somar 22 bits
  • Ajustar o tamanho do node ajusta automaticamente o tamanho do counter
  • O node pode ser definido para 0 bits, o que deixa o counter com 22 bits
  • O contador é incrementado a cada chamada dentro do mesmo milissegundo e ao mudar de milissegundo o contador é definido para um valor aleatório a partir do qual é reiniciada a contagem

Snowflake do Twitter/X

  • 64 bits = 8 bytes

/posts/2025-05-17-public-ids/_resources/8537dbfc2aa8f700613bf481957879b3.png

KSUID

NanoID

Características

  • Sem ordenação
  • Permite personalizar o tamanho do ID

Depoimentos na internet

  • Uso sempre o nanoid com 21 char, na minha opinião é o melhor dos mundos, a colisão aconteceria em ~41 milhões de anos com a inserção de 1.000 ids por segundo.

Tabela comparativa 📊

ImplementationSortableSecurityStorageOracle TypeCollision RiskRFC Status
ULID128b / 26charRAW(16) / CHAR(26)Spec
UUIDv7128b / 36charRAW(16) / VARCHAR2(36)RFC 9562
TSID64b (int) / 20charNUMBER(20) / VARCHAR2(20)Lib
UUIDv1128b / 36charRAW(16) / VARCHAR2(36)RFC 9562
UUIDv2⚠️128b / 36charRAW(16) / VARCHAR2(36)RFC 9562
UUIDv3128b / 36charRAW(16) / VARCHAR2(36)RFC 9562
UUIDv4128b / 36charRAW(16) / VARCHAR2(36)RFC 9562
UUIDv5⚠️128b / 36charRAW(16) / VARCHAR2(36)RFC 9562
UUIDv6128b / 36charRAW(16) / VARCHAR2(36)RFC 9562
UUIDv8128b / 36charRAW(16) / VARCHAR2(36)User-definedRFC 9562
NanoID64b / 21charRAW(8) / VARCHAR2(21)Lib
KSUID160b / 27charRAW(20) / VARCHAR2(27)Spec

Legenda:

  • Sortable: Capacidade de ordenação temporal nativa
  • Security: Resistência a previsibilidade/ataques
  • Storage:
    • Binário (ex: 128b = 16 bytes)
    • Texto (ex: 26char = 26 caracteres)
    • Numérico (ex: 64b (int) = inteiro correspondente a 2^64)
  • Oracle Type: Tipo de dado no Oracle
  • Collision Risk: Probabilidade aproximada de colisão por unidade temporal
  • RFC Status:
    • RFC: Padrão formal
    • Spec: Especificação pública não-RFC
    • Lib: Definido por biblioteca

Consultas / SQL

Algumas dicas de SQL que podem ser úteis.

Verificar no Oracle 19c o tamanho, em bytes, do valor ocupado por uma coluna (não o tamanho declarado pelo tipo da coluna):

1
2
3
4
5
SELECT
    ID_CIDADE, VSIZE(ID_CIDADE) AS TAMANHO_BYTES,
    NOME, VSIZE(NOME) AS NOME_TAMANHO_BYTES
FROM CIDADE
ORDER BY ID_CIDADE desc;

Função to_uuid para exibir o UUID textualmente no Oracle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
CREATE OR REPLACE
FUNCTION to_uuid(p_raw RAW)
RETURN VARCHAR2
IS
BEGIN
  RETURN LOWER(
    REGEXP_REPLACE(
      RAWTOHEX(p_raw),
      '(.{8})(.{4})(.{4})(.{4})(.{12})',
      '\1-\2-\3-\4-\5'
    )
  );
END;
/

Projeto de POC com IDs

POC with UUIDv4, UUIDv7, ULID, TSID etc, with Quarkus, Hibernate and Rest

Referências