UUID vs bigint in PostgreSQL: scegliere gli ID che scalano
UUID vs bigint in PostgreSQL: confronta dimensione degli indici, ordine, prontezza allo sharding e come gli ID attraversano API, web e app mobile.

Perché la scelta dell'ID conta più di quanto sembri
Ogni riga in una tabella PostgreSQL ha bisogno di un modo stabile per essere ritrovata. Questo è il compito di un ID: identifica un record in modo univoco, di solito è la chiave primaria e diventa la colla per le relazioni. Altre tabelle lo conservano come foreign key, le query fanno join su di esso e le app lo passano in giro come riferimento per “quel cliente”, “quella fattura” o “quel ticket di supporto”.
Perché gli ID finiscono ovunque, la scelta non è solo un dettaglio di database. Si manifesta più avanti nelle dimensioni degli indici, nei pattern di scrittura, nella velocità delle query, nei tassi di hit della cache e persino nel lavoro di prodotto come analytics, importazioni e debugging. Influisce anche su cosa esponi nelle URL e nelle API, e su quanto sia semplice per un'app mobile memorizzare e sincronizzare i dati in modo sicuro.
La maggior parte dei team si ritrova a confrontare UUID vs bigint in PostgreSQL. In termini semplici, stai scegliendo tra:
- bigint: un numero a 64 bit, spesso generato da una sequenza (1, 2, 3...).
- UUID: un identificatore a 128 bit, dall'aspetto spesso casuale, o generato in modo ordinato nel tempo.
Nessuna opzione è vincente in ogni caso. Il bigint tende a essere compatto e amico degli indici e dell'ordinamento. Gli UUID sono adatti quando ti servono ID globalmente unici tra sistemi, vuoi ID pubblici più sicuri o prevedi che i dati vengano creati in molti posti (più servizi, mobile offline o sharding futuro).
Una regola pratica: decidi in base a come i tuoi dati verranno creati e condivisi, non solo a come verranno memorizzati oggi.
Fondamenti di Bigint e UUID in parole semplici
Quando le persone confrontano UUID vs bigint in PostgreSQL, stanno scegliendo tra due modi di nominare le righe: un numero piccolo e contatore, oppure un valore più lungo e globalmente unico.
Un bigint è un intero a 64 bit. In PostgreSQL lo generi di solito con una colonna identity (o lo schema serial più vecchio). Il database mantiene una sequenza e fornisce il numero successivo a ogni insert. Questo significa che gli ID tendono a essere 1, 2, 3, 4... È semplice, facile da leggere e comodo negli strumenti e nei report.
Un UUID (Universally Unique Identifier) è a 128 bit. Lo vedrai spesso scritto come 36 caratteri con trattini, tipo 550e8400-e29b-41d4-a716-446655440000. Tipi comuni includono:
- v4: UUID casuali. Facili da generare ovunque, ma non si ordinano per ordine di creazione.
- v7: UUID ordinati per tempo. Restano unici ma progettati per aumentare grossomodo nel tempo.
Lo storage è una delle prime differenze pratiche: bigint usa 8 byte, mentre UUID usa 16 byte. Questo divario di dimensioni si vede negli indici e può influire sui tassi di hit della cache (il database può contenere meno voci di indice in memoria).
Pensa anche a dove gli ID compaiono fuori dal database. Gli ID bigint sono corti nelle URL e facili da leggere da log o ticket di supporto. Gli UUID sono più lunghi e fastidiosi da digitare, ma più difficili da indovinare e possono essere generati in modo sicuro dai client quando necessario.
Dimensione degli indici e bloat delle tabelle: cosa cambia
La differenza pratica più grande tra bigint e UUID è la dimensione. Bigint è 8 byte; UUID è 16 byte. Sembra poco finché non ricordi che gli indici ripetono i tuoi ID molte volte.
L'indice della chiave primaria deve restare caldo in memoria per essere veloce. Un indice più piccolo significa che una parte maggiore entra in shared buffers e nella cache CPU, quindi lookup e join richiedono meno letture da disco. Con le chiavi UUID l'indice è di solito significativamente più grande per lo stesso numero di righe.
Il moltiplicatore sono gli indici secondari. Negli indici B-tree di PostgreSQL, ogni voce di indice secondario memorizza anche il valore della chiave primaria (per trovare la riga). Quindi ID più larghi gonfiano non solo l'indice primario, ma anche ogni altro indice che aggiungi. Se hai tre indici secondari, gli extra 8 byte degli UUID si manifestano effettivamente in quattro posti.
Anche foreign key e tabelle di join lo sentono. Qualsiasi tabella che faccia riferimento al tuo ID memorizza quel valore nelle proprie righe e indici. Una tabella di join molti-a-molti può essere composta soprattutto da due foreign key più un piccolo overhead, quindi raddoppiare la larghezza delle chiavi può cambiare molto il suo ingombro.
In pratica:
- Gli UUID di solito rendono gli indici primari e secondari più grandi, e la differenza si compone man mano che aggiungi indici.
- Indici più grandi significano più pressione sulla memoria e più letture di pagina sotto carico.
- Più tabelle fanno riferimento alla chiave (eventi, log, tabelle di join), più conta la differenza di dimensione.
Se un ID utente appare in users, orders, order_items e in un audit_log, lo stesso valore è memorizzato e indicizzato in tutte quelle tabelle. Scegliere una chiave più larga è una decisione di storage tanto quanto una decisione sugli ID.
Ordine di ordinamento e pattern di scrittura: ID sequenziali vs casuali
La maggior parte delle chiavi primarie PostgreSQL sta su un indice B-tree. Un B-tree funziona meglio quando le nuove righe finiscono vicino alla fine dell'indice, perché il database può appenderle con minimo rimescolamento.
ID sequenziali: prevedibili e amichevoli allo storage
Con un bigint identity o una sequence, i nuovi ID aumentano nel tempo. Gli insert di solito colpiscono la parte destra dell'indice, quindi le pagine restano piene, la cache resta calda e PostgreSQL fa meno lavoro extra.
Questo conta anche se non esegui mai ORDER BY id. Il percorso di scrittura deve comunque posizionare ogni nuova chiave nell'indice in ordine ordinato.
UUID casuali: più dispersione, più churn
Un UUID casuale (comune con UUIDv4) distribuisce gli insert su tutto l'indice. Questo aumenta la probabilità di page split, dove PostgreSQL deve allocare nuove pagine di indice e spostare voci per fare spazio. Il risultato è più write amplification: più byte di indice scritti, più WAL generato e spesso più lavoro in background in seguito (vacuum e gestione del bloat).
Gli UUID ordinati per tempo cambiano la storia. Gli UUID che aumentano grosso modo nel tempo (come UUIDv7 o altri schemi basati sul tempo) ripristinano gran parte della località, pur restando 16 byte e mantenendo l'aspetto di UUID nelle API.
Queste differenze si sentono di più quando hai tassi di insert elevati, tabelle grandi che non entrano in memoria e più indici secondari. Se sei sensibile a spike di latenza delle scritture dovuti a page split, evita ID completamente casuali sulle tabelle con scritture intense.
Esempio: una tabella eventi molto trafficata che riceve log da app mobile tutto il giorno di solito girerà meglio con chiavi sequenziali o UUID ordinati per tempo rispetto a UUID completamente casuali.
Impatto sulle performance che puoi effettivamente percepire
La maggior parte dei rallentamenti reali non è “gli UUID sono lenti” o “i bigint sono veloci”. È ciò che il database deve toccare per rispondere alla tua query.
I piani di query si preoccupano principalmente se possono usare uno scan di indice per i filtri, fare join veloci sulla chiave e se la tabella è ordinata fisicamente (o abbastanza vicina) da rendere economiche le letture di range. Con una chiave primaria bigint, le nuove righe atterrano in ordine crescente, quindi l'indice primario tende a restare compatto e favorevole alla località. Con UUID casuali, gli insert si disperdono nell'indice, il che può creare più page split e un ordine su disco più disordinato.
Le letture sono dove molti team lo notano per primi. Chiavi più grandi significano indici più grandi, e indici più grandi significano che meno pagine utili stanno in RAM. Questo riduce i tassi di hit della cache e aumenta l'I/O, specialmente nelle schermate con molti join come “elenco ordini con info cliente”. Se il tuo working set non entra in memoria, gli schemi ricchi di UUID possono spingerti oltre quel limite prima.
Anche le scritture possono cambiare. Gli insert con UUID casuali possono aumentare il churn nell'indice, aggiungendo pressione su autovacuum e manifestandosi come spike di latenza nei periodi di punta.
Se fai benchmark tra UUID e bigint in PostgreSQL, fallo onestamente: stesso schema, stessi indici, stesso fillfactor e abbastanza righe da superare la RAM (non 10k). Misura la latenza p95 e l'I/O, e testa sia cache calda che fredda.
Se costruisci app in AppMaster su PostgreSQL, questo spesso si manifesta come pagine di elenco più lente e carico DB più alto molto prima che sembri un “problema CPU”.
Sicurezza e usabilità nei sistemi pubblici
Se i tuoi ID escono dal database e compaiono in URL, risposte API, ticket di supporto e schermate mobile, la scelta influisce sia sulla sicurezza sia sull'usabilità quotidiana.
Gli ID bigint sono facili per gli umani. Sono corti, si possono leggere al telefono e il team di supporto può rapidamente individuare pattern come “tutti gli ordini falliti sono intorno a 9.200.000”. Questo può velocizzare il debugging, specialmente quando lavori da log o screenshot dei clienti.
Gli UUID sono utili quando esponi identificatori al pubblico. Un UUID è difficile da indovinare, quindi scraping casuale come /users/1, /users/2, /users/3 non funziona. Inoltre rende più difficile per estranei inferire quanti record hai.
La trappola è pensare che “non indovinabile” significhi “sicuro”. Se i controlli di autorizzazione sono deboli, gli ID prevedibili possono essere abusati rapidamente, ma gli UUID possono comunque essere rubati da un link condiviso, un log trapelato o una risposta API in cache. La sicurezza deve venire dai controlli di permesso, non dall'occultamento dell'ID.
Un approccio pratico:
- Applica controlli di proprietà o di ruolo su ogni lettura e scrittura.
- Se esponi ID in API pubbliche, usa UUID o token pubblici separati.
- Se vuoi riferimenti leggibili, mantieni un bigint interno per le operazioni.
- Non codificare significati sensibili nell'ID stesso (ad es. tipo utente).
Esempio: un portale clienti mostra gli ID delle fatture. Se le fatture usano bigint e la tua API controlla solo “la fattura esiste”, qualcuno può iterare i numeri e scaricare le fatture altrui. Risolvi prima il controllo; poi decidi se usare UUID per gli ID pubblici riduce il rischio e il carico di supporto.
In piattaforme come AppMaster, dove gli ID scorrono attraverso API generate e app mobile, il default più sicuro è una autorizzazione coerente più un formato ID che i client possano gestire affidabilmente.
Come gli ID fluiscono attraverso API e app mobile
Il tipo scelto non resta nel database. Trapela in ogni confine: URL, payload JSON, storage client, log e analytics.
Se cambi tipo di ID più tardi, la rottura raramente è “solo una migrazione”. Le foreign key devono cambiare ovunque, non solo nella tabella principale. ORM e generatori di codice possono rigenerare modelli, ma le integrazioni si aspettano ancora il vecchio formato. Anche un semplice GET /users/123 diventa complicato quando l'ID diventa un UUID di 36 caratteri. Devi aggiornare cache, code di messaggi e ogni posto dove gli ID erano memorizzati come interi.
Per le API, la scelta più grande è formato e validazione. I bigint viaggiano come numeri, ma alcuni sistemi (e alcuni linguaggi) rischiano problemi di precisione su valori molto grandi se li parsano come float. Gli UUID viaggiano come stringhe, il che è più sicuro per il parsing, ma serve una validazione rigorosa per evitare che “quasi UUID” finiscano in log e database.
Sul mobile, gli ID vengono continuamente serializzati e memorizzati: risposte JSON, tabelle SQLite locali e code offline che salvano azioni finché la rete non ritorna. Gli ID numerici sono più piccoli, ma gli UUID come stringhe sono spesso più facili da trattare come token opachi. Il vero problema è l'incoerenza: uno strato lo memorizza come intero, un altro come testo, e confronti o join diventano fragili.
Alcune regole che evitano problemi:
- Scegli una rappresentazione canonica per le API (spesso stringa) e rispettala.
- Valida gli ID all'edge e ritorna errori 400 chiari.
- Memorizza la stessa rappresentazione in cache locali e code offline.
- Logga gli ID con nomi di campo e formati coerenti tra i servizi.
Se costruisci client web e mobile con uno stack generato (per esempio AppMaster che genera backend e app native), un contratto stabile sugli ID conta ancora di più perché entra in ogni modello e richiesta generata.
Prontezza per lo sharding e sistemi distribuiti
“Pronto per lo sharding” significa in pratica poter creare ID in più posti senza rompere l'unicità e poter spostare dati tra nodi in seguito senza riscrivere ogni foreign key.
Gli UUID sono popolari in setup multi-regione o multi-writer perché ogni nodo può generare un ID unico senza chiedere a una sequenza centrale. Questo riduce la coordinazione e facilita l'accettazione di scritture in regioni diverse e la successiva fusione dei dati.
Il bigint può ancora funzionare, ma serve un piano. Opzioni comuni includono allocare range numerici per shard (shard 1 usa 1-1B, shard 2 usa 1B-2B), eseguire sequenze separate con un prefisso shard o usare ID in stile Snowflake (bit basati su tempo più bit macchina o shard). Queste soluzioni mantengono gli indici più piccoli degli UUID e preservano un certo ordine, ma aggiungono regole operative da far rispettare.
Trade-off quotidiani:
- Coordinazione: UUID richiedono quasi nulla; bigint spesso richiede pianificazione di range o un servizio generatore.
- Collisioni: le collisioni negli UUID sono estremamente improbabili; bigint è sicuro solo se le regole di allocazione non si sovrappongono.
- Ordinamento: molti schemi bigint sono approssimativamente ordinati per tempo; UUID è spesso casuale a meno che non usi una variante ordinata per tempo.
- Complessità: il bigint sharded resta semplice solo se il team è disciplinato.
Per molti team, “pronto per lo sharding” significa più semplicemente “pronto per la migrazione”. Se oggi sei su un singolo database, scegli l'ID che semplifica il lavoro corrente. Se stai già costruendo più writer (es. tramite API generate e app mobile in AppMaster), decidi presto come gli ID vengono creati e validati tra i servizi.
Passo-passo: scegliere la strategia di ID giusta
Inizia descrivendo la forma reale della tua app. Un singolo database PostgreSQL in una regione ha esigenze diverse rispetto a un sistema multi-tenant, un setup che potrebbe poi essere diviso per regione, o un'app mobile che deve creare record offline e sincronizzare dopo.
Poi sii onesto su dove compariranno gli ID. Se gli identificatori restano all'interno del backend (job, strumenti interni, pannelli admin), la semplicità spesso vince. Se gli ID compaiono in URL, log condivisi con i clienti, ticket di supporto o deep link mobile, la prevedibilità e la privacy contano di più.
Usa l'ordinamento come fattore decisionale, non come ripensamento. Se fai affidamento su feed “più recenti prima”, paginazione stabile o audit facili da scorrere, gli ID sequenziali (o ordinati per tempo) riducono le sorprese. Se l'ordinamento non è legato alla chiave primaria, puoi mantenere la scelta del PK separata e ordinare per un timestamp invece.
Un flusso decisionale pratico:
- Classifica l'architettura (singolo DB, multi-tenant, multi-regione, offline-first) e se potresti unire dati da più fonti.
- Decidi se gli ID sono identificatori pubblici o puramente interni.
- Conferma le esigenze di ordinamento e paginazione. Se ti serve l'ordine naturale di inserimento, evita ID completamente casuali.
- Se scegli UUID, seleziona una versione con consapevolezza: casuale (v4) per imprevedibilità, o ordinata per tempo per miglior località negli indici.
- Blocca le convenzioni presto: una forma testuale canonica, regole di maiuscole/minuscole, validazione e come le API restituiscono e accettano gli ID.
Esempio: se un'app mobile crea “ordini di bozza” offline, gli UUID permettono al dispositivo di generare ID in modo sicuro prima che il server li veda. In strumenti come AppMaster, questo è conveniente perché lo stesso formato ID può fluire dal database all'API fino alle app web e native senza casi particolari.
Errori comuni e trappole da evitare
La maggior parte dei dibattiti sugli ID va storto perché le persone scelgono un tipo per una ragione e poi si sorprendono degli effetti collaterali.
Un errore comune è usare UUID completamente casuali su una tabella con scritture intense e poi chiedersi perché gli insert sembrano a spintoni. I valori casuali distribuiscono le nuove righe sull'indice, il che può significare più page split e più lavoro per il database sotto carico. Se la tabella è write-heavy, pensa alla località di inserimento prima di decidere.
Un altro problema frequente è mescolare tipi di ID tra servizi e client. Per esempio, un servizio usa bigint, un altro usa UUID e la tua API finisce con ID sia numerici che stringa. Questo porta spesso a bug sottili: parser JSON che perdono precisione su numeri grandi, codice mobile che tratta gli ID come numeri in una schermata e stringhe in un'altra, o chiavi di cache che non corrispondono.
Una terza trappola è trattare “ID non indovinabili” come controllo di accesso. Anche usando UUID, servono controlli di autorizzazione.
Infine, i team cambiano il tipo di ID tardi senza un piano. La parte più difficile non è la chiave primaria in sé, ma tutto ciò che è attaccato: foreign key, tabelle di join, URL, eventi analytics, deep link mobile e stato memorizzato nei client.
Per evitare dolore:
- Scegli un tipo di ID per le API pubbliche e mantienilo.
- Tratta gli ID come stringhe opache nei client per evitare problemi numerici.
- Non usare mai la casualità dell'ID come controllo d'accesso.
- Se devi migrare, versiona l'API e pianifica per client a lunga durata.
Se costruisci con una piattaforma che genera codice come AppMaster, la coerenza conta ancora di più perché lo stesso tipo di ID fluisce dallo schema del DB al backend generato e nelle app.
Checklist rapida prima di decidere
Se sei bloccato, non partire dalla teoria. Parti da come sarà il tuo prodotto tra un anno e da quanti posti viaggerà quell'ID.
Chiediti:
- Quanto grandi diventeranno le tabelle nei prossimi 12-24 mesi e manterrai anni di cronologia?
- Ti servono ID che si ordinino approssimativamente per tempo per paginazione e debugging facili?
- Più di un sistema creerà record contemporaneamente, inclusi mobile offline o job in background?
- L'ID apparirà in URL, ticket di supporto, esportazioni o screenshot condivisi con clienti?
- Ogni client può trattare l'ID allo stesso modo (web, iOS, Android, script), inclusa validazione e storage?
Dopo aver risposto, controlla l'infrastruttura. Se usi bigint, assicurati di avere un piano chiaro per la generazione di ID in ogni ambiente (specialmente dev locale e importazioni). Se usi UUID, assicurati che i contratti API e i modelli client gestiscano coerentemente gli ID come stringhe e che il team sia a suo agio nel leggerli e confrontarli.
Un test di realtà rapido: se un'app mobile deve creare un ordine offline e sincronizzare dopo, gli UUID spesso riducono il lavoro di coordinazione. Se la tua app è per lo più online e vuoi indici compatti, bigint è solitamente più semplice.
Se costruisci in AppMaster (appmaster.io), decidilo presto perché la convenzione sull'ID attraversa il modello PostgreSQL, le API generate e le app web e native.
Un esempio realistico
Una piccola azienda ha uno strumento operativo interno, un portale clienti e un'app mobile per il personale sul campo. Tutti e tre accedono allo stesso database PostgreSQL tramite un'unica API. Si creano nuovi record tutto il giorno: ticket, foto, aggiornamenti di stato e fatture.
Con bigint gli payload API sono compatti e facili da leggere:
{ "ticket_id": 4821931, "customer_id": 91244 }
La paginazione è naturale: ?after_id=4821931&limit=50. Ordinare per id di solito corrisponde al tempo di creazione, quindi “ultimi ticket” è veloce e prevedibile. Il debugging è semplice: il supporto può chiedere “ticket 4821931” e la maggior parte delle persone può digitarlo senza errori.
Con UUID i payload diventano più lunghi:
{ "ticket_id": "3f9b3c0a-7b9c-4bf0-9f9b-2a1b3c5d1d2e" }
Se usi UUID v4 casuali, gli insert atterrano in tutto l'indice. Questo può significare più churn dell'indice e un debug quotidiano leggermente più scomodo (copia/incolla diventa la norma). La paginazione spesso si sposta verso token a cursore invece di usare after id.
Se usi UUID ordinati per tempo, mantieni gran parte del comportamento “più recenti prima” evitando comunque ID indovinabili nelle URL pubbliche.
In pratica i team notano di solito quattro cose:
- Quanto spesso gli ID vengono digitati dagli umani rispetto al copia/incolla
- Se “ordinare per id” corrisponde a “ordinare per created”
- Quanto pulita e stabile è la paginazione a cursore
- Quanto è facile tracciare un record tra log, chiamate API e schermate mobile
Prossimi passi: scegli un default, testalo e standardizza
La maggior parte dei team si blocca perché cerca la risposta perfetta. Non serve perfetto. Serve un default che si adatti al prodotto oggi, più un modo rapido per dimostrare che non ti farà male dopo.
Regole che puoi standardizzare:
- Usa bigint quando vuoi indici minori, ordinamento prevedibile e debugging semplice.
- Usa UUID quando gli ID devono essere difficili da indovinare nelle URL, prevedi creazione offline (mobile) o vuoi meno collisioni tra sistemi.
- Se potresti dividere i dati per tenant o regione più avanti, preferisci un piano ID che funzioni tra nodi (UUID o uno schema bigint coordinato).
- Scegli uno come default e rendi le eccezioni rare. La coerenza batte spesso l'ottimizzazione micro per una singola tabella.
Prima di bloccarlo, fai un piccolo spike. Crea una tabella con dimensione riga realistica, inserisci 1-5 milioni di righe e confronta (1) dimensione dell'indice, (2) tempo di insert e (3) alcune query comuni con la chiave primaria e un paio di indici secondari. Fallo sull'hardware reale e con la forma reale dei dati.
Se temi di dover cambiare in seguito, pianifica la migrazione in modo che sia noiosa:
- Aggiungi la nuova colonna ID e un indice unico.
- Dual-write: popola entrambi gli ID per le nuove righe.
- Backfilla le righe vecchie a batch.
- Aggiorna API e client per accettare il nuovo ID (mantieni il vecchio funzionante durante la transizione).
- Passa alle letture col nuovo ID e poi rimuovi il vecchio quando log e metriche sono puliti.
Se costruisci su AppMaster (appmaster.io), vale la pena decidere presto perché la convenzione sugli ID scorre nel modello PostgreSQL, nelle API generate e nelle app web e native. Il tipo specifico conta, ma la coerenza conta di più quando hai utenti reali e più client.
FAQ
Default a bigint quando hai un singolo database PostgreSQL, la maggior parte delle scritture avviene sul server e ti importano indici compatti e un comportamento di inserimento prevedibile. Scegli UUID quando gli ID devono essere generati in molti punti (più servizi, mobile offline, sharding futuro) o quando non vuoi che gli ID pubblici siano facili da indovinare.
Perché l'ID viene copiato in molti posti: l'indice della chiave primaria, ogni indice secondario (come puntatore alla riga), colonne di foreign key in altre tabelle e tabelle di join. Gli UUID occupano 16 byte contro gli 8 byte del bigint, quindi la differenza di dimensione si moltiplica attraverso lo schema e può ridurre i tassi di hit della cache.
Sì, sulle tabelle con inserimenti intensi. Gli UUID casuali (come v4) distribuiscono gli inserimenti su tutto l'albero B, aumentando page split e churn degli indici sotto carico. Se vuoi UUID ma anche scritture più fluide, usa una strategia con UUID ordinati per tempo in modo che le nuove chiavi atterrino per lo più alla fine.
Si manifesta spesso come più IO, non come maggiore uso CPU. Chiavi più grandi significano indici più grandi, e indici più grandi significano che meno pagine stanno in memoria, quindi join e lookup possono causare più letture. La differenza è più evidente su tabelle grandi, query ricche di join e sistemi il cui working set non entra in RAM.
Gli UUID riducono il tentativo di indovinare URL come /users/1, ma non sostituiscono l'autorizzazione. Se i controlli di permesso sono sbagliati, gli UUID possono comunque essere divulgati e riutilizzati. Tratta gli UUID come una comodità per identificatori pubblici e fai affidamento su un controllo degli accessi rigoroso per la sicurezza reale.
Usa una singola rappresentazione canonica e rispettala. Un default pratico è trattare gli ID come stringhe nelle richieste e risposte API, anche se il database usa bigint, perché evita problemi numerici nei client e mantiene semplice la validazione. Qualunque sia la scelta, mantienila coerente su web, mobile, log e cache.
I bigint possono creare problemi in alcuni client se vengono parsati come numeri in virgola mobile, che possono perdere precisione a valori molto grandi. Gli UUID evitano questo perché sono stringhe, ma sono più lunghi e più facili da gestire male se non li validi strettamente. L'approccio più sicuro è la coerenza: un tipo unico ovunque, con validazione chiara all'edge dell'API.
Gli UUID sono una scelta semplice perché possono essere creati indipendentemente senza coordinare una sequenza centrale. Il bigint può funzionare ma richiede regole (range per shard o un generatore Snowflake) e devi farle rispettare sempre. Se vuoi la storia distribuita più semplice, scegli gli UUID (preferibilmente ordinati per tempo).
Cambiare il tipo di chiave primaria coinvolge molto più di una colonna. Devi aggiornare foreign key, tabelle di join, contratti API, storage client, dati in cache, eventi di analytics e ogni integrazione che ha memorizzato ID come numeri o stringhe. Se potresti dover cambiare, pianifica una migrazione graduale con dual-write e una finestra di transizione lunga.
Mantieni una chiave interna bigint per l'efficienza del database e aggiungi un UUID pubblico separato (o un token) per URL e API esterne. Questo ti dà indici compatti e debug interno facile, evitando l'enumerazione negli identificatori pubblici. La chiave è decidere presto quale sia l'ID “pubblico” e non mischiarli casualmente.


