Questa è la lezione finale del Modulo 3. Nel corso delle ultime otto lezioni abbiamo attraversato le principali categorie di datastore: relazionali, document, key-value, columnar, graph, time-series, vector. Ogni categoria risolve una classe di query che le altre gestiscono in modo goffo. Ogni categoria ha un ecosistema sano di prodotti. E ognuna, presa per conto suo, è tentante da aggiungere al tuo stack nel momento in cui incontri un workload che gestirebbe bene.
Questa lezione è la risposta alla domanda “dovrei aggiungere un altro database?”, e la risposta, sorprendentemente spesso, è “no, ma continua a leggere”. Il termine per il design pattern che consiste nel far girare più datastore specializzati in una sola applicazione è polyglot persistence, e ha passato gli ultimi quindici anni oscillando tra l’essere di moda e l’essere rimpianto. Dove si colloca nel 2026, e come decidere se è la scelta giusta per te, è il tema di oggi.
Da dove arriva il termine
Martin Fowler ha coniato “polyglot persistence” nel 2011 in un breve post bliki (https://martinfowler.com/bliki/PolyglotPersistence.html). L’argomento era diretto: i database relazionali erano stati l’opzione universale di default per trent’anni, ma l’ascesa di store specializzati significava che un’applicazione poteva scegliere lo strumento giusto per ogni sottoinsieme dei suoi dati. Usa il database relazionale per i dati transazionali, un key-value store per le sessioni, un motore di ricerca per le full-text query, un graph database per le relazioni, un column store per l’analytics. Ogni parte del sistema usa lo storage che si adatta, e l’insieme si compone in qualcosa di più performante di qualsiasi singolo store.
Il diagramma nel post di Fowler è diventato iconico: un’app e-commerce con sette box diversi, ciascuno etichettato con un database diverso, tutti collegati allo stesso application layer. Sembrava elegante. Sembrava ciò che il 2011 voleva fosse il futuro.
Gli anni successivi ci hanno insegnato che al diagramma mancava il box più importante: il team. Ogni store aggiuntivo è un’altra cosa che il tuo team deve imparare, monitorare, fare backup, aggiornare e debuggare alle tre del mattino, e quei costi non si sommano in modo lineare. Si compongono in modo esponenziale.
La promessa: lo strumento perfetto per ogni lavoro
L’argomento a favore della polyglot persistence è quello che ho portato implicitamente per tutto il Modulo 3. I pattern di query sono diversi. Un graph traversal è scomodo in SQL. La full-text search è lenta senza un inverted index. Le aggregazioni time-series vogliono un column store. Le sessioni vogliono una lookup key-value in memoria a bassa latenza. Il vector search vuole HNSW. Se costruisci tutto questo sopra un singolo database relazionale, stai facendo cose per cui il database non è ottimizzato, e prima o poi colpisci un soffitto di performance che lo strumento giusto avrebbe sollevato facilmente.
In un mondo perfetto, avresti un database per pattern di query, ognuno tarato sul suo lavoro, ognuno scalabile in modo indipendente. L’applicazione sarebbe un sottile layer che instrada ogni richiesta verso lo store appropriato. Nuovi requisiti significherebbero un nuovo database. L’architettura si comporrebbe in modo pulito.
Quel mondo esiste, nelle slide. In produzione, ha costi che le slide non mostrano.
La realtà: ogni store raddoppia la tua superficie operativa
Ogni database che aggiungi è un impegno di un ordine di grandezza, non di una percentuale. Acquisisci, con il nuovo store: una strategia di backup e restore che qualcuno ha effettivamente testato; una storia di monitoring con metriche, soglie, dashboard e regole di pager; un piano di upgrade, perché le major version rompono le cose e il database che hai adottato alla versione 5 sarà alla versione 9 fra tre anni; un onere di formazione on-call, perché quando il database si comporta male alle 3 del mattino qualcuno deve conoscerlo abbastanza bene da diagnosticarlo; una storia di disaster recovery con RTO e RPO misurati; e un problema di sincronizzazione, perché i dati che vivono in due posti divergono.
L’euristica grossolana che uso è: ogni nuovo sistema di persistenza all’incirca raddoppia la superficie on-call per il team che lo possiede. Due database sono il doppio del lavoro di uno. Tre sono quattro volte. L’esponente non è preciso, ma la direzione lo è. Un piccolo team con tre database fa più lavoro operativo dello stesso team con uno solo, e quel lavoro è sottratto al tempo che altrimenti spenderebbe a spedire feature.
La posizione di default nel 2026: parti con un solo Postgres
Quello che è cambiato da quando Fowler ha scritto il suo post è quanto può fare un singolo database relazionale moderno. Postgres nel 2026 non è il Postgres del 2011. La stessa istanza che serve il tuo workload transazionale può anche gestire, in modo competente:
- Full-text search, tramite
tsvector,tsquerye indici GIN. Le performance sono buone fino ai milioni di documenti, e la search è transazionale rispetto ai dati sottostanti, cosa che Elasticsearch non può offrire. - Document storage, tramite
jsonb. Gli operatori (->,->>,@>,?) e gli indici GIN sui path JSON ti danno un document database dentro Postgres, con lo schema relazionale per le parti che vuoi strutturate e il JSON per le parti che variano. - Vector search, tramite l’estensione
pgvectortrattata nella lezione precedente: indici HNSW, distanze coseno, L2 e dot-product, query ibride che combinano similarità vettoriale con filtri relazionali. - Time-series, tramite TimescaleDB o le hypertable partizionate native. Hypertable, compressione, continuous aggregate, retention policy. Competitivo con gli store dedicati della lezione 22 per la maggior parte dei workload.
- Graph traversal, tramite l’estensione Apache AGE, che aggiunge query in stile Cypher sopra le tabelle Postgres. Per i casi più semplici, le CTE ricorsive (
WITH RECURSIVE) ti portano gran parte della strada. - Query geografiche, tramite PostGIS: il gold standard dei database GIS open-source, che gira come estensione Postgres.
Questo elenco significa che, nel 2026, alla domanda “dovrei aggiungere un motore di ricerca, un vector database, un time-series database e un graph database” la risposta per molti team è la stessa: non ancora. Fai girare i workload su Postgres prima. Quando superi Postgres per uno di essi in particolare, estrai quel singolo workload. Gli altri restano dove sono. Questa è la stessa lezione del tema “parti più semplice” del Modulo 1, applicata all’infrastruttura dati: i default del 2026 sono molto più capaci dei default del 2011, e la prescrizione architetturale deve aggiornarsi di conseguenza.
Pattern dove la polyglot è inevitabile
Ci sono workload in cui Postgres davvero non basta e il secondo database si guadagna il suo posto.
Search at scale. tsvector di Postgres è buono fino a un certo punto. Sopra le decine di milioni di documenti, con relevance ranking complesso, navigazione faceted, autocomplete, analyzer multi-lingua e latenza p99 in millisecondi, Elasticsearch (o OpenSearch) prende il largo. Se la tua applicazione è centralmente sulla search, Elasticsearch è la scelta corretta. Se la search è una feature laterale, Postgres è la scelta corretta.
Caching at scale. Redis è la risposta giusta per le hot key, lo storage delle sessioni, le leaderboard, il pub/sub, il rate limiting: ovunque tu abbia bisogno di accesso a piccoli dati con TTL in millisecondi a singola cifra e non vuoi che ogni read colpisca il primary. Redis serve un ordine di grandezza in più di richieste al secondo rispetto a Postgres sullo stesso hardware e ti dà le primitive giuste. Aggiungere Redis come secondo store è la mossa polyglot più comune e più giustificata.
Analytics at scale. Far girare query di aggregazione pesanti contro il tuo Postgres transazionale fa soffrire il workload OLTP. Spedisci i dati in un column store (BigQuery, Snowflake, ClickHouse, DuckDB per scale più piccole) e fai girare l’analytics lì. Il database transazionale torna al suo lavoro. La lezione 19 ha coperto questo caso per intero.
Event sourcing. Quando il log degli eventi è esso stesso la fonte di verità, Kafka (o un log simile) è il system of record. Il database relazionale diventa una vista derivata, ricostruita dal log all’occorrenza. Il Modulo 4, lezione 42, riprende questo tema.
In ciascun caso, il secondo database fa qualcosa che Postgres davvero non sa fare bene, e il costo operativo viene pagato volentieri perché l’alternativa è peggio.
Il problema di tenerli sincronizzati
Una volta che i dati vivono in due posti, possiedi un problema di sincronizzazione. L’utente cambia la sua email nel primary; la cache, l’indice di search e il warehouse di analytics hanno tutti il vecchio valore. Ognuno deve essere aggiornato, ogni aggiornamento può fallire, ogni sistema ha la sua finestra di consistency. Il Modulo 4, lezione 46, copre i pattern di soluzione (change data capture e l’outbox pattern) in profondità. Le due forme che vale la pena nominare ora:
- Dual writes: quando l’applicazione scrive sul primary, scrive anche sulla cache, sull’indice di search e sulla coda di analytics. Facile da implementare, facile da sbagliare, e quasi sempre finisce con i secondary che divergono. Da evitare tranne che per le cache più semplici con TTL brevi.
- Change data capture (CDC): il primary emette uno stream di ogni cambiamento (logical replication di Postgres, Debezium, o simile) e i consumer downstream lo leggono e aggiornano i loro store. Il primary resta la fonte di verità, i secondary sono derivati, e la sincronizzazione è idempotente (lezione 16).
Se stai adottando un secondo store e la risposta a “come resta sincronizzato” è “facciamo dual-write”, metti a budget il tempo per la riscrittura. Finirai a CDC.
Un esempio lavorato: un’app e-commerce con tre store
Scenario concreto. Una piattaforma e-commerce con tre store scelti per le ragioni giuste:
- Postgres come system of record primario: ordini, prodotti, utenti, inventario, pagamenti. ACID sulle cose che ne hanno bisogno.
- Redis come cache: storage delle sessioni, hot product page, contatori di rate-limit, pub/sub per gli aggiornamenti in tempo reale tra processi.
- Elasticsearch come indice di search del catalogo: il catalogo prodotti completo con analyzer multi-lingua, navigazione faceted, autocomplete, tolleranza ai refusi.
flowchart LR
User[User request] --> App[Application]
App -->|writes| PG[(Postgres - source of truth)]
App -->|reads hot keys| R[(Redis - cache)]
App -->|search queries| ES[(Elasticsearch - search)]
PG -->|CDC stream| ES
PG -.->|invalidate| R
Le scritture vanno a Postgres. Uno stream CDC alimenta Elasticsearch, che resta aggiornato nel giro di secondi. Redis viene popolato in modo lazy in lettura, con invalidation alla scrittura su Postgres. Le letture di dati transazionali vanno a Postgres, le hot page passano per Redis, le query di search vanno a Elasticsearch.
I pro: ogni store fa ciò in cui è bravo. La search del catalogo è veloce e ricca, le lookup di sessione sono sotto il millisecondo, le scritture transazionali sono ACID, nessuno degli store viene maltrattato. I contro: tre database, tre rotazioni on-call, tre strategie di backup, tre dashboard di monitoring. Quando i risultati di search sembrano stantii il team deve decidere se il bug sia in Postgres, nella pipeline CDC o in Elasticsearch. Quando Redis va giù il team deve sapere se il fallback su Postgres possa assorbire il carico. La pipeline CDC è essa stessa un sistema da gestire.
Tre failure mode che vale la pena nominare. Cache stampede: la cache entry di un articolo popolare scade, mille richieste concorrenti fanno miss, tutte colpiscono Postgres, Postgres crolla. Mitiga con il single-flight. CDC lag: lo stream verso Elasticsearch resta indietro, la search mostra prodotti cancellati o nasconde quelli nuovi. Monitora e fai alert sul lag. Cache-write split-brain: la scrittura su Postgres riesce ma l’invalidation della cache fallisce, la cache serve dati stantii fino al TTL. TTL brevi, e tollera la staleness.
Questa è un’architettura polyglot ragionevole. Ogni store si guadagna il suo posto. L’applicazione è significativamente migliore di quanto sarebbe su Postgres da solo, ma solo perché il workload richiede davvero la specializzazione e il team ha investito nella maturità operativa per gestire tre sistemi.
La raccomandazione finale
L’istinto di aggiungere un altro database sembra quasi sempre produttivo. Il nuovo store risolverà il problema specifico che hai davanti oggi. I benefici sono concreti. I costi sono astratti e non si presentano fino a dopo, quando stai gestendo quattro sistemi di backup e tre rotazioni on-call e il tuo team sta facendo più lavoro operativo che lavoro sulle feature.
Sii scettico verso “dovremmo aggiungere il database X”. La risposta di default è “abbiamo già spremuto Postgres prima?”. Guarda jsonb, pgvector, TimescaleDB, PostGIS, AGE. La maggior parte delle volte, uno di loro fa il lavoro abbastanza bene da rendere il secondo database non necessario. Quando la risposta è genuinamente “no, Postgres non può farlo alla nostra scala”, allora aggiungi il secondo store, a occhi aperti. Paga il costo volentieri perché il workload lo richiede, non perché il diagramma sembrava elegante.
I team che ho visto avere successo con la polyglot persistence sono quelli che ci sono arrivati di malavoglia. Ogni store è stato aggiunto perché un dolore misurato li ha spinti a farlo. I team che ho visto faticare sono quelli che sono partiti dal diagramma elegante e poi hanno passato due anni a operarlo.
Di cosa è stato il Modulo 3
Otto lezioni fa abbiamo iniziato il Modulo 3 con il modello relazionale e la domanda di cosa lo renda l’opzione di default. Abbiamo attraversato le alternative: document, key-value, columnar, graph, time-series, vector. Ognuna risolve un problema che il modello relazionale gestisce in modo scomodo, e ognuna costa qualcosa in cambio. L’arco del modulo è stato dare il vocabolario per valutare qualsiasi scelta di database, inclusa quella che hai già.
Tre cose da ricordare. Primo, “il database giusto” è una funzione dei pattern di query e dei vincoli operativi, non della moda. Secondo, Postgres nel 2026 fa molto di più di quanto la sua reputazione del 2011 suggerisca, e il default “usa Postgres finché non hai misurato che non puoi” è corretto più spesso di quanto i diagrammi suggeriscano. Terzo, ogni database aggiuntivo è un costo reale: la polyglot persistence è uno strumento da usare con riluttanza e in modo deliberato, non un obiettivo in sé.
Il Modulo 3 è concluso. Il Modulo 4 si rivolge alla domanda che è stata rimandata per tutto il modulo: come scali orizzontalmente una qualsiasi di queste scelte di database, quando il soffitto del singolo server è il collo di bottiglia. Replication, partitioning, sharding, i modelli di consistency del Modulo 2 applicati ai layout di storage reali. I pattern sono gli stessi indipendentemente dal database che hai scelto, e sono la prossima fondamenta.
Citazioni e letture di approfondimento
- Martin Fowler, “PolyglotPersistence”, 2011,
https://martinfowler.com/bliki/PolyglotPersistence.html(consultato 2026-05-01). Il post originale che ha dato il nome al pattern. - Pramod Sadalage e Martin Fowler, “NoSQL Distilled” (Addison-Wesley, 2012). Il trattamento in formato libro dell’argomento polyglot, scritto quando era il caso ottimista.
- Documentazione TimescaleDB,
https://docs.timescale.com/(consultato 2026-05-01). Il riferimento per il percorso time-series-su-Postgres. - Documentazione Apache AGE,
https://age.apache.org/age-manual/master/index.html(consultato 2026-05-01). L’estensione Cypher-su-Postgres. - Documentazione PostGIS,
https://postgis.net/documentation/(consultato 2026-05-01). L’estensione spaziale che è stata il gold standard per due decenni. - pgvector,
https://github.com/pgvector/pgvector(consultato 2026-05-01). L’estensione vector-search trattata nella lezione 23. - Documentazione Postgres, “Full Text Search”,
https://www.postgresql.org/docs/current/textsearch.html(consultato 2026-05-01). Il riferimento per il macchinariotsvector/tsquery.