O que é o German Tank Problem aplicado a IDs
Durante a Segunda Guerra Mundial, os Aliados precisavam saber quantos tanques a Alemanha produzia. Em vez de confiar só em espionagem, estatísticos analisaram os números de série dos tanques capturados e estimaram a produção total com uma precisão impressionante. Esse método ficou conhecido como German Tank Problem.
O mesmo raciocínio se aplica ao seu sistema. Se o seu banco de dados usa IDs sequenciais (1, 2, 3...), qualquer pessoa que crie duas contas com alguns dias de diferença consegue estimar quantos usuários você ganhou no período. O ID virou um vazamento silencioso de informação de negócio.
Por isso a comunidade de desenvolvimento recomenda o UUID (Universally Unique Identifier) como identificador público. Ele não revela ordem, volume nem ritmo de crescimento, e ainda dificulta ataques que dependem de adivinhar o próximo ID.
Como funciona um UUID
Um UUID é um identificador de 128 bits, normalmente exibido como 36 caracteres no formato 8-4-4-4-12, por exemplo 550e8400-e29b-41d4-a716-446655440000. São 32 dígitos hexadecimais separados por quatro hifens.
O espaço de valores possíveis é gigantesco. Com 122 bits efetivamente aleatórios na versão 4, a chance de duas máquinas gerarem o mesmo valor é tão baixa que, na prática, tratamos como impossível. Por isso você pode gerar UUIDs no próprio código, sem pedir o próximo número ao banco.
O padrão atual é a RFC 9562, publicada em 2024, que substitui a antiga RFC 4122. Ela define várias versões, cada uma com uma estratégia diferente para montar esses 128 bits.
Principais recursos e versões do UUID
Nem todo UUID é igual. As versões mais usadas hoje são:
- UUIDv4: quase totalmente aleatório. É o mais comum e o mais simples de adotar, ótimo para identificadores públicos.
- UUIDv7: começa com um carimbo de tempo em milissegundos e termina com bits aleatórios. O resultado é ordenável por data de criação, o que ajuda muito o índice do banco.
- UUIDv1: baseado em tempo e no endereço de rede da máquina. Caiu em desuso porque podia vazar o endereço MAC.
A grande novidade da RFC 9562 foi justamente o UUIDv7. Ele resolve o principal problema de performance do v4 sem voltar a expor a ordem real de criação de forma legível para um atacante casual.
Além disso, por ter 128 bits, o UUID é perfeito para sistemas distribuídos: cada serviço gera o seu identificador sem coordenação central e sem risco de colisão.
Como começar: usando UUID no seu banco
A boa notícia é que os bancos modernos já têm suporte direto. No PostgreSQL, existe o tipo nativo uuid e a função gen_random_uuid() para gerar valores v4 sem instalar nada.
Um passo a passo simples:
- Passo 1: troque a coluna de chave primária de
serialoubigintpara o tipouuid. - Passo 2: defina um valor padrão com
DEFAULT gen_random_uuid()ou gere o UUID na aplicação. - Passo 3: garanta que a sua linguagem use uma biblioteca confiável de UUID (a maioria das stacks tem suporte nativo).
No MySQL e no SQL Server também dá para usar, geralmente armazenando o valor em formato binário de 16 bytes para economizar espaço. Para UUIDv7, hoje você normalmente gera o valor na aplicação, já que nem todo banco traz uma função pronta.
Exemplo prático: de ID sequencial para UUIDv7
Imagine uma tabela de pedidos com ID sequencial. Um concorrente faz um pedido de teste na segunda e outro na sexta. Se os IDs forem 10250 e 10410, ele sabe que você processou cerca de 160 pedidos na semana. Isso é informação estratégica saindo de graça.
Com UUID, o mesmo pedido teria um identificador como 018f1a2b-7c3d-7e4f-8a9b-0c1d2e3f4a5b. Não dá para inferir volume nem ordem a olho nu. No PostgreSQL, a criação da tabela ficaria assim:
CREATE TABLE pedidos (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), valor numeric, criado_em timestamptz DEFAULT now());
Se você escolher UUIDv7 gerado na aplicação, ganha um bónus: como ele começa pelo tempo, os novos registros são inseridos quase sempre no fim do índice, evitando a fragmentação que o v4 puro causa.
Comparação com alternativas
O UUID não é a única saída. Vale conhecer os concorrentes:
- ULID: 128 bits, ordenável por tempo e escrito em base32, o que gera textos mais curtos e legíveis. Conceito muito parecido com o UUIDv7.
- Snowflake ID: criado pelo Twitter, usa 64 bits com tempo, ID de máquina e sequência. Cabe em um inteiro, mas exige coordenação de IDs de máquina.
- Nano ID: foco em URLs curtas, com tamanho configurável. Ótimo para links públicos, menos comum como chave primária.
A regra mental é simples: se você quer suporte nativo no banco e padrão oficial, use UUID (de preferência v7). Se precisa de IDs menores em 64 bits e já tem infraestrutura para isso, o Snowflake pode valer. Se quer algo curto e bonito na URL, o Nano ID resolve.
Para a maioria dos sistemas brasileiros de pequeno e médio porte, o UUID continua sendo a escolha mais segura e com menos surpresas.
Pontos positivos e limitações
Os ganhos são claros: identificadores que não vazam dados de negócio, geração distribuída sem coordenação, e facilidade para juntar dados de vários bancos sem conflito de chaves.
Mas existem custos reais. Um UUID ocupa 16 bytes, contra 4 ou 8 bytes de um inteiro. Em tabelas com bilhões de linhas e muitas chaves estrangeiras, isso pesa no armazenamento e na memória do índice.
O outro ponto é a performance de escrita do UUIDv4: por ser aleatório, ele espalha as inserções pelo índice e causa divisão de páginas. Esse é exatamente o problema que o UUIDv7 veio resolver, então prefira v7 quando o volume for alto.
Casos de uso reais
Onde o UUID brilha de verdade:
- APIs públicas: quando o ID aparece na URL ou na resposta, o UUID evita que terceiros mapeiem o seu crescimento.
- Sistemas multi-tenant e SaaS: cada cliente gera registros sem risco de colisão entre bases diferentes.
- Apps offline-first e mobile: o aparelho cria o ID localmente e sincroniza depois, sem esperar o servidor.
- Integrações e migrações: juntar dados de vários sistemas fica trivial quando as chaves já são globalmente únicas.
Por outro lado, uma tabela interna de log que ninguém vê de fora e que cresce muito talvez fique melhor com um inteiro sequencial, mais leve. A decisão é por contexto, não por moda.
O ponto de equilíbrio comum é usar UUID na chave pública exposta e manter, se quiser, um inteiro interno só para uso do banco.
Dicas e boas práticas
Quem já usa UUID em produção costuma seguir alguns padrões:
- Prefira UUIDv7 para novas chaves primárias com alto volume de escrita, pela ordenação temporal.
- No banco, armazene como tipo uuid nativo (ou binário de 16 bytes), nunca como texto de 36 caracteres, que desperdiça espaço.
- Não exponha o inteiro sequencial interno em URL ou API ao mesmo tempo, senão o vazamento volta.
- Valide o formato do UUID na entrada para evitar buscas inválidas.
Um erro clássico de iniciante é guardar UUID como VARCHAR(36). Além de ocupar mais que o dobro do necessário, deixa os índices maiores e mais lentos.
Outro cuidado: lembrar que o UUID protege contra adivinhação, mas não substitui autorização. Continue verificando se o usuário pode acessar aquele recurso, mesmo que o ID seja impossível de adivinhar.
Vale a pena migrar para UUID?
Para quem expõe IDs em URLs, APIs ou documentos públicos, a resposta é quase sempre sim. O custo de alguns bytes a mais é pequeno perto do risco de vazar volume de negócio e abrir a porta para enumeração.
Se o seu projeto é novo, comece já com UUIDv7 nas chaves públicas e evite a dor de uma migração futura. Se o sistema é antigo, dá para migrar aos poucos, expondo um UUID novo sem apagar o inteiro interno de imediato.
O próximo passo é simples: olhe agora para as suas URLs e respostas de API. Se você vê números sequenciais por lá, o German Tank Problem já está trabalhando contra você, e o UUID é a correção mais direta.
Comentários
Deixar um comentárioVocê precisa ter uma conta no CuritibaBlog para comentar.