Architecture Decision Records - ADR241015

2024/10/15

Uso di un database a grafo per gestire compatibilità complesse in un catalogo prodotti enterprise

Autore: Marco Orlandin, Architect
Data: 15 Ottobre 2024 (aggiornato Dicembre 2025)
Status: Implementato con successo
Settore: Manufacturing / Cataloghi tecnici
Dimensione dataset: >40.000 elementi, >252 milioni di relazioni
Constraint principali: Performance (query <1 secondo), integrazione con legacy RDBMS, costi contenuti (open-source preferred), rispetto NDA cliente

Nota: Progetto reale, anonimizzato per riservatezza (NDA). Metriche e dettagli semplificati, ma decisioni e lezioni apprese fedeli alla realtà.

Contesto e problema

In un progetto per un cliente enterprise, dovevamo gestire la compatibilità tra migliaia di componenti tecnici (immagina un catalogo prodotti con varianti, accessori, versioni software/firmware, norme di sicurezza, ecc.).

Un componente A è compatibile con B solo se soddisfa una serie di condizioni multiple (tipo di connessione, versione, certificazione regionale, ecc.). Il sistema legacy usava un database relazionale classico (PostgreSQL), con tabelle normalizzate e join multiple per verificare le compatibilità.

Problema principale:
Le query diventavano esplosive. Con volumi reali, una singola verifica di compatibilità richiedeva secondi o decine di secondi – inaccettabile per un’interfaccia utente o un configuratore online. Le join cartesiane crescevano esponenzialmente con l’aggiunta di nuovi parametri.

Obiettivo: portare le query complesse sotto il secondo, senza riscrivere tutto il legacy e mantenendo l’RDBMS come source of truth.

Requisiti non funzionali

  • Tempo di risposta query < 800 ms (95° percentile)
  • Scalabilità orizzontale futura
  • Integrazione bidirezionale con RDBMS esistente
  • Costi operativi bassi (no licenze proprietarie pesanti)
  • Facilità di evoluzione del modello (nuovi tipi di relazione)

Opzioni valutate

Opzione 1: Ottimizzare l'RDBMS esistente

  • Indicizzazioni avanzate, materialized views, denormalizzazione parziale.
  • Pro: Zero nuove tecnologie, minimo impatto sul legacy.
  • Contro: Limiti strutturali – le relazioni many-to-many complesse restano costose. Difficile scalare oltre un certo punto senza riscritture massive.

Opzione 2: Graph database dedicato (ArangoDB / Neo4j)

  • Rappresentare nodi (componenti) e archi (relazioni di compatibilità) in modo nativo.
  • Pro: Traversing di grafo estremamente veloci, modello dati naturale per relazioni.
  • Contro: Introduzione nuova tecnologia, necessità di ETL/sincronizzazione, curva di apprendimento team.

Opzione 3: Soluzioni NoSQL document-oriented (es. MongoDB con aggregation)

  • Pro: Flessibile, scalabile.
  • Contro: Le query di traversing profondo restano complesse e lente rispetto a un graph DB nativo.

Decisione

Ho scelto ArangoDB (open-source, multi-model) come database a grafo dedicato, integrato con il sistema esistente.

Motivazioni principali:

  • Performance nativa sui traversing (ordini di grandezza migliore di SQL join).
  • Licenza open-source + possibilità di deploy su infrastructure esistente.
  • Supporto AQL (query language simile a SQL) → curva apprendimento bassa per il team.
  • Facilità di integrazione via ETL periodico/custom.

L’RDBMS resta la singola fonte di verità per i dati master; il grafo viene popolato via ETL e usato solo per query di compatibilità.

Implementazione passo-passo

  1. Analisi dataset: Mappato entità e relazioni esistenti.
  2. Modello grafo: Nodi = Componenti (con proprietà), Archi = COMPATIBLE_WITH (con proprietà condizionali).
  3. Dati di test: Generati pseudocasuali ma realistici (>40k nodi, >252M archi).
  4. ETL custom: Script Python per estrarre, trasformare e caricare (usa ArangoDB driver).
  5. API: Flask per esporre endpoint RESTful di query compatibilità.
  6. Deploy: Tutto containerizzato con Docker per riproducibilità.
  7. Monitoring: Query profiling integrato in ArangoDB.

Conseguenze osservate (dopo go-live)

  • Performance: Query complesse passate da secondi/decine di secondi a <800 ms su hardware modesto, senza tuning avanzato.
  • Flessibilità: Aggiungere nuovi tipi di compatibilità = solo nuovi tipi di archi, senza alter table.
  • Manutenibilità: Team ha apprezzato la leggibilità del modello grafo.
  • Rischi mitigati: Sincronizzazione ETL schedulata + fallback su RDBMS in caso di discrepanza.
  • Lezioni apprese:
    • Inizia con un PoC su subset dati reali.
    • Monitora la densità del grafo (troppi archi → ottimizza con filtri preliminari).
    • Non tutto deve andare nel grafo: usa ibrido quando serve.

Stack utilizzato

  • Database grafo: ArangoDB (open-source)
  • Backend/API: Python + Flask
  • ETL: Script Python custom
  • Containerizzazione: Docker
  • Legacy: PostgreSQL (come source of truth)

Quando considerare un database a grafo

Se hai:

  • Relazioni many-to-many profondamente annidate
  • Query di traversing ricorrenti (compatibilità, raccomandazioni, bill of materials)
  • Evoluzione frequente del modello relazionale

...e le prestazioni SQL non bastano più, un grafo è spesso la soluzione più pulita.

Se invece le relazioni sono semplici o il volume è basso, ottimizzare l’RDBMS è quasi sempre sufficiente.

Hai un caso simile (catalogo prodotti, supply chain, configuratori)?

Contattami. Valutiamo insieme se un grafo risolve o se basta un tuning mirato.