UUID vs bigint no PostgreSQL: escolhendo IDs que escalam
UUID vs bigint no PostgreSQL: compare tamanho de índices, ordem de ordenação, prontidão para sharding e como IDs circulam por APIs, web e apps móveis.

Por que a escolha do ID importa mais do que parece
Toda linha em uma tabela PostgreSQL precisa de uma maneira estável de ser encontrada novamente. Isso é o que um ID faz: identifica uma linha de forma única, geralmente é a chave primária e vira a cola para relacionamentos. Outras tabelas o armazenam como chave estrangeira, consultas fazem joins com ele e apps o passam por aí como o identificador de “aquele cliente”, “aquela fatura” ou “aquele ticket de suporte”.
Como os IDs acabam em todo lugar, a escolha não é só um detalhe de banco de dados. Ela aparece depois no tamanho de índices, padrões de escrita, velocidade de consultas, taxas de acerto de cache e até em trabalho de produto como analytics, importações e depuração. Também afeta o que você expõe em URLs e APIs e quão fácil é para um app mobile armazenar e sincronizar dados com segurança.
A maioria das equipes acaba comparando UUID vs bigint no PostgreSQL. Em termos simples, você está escolhendo entre:
- bigint: um número de 64 bits, frequentemente gerado por uma sequência (1, 2, 3...).
- UUID: um identificador de 128 bits, que costuma parecer aleatório ou ser gerado de forma ordenada por tempo.
Nenhuma opção vence em todo cenário. Bigint tende a ser compacto e amigável a índices e ordenação. UUIDs são uma boa escolha quando você precisa de IDs globalmente únicos entre sistemas, quer IDs públicos menos previsíveis ou espera criar dados em muitos lugares (vários serviços, mobile offline ou sharding futuro).
Uma boa regra: decida com base em como seus dados serão criados e compartilhados, não só em como serão armazenados hoje.
Noções básicas: bigint e UUID em termos simples
Quando as pessoas comparam UUID vs bigint no PostgreSQL, estão escolhendo entre duas maneiras de nomear linhas: um número tipo contador pequeno ou um valor maior e globalmente único.
Um bigint é um inteiro de 64 bits. No PostgreSQL você geralmente o gera com uma coluna identity (ou o padrão mais antigo serial). O banco mantém uma sequência por trás e fornece o próximo número a cada inserção. Isso faz com que os IDs tendam a ser 1, 2, 3, 4... é simples, fácil de ler e amigável em ferramentas e relatórios.
Um UUID (Universally Unique Identifier) tem 128 bits. Você normalmente o vê escrito como 36 caracteres com hífens, por exemplo 550e8400-e29b-41d4-a716-446655440000. Tipos comuns incluem:
- v4: UUIDs aleatórios. Fáceis de gerar em qualquer lugar, mas não ordenam por criação.
- v7: UUIDs ordenados por tempo. Ainda únicos, mas projetados para aumentar aproximadamente com o tempo.
O armazenamento é uma das primeiras diferenças práticas: bigint usa 8 bytes, enquanto UUID usa 16 bytes. Essa diferença aparece em índices e pode afetar a taxa de acerto do cache (o banco consegue colocar menos entradas de índice na memória).
Pense também em onde os IDs aparecem fora do banco. IDs bigint são curtos em URLs e fáceis de ler em logs ou tickets. UUIDs são mais longos e chatos de digitar, mas difíceis de adivinhar e podem ser gerados com segurança pelos clientes quando necessário.
Tamanho do índice e crescimento de tabela: o que muda
A maior diferença prática entre bigint e UUID é o tamanho. Bigint tem 8 bytes; UUID tem 16 bytes. Isso parece pouco até lembrar que índices repetem seus IDs muitas vezes.
O índice da chave primária precisa ficar “quente” na memória para ser rápido. Um índice menor significa que mais dele cabe em shared buffers e no cache da CPU, então buscas e joins exigem menos leituras de disco. Com chaves primárias UUID, o índice normalmente fica visivelmente maior para a mesma quantidade de linhas.
O multiplicador são os índices secundários. Em índices B-tree do PostgreSQL, cada entrada de índice secundário também armazena o valor da chave primária (para que o banco encontre a linha). Então IDs mais largos inflacionam não só o índice primário, mas todos os outros índices que você adicionar. Se você tiver três índices secundários, os 8 bytes extras dos UUIDs aparecem efetivamente em quatro lugares.
Chaves estrangeiras e tabelas de junção também sentem o impacto. Qualquer tabela que referencia seu ID armazena esse valor em suas próprias linhas e índices. Uma tabela de muitos-para-muitos pode ser quase duas chaves estrangeiras mais um overhead pequeno, então dobrar a largura da chave pode mudar bastante seu footprint.
Na prática:
- UUIDs geralmente tornam índices primários e secundários maiores, e a diferença se multiplica conforme você adiciona índices.
- Índices maiores significam mais pressão na memória e mais leituras de página sob carga.
- Quanto mais tabelas referenciam a chave (events, logs, tabelas de junção), maior a importância da diferença de tamanho.
Se um ID de usuário aparece em users, orders, order_items e em um audit_log, esse mesmo valor é armazenado e indexado em todas essas tabelas. Escolher uma chave mais larga é tanto uma decisão de armazenamento quanto de identificação.
Ordem de ordenação e padrões de escrita: IDs sequenciais vs aleatórios
A maioria das chaves primárias do PostgreSQL fica em um índice B-tree. Um B-tree funciona melhor quando novas linhas caem perto do fim do índice, porque o banco pode ir apenas anexando com pouca reorganização.
IDs sequenciais: previsíveis e amigáveis ao armazenamento
Com bigint gerado por identity ou sequence, os novos IDs aumentam com o tempo. As inserções geralmente atacam a parte direita do índice, então as páginas ficam compactadas, o cache se mantém quente e o PostgreSQL faz menos trabalho extra.
Isso importa mesmo se você nunca fizer ORDER BY id. O caminho de escrita ainda precisa colocar cada nova chave no índice em ordem ordenada.
UUIDs aleatórios: mais dispersão, mais churn
Um UUID aleatório (comum em UUIDv4) espalha inserções por todo o índice. Isso aumenta a chance de page splits, onde o PostgreSQL precisa alocar novas páginas de índice e mover entradas para abrir espaço. O resultado é mais amplificação de escrita: mais bytes de índice escritos, mais WAL gerado e frequentemente mais trabalho em segundo plano (vacuum e controle de bloat).
UUIDs ordenados por tempo mudam a história. UUIDs que crescem com o tempo (como UUIDv7 ou outros esquemas baseados em tempo) restauram grande parte da localidade, mantendo o tamanho de 16 bytes e a aparência de UUIDs nas suas APIs.
Você sente essas diferenças principalmente quando tem alta taxa de inserções, tabelas grandes que não cabem na memória e vários índices secundários. Se você é sensível a picos de latência de escrita por causa de page splits, evite IDs totalmente aleatórios em tabelas com muitas gravações.
Exemplo: uma tabela de events ocupada recebendo logs de apps mobile o dia todo geralmente roda mais suave com chaves sequenciais ou UUIDs ordenados por tempo do que com UUIDs totalmente aleatórios.
Impacto de performance que você realmente sente
A maioria das quedas de desempenho no mundo real não é “UUIDs são lentos” ou “bigints são rápidos”. É o que o banco precisa tocar para responder sua consulta.
Planos de consulta se preocupam principalmente em saber se podem usar um index scan para filtros, fazer joins rápidos pela chave e se a tabela está fisicamente ordenada (ou próxima disso) para tornar leituras por faixa baratas. Com uma chave primária bigint, novas linhas entram em ordem crescente, então o índice tende a permanecer compacto e com boa localidade. Com UUIDs aleatórios, inserções se espalham pelo índice, o que pode gerar mais page splits e uma ordem em disco mais bagunçada.
Leituras são onde muitas equipes percebem primeiro. Chaves maiores significam índices maiores, e índices maiores significam menos páginas úteis na RAM. Isso reduz as taxas de acerto do cache e aumenta IO, especialmente em telas com muitos joins como “listar pedidos com info do cliente”. Se seu working set não cabe na memória, esquemas com muitos UUIDs podem te empurrar para o limite mais cedo.
Escritas também mudam. Inserções com UUID aleatório podem aumentar o churn do índice, o que adiciona pressão no autovacuum e aparece como picos de latência durante períodos de alta atividade.
Se você for fazer benchmark de UUID vs bigint no PostgreSQL, faça honestamente: mesmo esquema, mesmos índices, mesmo fillfactor e linhas suficientes para exceder a RAM (não 10k). Meça latência p95 e IO, e teste com cache quente e frio.
Se você constrói apps em AppMaster sobre PostgreSQL, isso frequentemente aparece como páginas de listagem mais lentas e carga maior no banco antes de parecer um “problema de CPU”.
Segurança e usabilidade em sistemas públicos
Se seus IDs saem do banco e aparecem em URLs, respostas de API, tickets de suporte e telas mobile, a escolha afeta segurança e usabilidade no dia a dia.
IDs bigint são fáceis para humanos. São curtos, você pode ditá-los ao telefone e a equipe de suporte consegue ver rapidamente padrões como “todos os pedidos com falha estão em torno de 9.200.000”. Isso acelera a depuração, especialmente quando se trabalha com logs ou screenshots de clientes.
UUIDs ajudam quando você expõe identificadores publicamente. Um UUID é difícil de adivinhar, então raspagem casual como /users/1, /users/2, /users/3 não funciona. Também fica mais difícil para terceiros inferirem quantos registros você tem.
A armadilha é pensar que “indecifrável” equivale a “seguro”. Se as checagens de autorização são frágeis, IDs previsíveis podem ser abusados rapidamente, mas UUIDs ainda podem ser roubados de um link compartilhado, um log vazado ou uma resposta de API em cache. Segurança vem de checagens de permissão, não de esconder o ID.
Uma abordagem prática:
- Faça checagens de propriedade ou de papel em toda leitura e escrita.
- Se expuser IDs em APIs públicas, use UUIDs ou tokens públicos separados.
- Se quiser referências amigáveis para humanos, mantenha um bigint interno para operações.
- Não codifique significado sensível no próprio ID (como tipo de usuário).
Exemplo: um portal de clientes mostra IDs de fatura. Se faturas usam bigint e sua API apenas checa “invoice existe”, alguém pode iterar números e baixar faturas de outros. Conserte a checagem primeiro. Depois decida se UUIDs para IDs públicos reduzem risco e carga de suporte.
Em plataformas como AppMaster, onde IDs fluem por APIs geradas e apps mobile, o padrão mais seguro é autorização consistente mais um formato de ID que seus clientes consigam manipular sem surpresas.
Como IDs fluem por APIs e apps mobile
O tipo que você escolhe no banco não fica só no banco. Ele vaza por todas as fronteiras: URLs, payloads JSON, armazenamento cliente, logs e analytics.
Se você mudar o tipo mais tarde, a quebra raramente é “só uma migração”. Chaves estrangeiras precisam mudar em todo lugar, não só na tabela principal. ORMs e geradores de código podem regenerar modelos, mas integrações ainda esperam o formato antigo. Até um simples GET /users/123 fica bagunçado quando o ID vira um UUID de 36 caracteres. Você também terá de atualizar caches, filas de mensagens e qualquer lugar que armazenou IDs como inteiros.
Para APIs, a maior decisão é formato e validação. Bigints viajam como números, mas alguns sistemas (e linguagens) têm risco de perda de precisão se os interpretarem como ponto flutuante. UUIDs viajam como strings, o que é mais seguro para parsing, mas exige validação estrita para evitar “UUIDs quase válidos” poluindo logs e o banco.
No mobile, IDs são constantemente serializados e armazenados: respostas JSON, tabelas SQLite locais e filas offline que salvam ações até o retorno da rede. IDs numéricos são menores, mas strings UUID muitas vezes são mais fáceis de tratar como tokens opacos. O que causa dor real é inconsistência: uma camada armazena como inteiro, outra como texto, e comparações ou joins ficam frágeis.
Algumas regras que evitam problemas:
- Escolha uma representação canônica para APIs (frequentemente string) e mantenha-a.
- Valide IDs na borda e retorne 400s claros.
- Armazene a mesma representação em caches locais e filas offline.
- Logue IDs com nomes e formatos consistentes entre serviços.
Se você gera clientes web e mobile com uma stack gerada (por exemplo, AppMaster gerando backend e apps nativos), um contrato de ID estável importa ainda mais porque vira parte de todos os modelos e requisições geradas.
Prontidão para sharding e sistemas distribuídos
“Pronto para sharding” geralmente significa que você pode criar IDs em mais de um lugar sem quebrar unicidade, e mover dados entre nós depois sem reescrever todas as chaves estrangeiras.
UUIDs são populares em setups multi-região ou multi-writer porque qualquer nó pode gerar um ID único sem pedir a uma sequência central. Isso reduz coordenação e facilita aceitar gravações em diferentes regiões e mesclar dados depois.
Bigint ainda funciona, mas é preciso um plano. Opções comuns incluem alocar faixas numéricas por shard (shard 1 usa 1-1B, shard 2 usa 1B-2B), rodar sequências separadas com prefixo de shard ou usar IDs estilo Snowflake (bits de tempo + bits de máquina/shard). Isso pode manter índices menores que UUIDs e preservar alguma ordenação, mas adiciona regras operacionais a serem aplicadas.
Trade-offs importantes no dia a dia:
- Coordenação: UUIDs quase não precisam; bigint frequentemente exige planejamento de faixas ou um serviço gerador.
- Colisões: colisões de UUID são extremamente improváveis; bigint é seguro apenas se regras de alocação nunca se sobrepuserem.
- Ordenação: muitos esquemas bigint são aproximadamente ordenados por tempo; UUID é muitas vezes aleatório a menos que use uma variante ordenada por tempo.
- Complexidade: bigint shardado permanece simples só se a equipe for disciplinada.
Para muitas equipes, “pronto para sharding” significa na prática “pronto para migração”. Se você está em um único banco hoje, escolha o ID que facilita o trabalho atual. Se já está construindo múltiplos escritores (por exemplo, via APIs geradas e apps mobile em AppMaster), decida cedo como IDs são criados e validados entre os serviços.
Passo a passo: escolhendo a estratégia certa de ID
Comece definindo a forma real do seu app. Um único banco PostgreSQL em uma região tem necessidades diferentes de um sistema multi-tenant, de uma arquitetura que pode ser particionada por região ou de um app mobile que precisa criar registros offline e sincronizar depois.
Em seguida, seja honesto sobre onde os IDs vão aparecer. Se os identificadores ficam dentro do backend (jobs, ferramentas internas, painéis administrativos), simplicidade costuma vencer. Se IDs aparecem em URLs, logs compartilhados com clientes, tickets de suporte ou deep links mobile, previsibilidade e privacidade importam mais.
Use ordenação como fator de decisão, não como pensamento posterior. Se você depende de feeds “mais recentes primeiro”, paginação estável ou trilhas de auditoria fáceis de varrer, IDs sequenciais (ou IDs ordenados por tempo) reduzem surpresas. Se a ordenação não está amarrada à chave primária, mantenha a escolha do PK separada e ordene por um timestamp.
Um fluxo prático de decisão:
- Classifique sua arquitetura (DB único, multi-tenant, multi-região, offline-first) e se você pode precisar unir dados de múltiplas fontes.
- Decida se IDs são identificadores públicos ou puramente internos.
- Confirme necessidades de ordenação e paginação. Se precisar de ordem natural de inserção, evite IDs puramente aleatórios.
- Se for usar UUIDs, escolha a versão com propósito: aleatório (v4) para imprevisibilidade, ou ordenado por tempo para melhor localidade de índice.
- Trave convenções cedo: uma forma textual canônica, regras de caixa, validação e como cada API retorna e recebe IDs.
Exemplo: se um app mobile cria “pedidos rascunho” offline, UUIDs permitem que o dispositivo gere IDs com segurança antes do servidor ver. Em ferramentas como AppMaster, isso também é conveniente porque o mesmo formato de ID pode fluir do banco para a API e para apps nativos sem tratativas especiais.
Erros comuns e armadilhas para evitar
A maioria dos debates sobre ID dá errado porque equipes escolhem um tipo por um motivo e depois se surpreendem com efeitos colaterais.
Um erro comum é usar UUIDs totalmente aleatórios em uma tabela com muitas gravações e depois se perguntar por que inserções ficam com picos. Valores aleatórios espalham novas linhas pelo índice, aumentando page splits e trabalho sob alta carga. Se a tabela tem muito write, pense em localidade de inserção antes de decidir.
Outro problema frequente é misturar tipos de ID entre serviços e clientes. Por exemplo, um serviço usa bigint, outro usa UUID, e sua API acaba com IDs numéricos e string. Isso vira bugs sutis: parsers JSON que perdem precisão em números grandes, código mobile que trata IDs como número em uma tela e string em outra, ou chaves de cache que não batem.
A terceira armadilha é tratar “IDs não previsíveis” como segurança. Mesmo com UUIDs, você precisa de checagens de autorização.
Por fim, equipes mudam o tipo de ID tardiamente sem plano. A parte mais difícil não é a chave primária em si, são todas as coisas anexadas a ela: chaves estrangeiras, tabelas de junção, URLs, eventos de analytics, deep links mobile e estado armazenado no cliente.
Para evitar dor:
- Escolha um tipo de ID para APIs públicas e mantenha-o.
- Trate IDs como strings nos clientes para evitar problemas numéricos.
- Nunca use aleatoriedade de ID como controle de acesso.
- Se precisar migrar, version a API e planeje para clientes de longa vida.
Se você constrói com uma plataforma que gera código como AppMaster, a consistência importa ainda mais porque o mesmo tipo de ID flui do esquema do banco para o backend gerado e para apps web e mobile.
Checklist rápido antes de decidir
Se estiver travado, não comece pela teoria. Comece pelo que seu produto será em um ano e por quantos lugares esse ID vai viajar.
Pergunte-se:
- Qual será o tamanho das maiores tabelas em 12 a 24 meses e vocês manterão anos de histórico?
- Precisa de IDs que aproximadamente ordenem por criação para paginação fácil e depuração?
- Mais de um sistema criará registros ao mesmo tempo, incluindo mobile offline ou jobs em background?
- O ID aparecerá em URLs, tickets de suporte, exports ou screenshots compartilhadas com clientes?
- Todo cliente pode tratar o ID da mesma forma (web, iOS, Android, scripts), incluindo validação e armazenamento?
Depois de responder, verifique a infraestrutura. Se usar bigint, garanta um plano claro de geração de IDs em cada ambiente (especialmente dev local e imports). Se usar UUID, garanta que contratos de API e modelos de cliente manuseiem strings consistentemente e que a equipe saiba ler e comparar UUIDs.
Um teste prático: se um app mobile precisa criar um pedido offline e sincronizar depois, UUIDs costumam reduzir coordenação. Se seu app é majoritariamente online e você quer índices compactos, bigint é geralmente mais simples.
Se você constrói em AppMaster, decida cedo para que o modelo de banco, endpoints de API e clientes móveis gerados permaneçam consistentes ao regenerar e escalar o projeto.
Exemplo realista
Uma empresa pequena tem uma ferramenta interna, um portal de clientes e um app mobile para equipe de campo. Os três usam o mesmo PostgreSQL via uma API. Novos registros são criados o dia todo: tickets, fotos, atualizações de status e faturas.
Com bigint, payloads da API são compactos e fáceis de ler:
{ "ticket_id": 4821931, "customer_id": 91244 }
A paginação parece natural: ?after_id=4821931&limit=50. Ordenar por id geralmente coincide com a ordem de criação, então “últimos tickets” é rápido e previsível. Depurar é simples: suporte pode pedir por “ticket 4821931” e a maioria digita sem erro.
Com UUIDs, os payloads ficam maiores:
{ "ticket_id": "3f9b3c0a-7b9c-4bf0-9f9b-2a1b3c5d1d2e" }
Se usar UUID v4 aleatório, inserções caem por todo o índice. Isso pode significar mais churn de índices e paginação no estilo cursor em vez de after id.
Se usar UUIDs ordenados por tempo, você preserva a maior parte do comportamento “mais recentes primeiro” enquanto evita IDs previsíveis em URLs públicos.
Na prática, equipes costumam notar quatro coisas:
- Com que frequência IDs são digitados por humanos vs copiados
- Se “ordenar por id” bate com “ordenar por created”
- Quão limpa e estável é a paginação por cursor
- Quão fácil é rastrear um registro entre logs, chamadas de API e telas mobile
Próximos passos: escolha um padrão, teste e padronize
A maioria das equipes trava porque busca a resposta perfeita. Você não precisa da perfeição. Precisa de um padrão que sirva hoje e uma forma rápida de provar que não vai te atrapalhar depois.
Regras para padronizar:
- Use bigint quando quiser índices menores, ordenação previsível e depuração simples.
- Use UUID quando IDs devem ser difíceis de adivinhar em URLs, você espera criação offline (mobile) ou quer menos colisões entre sistemas.
- Se pode particionar dados por tenant/região no futuro, prefira um plano de ID que funcione entre nós (UUID ou esquema bigint coordenado).
- Escolha um padrão como default e faça exceções raras. Consistência costuma vencer micro-otimizações por tabela.
Antes de confirmar, faça um spike: crie uma tabela com tamanho realista de linha, insira 1 a 5 milhões de registros e compare (1) tamanho de índice, (2) tempo de inserção e (3) algumas consultas comuns com a PK e alguns índices secundários. Faça no seu hardware real e com seu formato de dados.
Se teme mudar depois, planeje a migração para que seja chata e previsível:
- Adicione a nova coluna de ID e um índice único.
- Escrita dupla: preencha ambos os IDs para novas linhas.
- Backfill das linhas antigas em lotes.
- Atualize APIs e clientes para aceitar o novo ID (mantenha o antigo funcionando durante a transição).
- Mude as leituras, depois remova a chave antiga quando logs e métricas estiverem limpos.
Se você está construindo em AppMaster, vale decidir cedo porque a convenção de ID percorre seu modelo PostgreSQL, APIs geradas e apps web e nativos.
FAQ
Prefira bigint quando você tiver um único banco PostgreSQL, a maioria das gravações ocorrer no servidor e você se importar com índices compactos e comportamento previsível de inserção. Escolha UUIDs quando os IDs precisarem ser gerados em muitos lugares (vários serviços, mobile offline, sharding futuro) ou quando você não quiser que IDs públicos sejam fáceis de adivinhar.
Porque o ID é copiado para muitos lugares: o índice da chave primária, cada índice secundário (como ponteiro para a linha), colunas de chave estrangeira em outras tabelas e tabelas de junção. UUIDs ocupam 16 bytes vs 8 bytes do bigint, então a diferença de tamanho se multiplica pelo esquema e pode reduzir a taxa de acertos de cache.
Em tabelas com muitas inserções simultâneas, sim. UUIDs aleatórios (como v4) espalham as inserções por toda a B-tree, o que aumenta page splits e churn no índice sob carga. Se você quer UUIDs mas também inserções mais suaves, use uma estratégia de UUID ordenado por tempo para que as chaves novas caiam principalmente no final.
Geralmente aparece como mais IO, não como CPU mais lenta. Chaves maiores significam índices maiores, e índices maiores significam menos páginas na memória, então joins e buscas podem provocar mais leituras. A diferença é mais perceptível em tabelas grandes, consultas com muitos joins e quando o working set não cabe na RAM.
UUIDs ajudam a reduzir tentativas fáceis de adivinhação como /users/1, mas não substituem autorização. Se suas checagens de permissão estiverem erradas, UUIDs ainda podem vazar e ser reutilizados. Trate UUIDs como conveniência para identificadores públicos e confie em controle de acesso rigoroso para segurança real.
Use uma representação canônica única e mantenha-a. Um padrão prático é tratar IDs como strings em requisições e respostas de API, mesmo que o banco use bigint, porque isso evita problemas numéricos nos clientes e simplifica a validação. Seja qual for a escolha, mantenha consistência entre web, mobile, logs e caches.
Bigint pode provocar problemas em alguns clientes se for parseado como número de ponto flutuante, o que perde precisão em valores muito grandes. UUIDs evitam isso por serem strings, mas são mais longos e precisam de validação rigorosa. A abordagem mais segura é consistência: um tipo em todos os lugares, com validação clara na borda da API.
UUIDs são uma escolha direta porque podem ser criados independentemente sem coordenar uma sequência central. Bigint ainda funciona, mas você precisa de regras como faixas por shard ou um gerador estilo Snowflake, e isso precisa ser aplicado sempre. Se quiser a história distribuída mais simples, prefira UUIDs (de preferência ordenados por tempo).
Mudar o tipo da chave primária toca muito mais do que uma coluna. Você precisa atualizar chaves estrangeiras, tabelas de junção, contratos de API, armazenamento em clientes, dados em caches, eventos de analytics e integrações que guardaram IDs como números ou strings. Se houver chance de mudança, planeje uma migração gradual com dual-write e uma janela de transição longa.
Mantenha uma chave interna bigint para eficiência do banco e adicione um UUID público (ou token) separado para URLs e APIs externas. Isso te dá índices compactos e depuração interna simples, ao mesmo tempo que evita enumeração fácil em identificadores públicos. O importante é decidir cedo qual é o “ID público” e não misturá-los sem critério.


