Escolhendo um formato de ID: UUID, ULID, Snowflake ou autoincremento
Quase todo sistema precisa dar IDs únicos às coisas, e a escolha tem consequências que aparecem bem mais tarde — no desempenho do banco de dados, em se os IDs vazam informação e na facilidade com que serviços diferentes podem gerá-los sem colidir. Este guia compara os formatos comuns e dá uma forma de escolher com intenção, não por hábito.
Inteiros autoincrementais
O ID clássico de banco de dados: 1, 2, 3, e assim por diante, distribuídos pelo banco. São minúsculos, rápidos de indexar e naturalmente ordenados por criação. Para um único banco de dados, costuma ser tudo o que você precisa.
Suas fraquezas aparecem nas bordas. Só o banco central pode cunhá-los, então serviços independentes ou clientes offline não conseguem gerar IDs com antecedência. Eles também vazam informação: um concorrente pode ler seu número de nota fiscal para estimar quantos pedidos você tem, e IDs sequenciais numa URL convidam a adivinhar registros vizinhos.
UUID versão 4 (aleatório)
Um UUID v4 são 122 bits aleatórios, normalmente escritos como 36 caracteres. Qualquer um, em qualquer lugar, pode gerar um sem coordenação e com chance de colisão praticamente nula, o que os torna ideais para sistemas distribuídos, chaves amigáveis a merges e identificadores públicos que não deveriam ser adivinháveis.
O custo é tamanho e ordenação. São muito maiores que um inteiro e, por serem totalmente aleatórios, não são ordenados por tempo — inserir chaves aleatórias num índice B-tree típico espalha as escritas e pode prejudicar o desempenho do banco em escala. Também não carregam informação embutida, o que às vezes é vantagem e às vezes limitação.
IDs ordenados por tempo: UUID v7, ULID e Snowflake
Uma família mais nova resolve o problema da ordenação colocando uma marca de tempo nos bits iniciais, de modo que IDs gerados depois ordenam após os anteriores sem deixar de ser globalmente únicos. O UUID v7 faz isso dentro do formato padrão de UUID. O ULID faz o mesmo numa codificação compacta de 26 caracteres, amigável a URLs e insensível a maiúsculas. Os Snowflake (popularizados pelo Twitter) empacotam uma marca de tempo, um ID de máquina e um contador por milissegundo num inteiro de 64 bits.
A ordenação temporal dá inserções amigáveis ao índice e ordenação cronológica de graça, sem uma autoridade central. O trade-off é que agora a hora de criação fica visível no ID — geralmente inofensivo, ocasionalmente algo que você preferiria não expor.
O que a escolha realmente afeta
Quatro propriedades se movem juntas ao escolher um formato, e ajuda pesá-las explicitamente.
- Coordenação: qualquer nó pode gerar IDs sozinho (UUID/ULID/Snowflake) ou só um banco central (autoincremento)?
- Ordenação: os IDs podem ser ordenados por hora de criação (autoincremento, v7, ULID, Snowflake) ou não (UUID v4)?
- Tamanho e custo de índice: inteiros são os menores; UUIDs aleatórios são os maiores e menos amigáveis ao índice.
- Vazamento de informação: inteiros sequenciais revelam volume e convidam à enumeração; UUIDs aleatórios não revelam nada; IDs ordenados por tempo revelam a marca de tempo.
Um padrão razoável
Se você tem um único banco e não expõe os IDs publicamente, inteiros autoincrementais são simples e eficientes. Se os IDs são públicos, ou gerados por muitos serviços, ou usados como slugs de URL, recorra a um UUID — v7 ou ULID quando quiser que sejam aproximadamente ordenados por tempo para a eficiência do banco, v4 quando quiser especificamente que não revelem nada.
Seja o que for que escolher, seja consistente dentro de um sistema e evite misturar formatos para o mesmo tipo de entidade. A pior estratégia de ID costuma ser a escolhida por acaso.