C’è un piccolo insieme di problemi per cui il modello relazionale è genuinamente una cattiva scelta, e il sintomo è sempre lo stesso: le tue query fanno crescere una foresta di clausole JOIN, ognuna delle quali percorre un passo in più lungo una catena di foreign key, e il query plan passa da accettabile a disastroso da qualche parte intorno al quinto hop. L’esempio a cui tutti ricorrono è “amici di amici di amici”, ma la stessa forma riappare nella fraud detection, nei recommendation engine, nei knowledge graph, e in ogni sistema in cui le relazioni tra entità contano quanto le entità stesse. Questa lezione parla della famiglia di database costruiti esattamente per questa forma.
Il titolo è breve: quando le relazioni sono l’80% delle tue query, un graph database rende quelle query banali e veloci. Quando le relazioni sono il 20% delle tue query, un database relazionale con CTE ricorsive va benissimo. È nella zona intermedia che vivono le decisioni interessanti.
Il modello dei dati
Un graph database memorizza due cose: node e edge.
Un node è un’entità. Un utente, un prodotto, un’azienda, una transazione. Ha un’etichetta (il tipo) e un insieme di proprietà (coppie chiave-valore). Un node tipico potrebbe essere (:Person {name: "Narcis", age: 33}), dove Person è l’etichetta e il blocco tra parentesi graffe sono le proprietà.
Un edge è una relazione tra due node. Ha un tipo, una direzione, e opzionalmente proprietà proprie. Un edge tipico potrebbe essere (narcis)-[:KNOWS {since: 2018}]->(maria), che significa una relazione KNOWS diretta da narcis a maria, con una proprietà since che registra quando è iniziata la relazione.
Non c’è uno schema separato in senso relazionale. Lo schema è il graph stesso. Puoi aggiungere nuovi tipi di node, nuovi tipi di relazione, nuove proprietà a piacere. La forma dei dati è quella che ci metti dentro, e il database è felice di percorrerla.
flowchart LR
U1[User: Narcis] -- KNOWS --> U2[User: Maria]
U2 -- KNOWS --> U3[User: Luca]
U1 -- BOUGHT --> P1[Product: Espresso machine]
U2 -- BOUGHT --> P1
U3 -- LIKED --> P2[Product: Italian moka pot]
U2 -- LIKED --> P2
Un piccolo graph come questo ti permette di chiedere, in una sola query: “quali prodotti sono stati comprati o messi tra i preferiti da persone che i miei amici conoscono ma di cui io non so ancora niente”. Quella query, in SQL, è un multi-self-join attraverso una tabella users, una tabella friendships e una tabella purchases, con uno step ricorsivo per espandere la catena di amicizie. In un graph database, sono quattro righe.
Il linguaggio di query
Il linguaggio di query dominante per i property graph è Cypher, originariamente di Neo4j e ora adottato (con piccole varianti) da AuraDB, Memgraph, AWS Neptune (nella sua modalità property-graph), e dall’estensione Apache AGE per Postgres. La caratteristica distintiva di Cypher è che la sintassi per fare il match di un pattern del graph sembra un disegno ASCII del pattern stesso.
MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
WHERE a.name = "Narcis" AND a <> c
RETURN c.name
Questa trova gli amici degli amici di Narcis, escludendo Narcis stessa. La clausola MATCH disegna il pattern: un node Person a, connesso da un edge KNOWS a un node Person b, connesso da un altro edge KNOWS a un node Person c. La WHERE filtra. La RETURN proietta.
Leggere Cypher richiede dieci minuti per abituarsi e poi diventa naturale. Scrivere traversal complesse (path a lunghezza variabile, shortest path, traversal pesate) è drammaticamente più conciso dell’equivalente SQL. Il linguaggio è la ragione principale per cui i graph database risultano ergonomici per i workload a cui puntano.
Il W3C definisce anche SPARQL, un linguaggio di query per RDF triple store, che è un modello di graph diverso (triple soggetto-predicato-oggetto, niente proprietà sugli edge, schema descritto separatamente). SPARQL è lo standard nel mondo del semantic-web e dei knowledge graph accademici. È un linguaggio eccellente per quello che fa, e oggi la maggior parte di chi costruisce graph database applicativi sceglie Cypher rispetto a SPARQL perché il modello property-graph mappa più naturalmente sul tipo di oggetti a cui le applicazioni tengono.
Dove brillano i graph database
I casi d’uso in cui il modello a graph vince senza ambiguità sono quelli in cui la traversal di relazioni multi-hop è la domanda centrale.
Recommendation engine
“Agli utenti come te è piaciuto anche X” è il problema canonico della recommendation, e nel suo cuore è una graph traversal. Trova gli utenti connessi a me da qualche segnale (acquisti simili, valutazioni simili, social graph simile), guarda con cosa quegli utenti hanno interagito, fai emergere gli item che non ho ancora visto. In un graph database è un’operazione da una query. In un database relazionale è un batch job che materializza una matrice di similarità di notte.
Fraud detection
La frode finanziaria spesso si manifesta come pattern nel graph delle relazioni che sono invisibili a livello delle singole transazioni. Un anello di account che si trasferiscono soldi a vicenda in un cerchio. Un account che improvvisamente riceve trasferimenti da venti nuove fonti. Un device fingerprint condiviso tra account che non dovrebbero conoscersi. Questi sono pattern di graph, e rilevarli in tempo reale è ciò in cui i graph database sono particolarmente bravi. Banche e payment processor sono gli utilizzatori più costanti dei graph database per questa ragione.
Social network
Amico-di-un-amico, connessioni in comune, shortest path tra due utenti, identificazione di influencer (node ad alta centralità). Sono i problemi di graph traversal da manuale, ed è esattamente come sono fatte le feature dei social network.
Knowledge graph
Wikidata, il Google Knowledge Graph, e una crescente serie di knowledge graph aziendali memorizzano entità (persone, luoghi, concetti, eventi) con relazioni tipizzate e ricche tra di esse. Le domande che fai a un knowledge graph (dammi tutti i film italiani diretti da donne negli anni 70, con gli attori che vi hanno recitato) sono traversal multi-hop attraverso edge tipizzati. Un graph database è lo store naturale.
Network e dependency graph
Dipendenze software, topologia infrastrutturale, supply chain, gerarchie organizzative. Qualunque cosa in cui la domanda “cosa dipende da X, transitivamente” sia una domanda reale. Sono graph, e un graph database rende la domanda transitiva una primitiva invece che un tema.
L’alternativa relazionale
Se il tuo problema sembra un graph ma è piccolo, oppure ha solo una manciata di tipi di relazione, oppure è per lo più tabellare con qualche query a forma di graph sopra, puoi fare il lavoro in SQL con le common table expression ricorsive (CTE).
Una CTE ricorsiva in Postgres ha questo aspetto per il caso amico-di-un-amico:
WITH RECURSIVE friends(person_id, depth) AS (
SELECT b.person_id, 1
FROM friendships b
WHERE b.friend_id = (SELECT id FROM persons WHERE name = 'Narcis')
UNION ALL
SELECT b.person_id, f.depth + 1
FROM friendships b
JOIN friends f ON b.friend_id = f.person_id
WHERE f.depth < 3
)
SELECT DISTINCT p.name
FROM friends f
JOIN persons p ON p.id = f.person_id
WHERE f.depth = 2;
Funziona. Non è bella, e non è ciò che un ingegnere legge in un pomeriggio impegnato, ma dà la risposta corretta per due o tre hop su graph piccoli o medi. Dove crolla è quando il graph è grande, la profondità è illimitata, o inizi ad aggiungere vincoli sul path stesso (solo edge con peso > X, solo certi tipi di edge, shortest-path-by-weight). Ognuno di questi è qualche clausola in più in una query a graph e un livello aggiuntivo di complessità nella CTE ricorsiva.
Il riassunto onesto: le CTE ricorsive di SQL vanno bene fino a due o tre hop su un graph moderato. Oltre, il costo cognitivo dell’SQL cresce più velocemente del costo cognitivo del Cypher equivalente, e le performance iniziano a degradare.
Il costo: un esempio concreto
Prendi la query “trova tutti i prodotti comprati dagli amici degli amici di Narcis, con il conteggio di quanti di quegli amici hanno comprato ciascun prodotto, ordinati per conteggio”.
In Cypher:
MATCH (n:Person {name: "Narcis"})-[:KNOWS*2]-(friend:Person)-[:BOUGHT]->(p:Product)
WHERE friend <> n
RETURN p.name, count(DISTINCT friend) AS buyers
ORDER BY buyers DESC
LIMIT 10
Quattro righe di sostanza. Si legge come una descrizione del problema.
In Postgres con CTE ricorsive:
WITH RECURSIVE friend_chain(person_id, depth) AS (
SELECT id, 0 FROM persons WHERE name = 'Narcis'
UNION ALL
SELECT f.friend_id, fc.depth + 1
FROM friendships f
JOIN friend_chain fc ON f.person_id = fc.person_id
WHERE fc.depth < 2
),
fof AS (
SELECT DISTINCT person_id
FROM friend_chain
WHERE depth = 2
AND person_id <> (SELECT id FROM persons WHERE name = 'Narcis')
)
SELECT p.name, count(DISTINCT pu.person_id) AS buyers
FROM fof
JOIN purchases pu ON pu.person_id = fof.person_id
JOIN products p ON p.id = pu.product_id
GROUP BY p.name
ORDER BY buyers DESC
LIMIT 10;
Una pagina di SQL. Funziona. È più difficile da leggere, più difficile da modificare, e su un graph di milioni di utenti e decine di milioni di relazioni, sarà più lenta della versione Cypher, a volte di parecchio.
Il panorama dei prodotti
Neo4j: la scelta dominante
Neo4j è il graph database a cui la maggior parte dei team ricorre per primo, e per buoni motivi. È in giro dal 2007, il linguaggio Cypher è stato inventato lì, la documentazione è eccellente, la community è grande, e il tooling (Neo4j Browser, Bloom, la libreria Graph Data Science) è maturo. C’è una community edition open-source e una enterprise edition con clustering, sicurezza, e funzionalità operative.
Il trade-off, come con la maggior parte delle storie del tipo “la scelta dominante a singolo prodotto”, è che Neo4j è il suo proprio database. Lo gestisci a parte, ne fai backup a parte, lo scali a parte. Per workload in cui il graph è il cuore dell’applicazione, va bene. Per workload in cui il graph è una feature tra tante, è overhead operativo.
AWS Neptune
Neptune è il graph database gestito di AWS. Supporta sia property graph con Gremlin/openCypher sia RDF con SPARQL. Il supporto a doppio modello è inusuale e utile se hai un piede in entrambi i mondi. L’argomento di vendita è il classico argomento del database gestito: meno carico operativo se sei già su AWS. Funzionalità e performance sono competitive con Neo4j, anche se l’implementazione di openCypher non è al 100% feature-complete rispetto al Cypher di Neo4j.
Memgraph
Memgraph è un graph database in-memory, scritto in C++, con un linguaggio di query Cypher-compatibile. Essendo in-memory, è significativamente più veloce dei graph database disk-based per i workload che ci stanno in RAM. L’argomento di vendita è per workload di graph in streaming (fraud detection in tempo reale, recommendation in tempo reale) dove la latenza conta e il working set sta in memoria.
Postgres + Apache AGE
Apache AGE è un’estensione di Postgres che aggiunge graph storage e un linguaggio di query simile a Cypher sopra un database Postgres standard. Il vantaggio è che mantieni un solo database. Puoi mescolare query relazionali e query a graph nella stessa transazione, e non gestisci un secondo store. Gli svantaggi sono che AGE è meno ricco di feature rispetto a Neo4j, le performance non sono altrettanto buone per workload graph-intensive, e l’ecosistema è più piccolo.
Per un team che già fa girare Postgres e vuole aggiungere modeste funzionalità di graph senza overhead operativo, AGE è una scelta ragionevole. Per un team in cui il graph è centrale, Neo4j è la risposta migliore.
La decisione
La regola di decisione, distillata:
- Se le relazioni sono il cuore della tua applicazione (fraud detection, recommendation engine, knowledge graph, social network), usa un graph database dedicato. Neo4j è il default sicuro.
- Se hai una o due query a forma di graph sopra un’applicazione per lo più relazionale, scrivile come CTE ricorsive nel tuo database relazionale esistente e convivi con la scomodità.
- Se sei nel mezzo, e sei già su Postgres, prova Apache AGE prima di aggiungere un secondo database.
- Se sei su AWS e non vuoi gestire nulla in proprio, Neptune va bene.
L’errore più comune è l’opposto della lezione: i team ricorrono a un graph database perché i graph database sono interessanti, decidono che tutto è un graph, e finiscono con un sistema in cui il graph database fa il 5% del lavoro e il 50% del carico operativo. La maggior parte dei dati è tabellare. Usa il graph store per il problema di graph reale. Usa lo store relazionale per il resto.
Cosa viene dopo
La prossima lezione parla dei search engine, dove la forma del dato è ancora una volta diversa (testo, con scoring di rilevanza) e lo store dominante (Elasticsearch e le alternative cresciute al suo fianco) fa cose che nessun database general-purpose può eguagliare. Dopodiché lasciamo lo zoo dello storage e ci spostiamo nello strato operativo del Modulo 3: replication, sharding, partitioning, e gli aspetti pratici del far girare questi sistemi in produzione.
Riferimenti e letture di approfondimento
- Documentazione di Neo4j,
https://neo4j.com/docs/(consultato 2026-05-01). Il riferimento per Cypher, il modello operativo, e la libreria Graph Data Science. - The Cypher language reference (openCypher),
https://opencypher.org/(consultato 2026-05-01). La specifica del linguaggio mantenuta dalla community. - Ian Robinson, Jim Webber, Emil Eifrem, “Graph Databases” (O’Reilly, 2nd edition 2015). Il manuale fondazionale. Vecchiotto ma ancora la migliore introduzione al modello dati e ai design pattern.
- Documentazione di AWS Neptune,
https://docs.aws.amazon.com/neptune/(consultato 2026-05-01). Il doppio modello property-graph e RDF, con note operative sul servizio gestito. - Documentazione di Memgraph,
https://memgraph.com/docs(consultato 2026-05-01). Architettura in-memory e il caso d’uso streaming-graph. - Documentazione di Apache AGE,
https://age.apache.org/age-manual/master/index.html(consultato 2026-05-01). L’estensione Postgres e le sue capacità attuali.