Il post stava diventando troppo lungo, quindi per semplicità ho preferito diverlo in due parti. E questa è la parte legata, invece, alle mie impressioni circa i database documentali, dopo la sessione fatta da Gabriele Lana:
Database Documentali: CouchDb e MongoDb
Sessioni interessante, che ha mostrato bene come lavora un database documentale. Una cosa che però ancora non ho capito è perchè molti sviluppatori ne rimangono affascinati, visto che la mia impressione è che non danno alcun valore reale aggiuntivo ad una soluzione nel medio-lungo termine, ma, anzi, IMHO possono far sembrare le cose più semplici di quelle che sono, andando cosi a minare una soluzione proprio nelle sue fondamenta: i dati.
Ok, so che quanto detto può sembrare polemico e far suppore una mia chiusura mentale verso altri tipi di database, ma vi assicuro che non è cosi: tornato dalla UGIALT conference, infatti, visto l’entusiasmo per tali daatabase, ero piuttosto sicuro di aver qualche lacuna personale in tal senso da colmare, e quindi proprio per capire il valore aggiunto di un database documentale, che evidentemente mi sfuggiva, appena possible ho scaricato ed installato Ubuntu 9.10 e CouchDB su una macchina virtuale (ormai uso solo queste…la mia macchina di lavoro è completamente virtuale):
L’interesse verso tali database è in (gran) parte legato alla semplificazione che offrono allo sviluppatore nella definizione dei database (es: provi ad accedere al db, se questo non c’è viene creato in automatico) e quindi, dopo un pò di ricerche per trovare la miglior libreria possibile, mi sono scaricato Divan per poter accedere a CouchDB utilizzando .NET.
Bene, fatto ciò mi sono messo subito a fare un pò di test per impratichirmi con CouchDB.
Il tempo “libero” a disposizione è poco, ma ho intenzione di fare un pò di test sulle prestazioni, che di primo achito non mi sembrano particolarmente esaltanti. Dato che non ho dati di prima mano, per ora, mi limito a fare alcune considerazioni di carattere generale, utilizzando – per ciò che concerne le performance - come riferimento i test fatti proprio da Gabriele Lana:
http://www.gabrielelana.it/archives/131
Ma prima ancora delle prestazioni mi sono dedicato alla ricerca della semplicità promessa, andando a provare a prendere un database di esempio (uno molto simile Northwind, ma adattato alla gesione di una libreria), portandolo su CouchDB.
Dopo diverse prove, è ora di fermarsi per condividere alcuni spunti di riflessione:
Facilità nello sviluppo applicativo
In effetti è tutto molto semplice. Posto che abbiate una libreria che vi supporta bene (e Divan lo fa), persistere il grafo di un oggetto è veramente banale. Si passa la classe ad un serializzatore JSON e si salva nel db. Fatto.
Ora, questa cosa mi ricorda molto da vicino un’altra cosa: XML. Prendo una classe, la serializzo in XML e…la passo al database (relazionale). Certo sarebbe bene che l’accesso al DB fosse via HTTP per una massima compatibilità. SQL Server 2005 permette di esporre Stored Procedure come Web Service dalla versione 2005. Funzione talmente tanto usata che in 2008 è stata deprecata. Urca. Che sia stata Microsoft ad essere troppo in anticipo sui tempi?
Non volete usare Stored Procedure per decomporre l’XML in entità relazionali? Beh male che vada potete salvare l’XML cosi com’è. In questo caso c’è ovviamente il problema delle performance nella ricerca dei dati all’interno di XML, ma ne parleremo dopo.
Ah, c’è un’altra possibilità. Se usate un ORM o simile (NHibernate, EntityFramework, LinqToSQL), ovviamente non dovete preoccuparvi (troppo) del mapping tra i due mondi (OO / ER). Non troppo, ma un pò si. Ok, se non volete fare neanche questo, in effetti CouchDB sembra ottimo. Ma a questo punto è ora di passare al prossimo paragrafo.
Definizione del modello dati
Un database relazionale per funzionare bene necessita di un modello dati corretto, ossia qualcosa che ci permetta di rappresentare la realtà che stiamo gestendo in modo da non avere situazioni impossibili od incorrette, il tutto fornendo delle performance ottimali. La normalizzazione, ossia “one entity per table”, ci può aiutare…ma a volte sembra una palla alla piede.
Bene, abbracciamo CouchDB e togliamoci la palla al piede. Il grafo dei miei oggetti viene serializzato per intero in un’unica “riga” (meglio, documento), e tanti saluti. Il processo di map/reduce mi garantisce le performance (ne parliamo dopo) ed io vivo felice. O no?
No. Perchè dopotutto è cmq necessario gestire correttamente delle relazioni tra entità, cosi come scritto nell’help di CouchDB:
http://wiki.apache.org/couchdb/EntityRelationship
Perchè è necessario? Inutile trovare un esempio più semplice, leggete questo estratto dell’help:
Imagine you are building a snazzy new web application that includes an address book where users can store their contacts. For each contact the user stores, you want to capture the contacts name, birthday, their address, telephone number and company they work for. That's great, your users immediately begin to use their address book and soon the datastore starts to fill up.
Not long after the deployment of your new application you hear from someone that they are not happy that there is only one phone number. What if they want to store someone's work telephone number in addition to their home number? No problem you think, you can just add a work phone number to your structure.
Update the form with the new field and you are back in business. Soon after redeploying your application, you get a number of new complaints. When they see the new phone number field, people start asking for even more fields. Some people want a fax number field, others want a mobile field. Some people even want more than one mobile field (boy modern life sure is hectic)! You could add another field for fax, and another for mobile, maybe two. What about if people have three mobile phones? What if they have ten? What if someone invents a phone for a place you've never thought of? Your model needs to use relationships.
La cosa veramente divertente è che questo esempio è esattamente quello che feci in un workshop UGISS per spiegare il perchè della necessità di normalizzare un database. Partendo da questo esempio arrivate a spiegare che cosa è la normalizzazione e perchè serve. Stiamo infatti dicendo che un database documentale necessità di relazioni tra entità. Ma appena introducete il concetto di relazione tra entità, introducete la necessità di capire cosa mettere in un’entità e cosa mettere in un’altra, in modo formale, senza seguire il buon senso (che non sempre è presente). Il che porta ai concetti base di chiavi e di dipendenza funzionale. Ohibò, questi sono concetti base della normalizzazione.
Sul sito è mostrato anche un approcio alternativo, ossia quello di avere le entità relazionate direttamente incluse (embedded) nel documento principale.
“That is the power of schema-less databases” dice l’help. Non dice perchè è però presente anche l’approcio che prevede delle relazioni tra diverse entità, visto che quello schema-less è più potente. Beh, ve lo dico io. I problemi introdotti dall’approccio embedded si chiamano “update anomalies” e “data duplication”. Supponente di avere un documento che rappresenta un libro e che quindi contiene anche i suoi autori. Ogni libro si porterebbe dietro i dati degli autori. E se dovessi aggiornare uno di questi dati (ad esempio la data di nascita o di morte)? Lo devo fare in tutti i documenti, con gran dispendio di energia. E se lo faccio solo in un documento e non negli altri? Introduco una inconsistenza nel sistema, per cui non so più quale documento contenga i dati corretti e quale no.
In pratica, gli stessi identici problemi di un database non normalizzato. Quindi, mettiamola come vogliamo, la normalizzazione non dipende dalla tecnologia usata, ma è una necessità di qualsiasi sistema tratti con insiemi di dati. Ora, se andiamo a dover normalizzare i documenti di un database documentale torniamo alla “palla al piede” dei database relazionali e quindi, tanto vale rimanere su questi ultimi.
A questo discorso andrebbe aggiunto anche un commento circa l’integrità dei dati. Senza andare a cercare esempi particolare, basti pensare che, usando JSON, per CouchDB non è possibile definire un data come tale, e quindi potremmo inserire date come 31 Febbraio 2010. Poco bello, direi.
E purtroppo l’integrità dei dati è importante: un database che non garantisce l’integrità è poco più di un file testo. E quindi non ci si può fare affidamento.
Le Performance e la Scalabilità
Performance e scalabilità sono gli altri due cavalli di battaglia dei database documentali. Non li conosco tutti, quindi mi limito ad analizzare CouchDB. Faccio sempre riferimento al post suddetto per quanto riguarda le osservazioni sulle perfomance, dato che arriva da una fonte autorevole in materia:
“il tempo totale d’inserimento di 15 milioni di documenti è stato di 26 minuti”
Su una macchina di media potenza (32GB di RAM, 8 Core), è possibile caricare in SQL Server 70 milioni di documenti (per un totale di circa 100GB) in meno di 10 minuti.
“l’occupazione di memoria durante tutto il processo non ha mai superato i 50MB”
Ok qui non ci sono paragoni. CouchDb vince facile. SQL Server si prende tutta la memoria che può :-)
“Contro: il tempo totale di calcolo della view è stato di circa 17 ore”
Dato che una view in couchdb serve sopratutto per aggregare, la comparazione va fatta con Analysis Services e/o con la creazione di viste indicizzate o di tabelle di aggregazione per SQL Server. Non ho attualmente possibilità di confrontare due soluzioni equivalenti sui due diversi database, ma posso direi che 17 ore per 15M di documenti mi sembrano veramente tante. Sempre sulla macchina di prima, in 5 ore, riusciamo a gestire più di 100 milioni di righe, per un totale di diversi centinaia di GB, ivi compresa l’elaborazione di un cubo OLAP…
Questo tempo (17 ore), inoltre, sarebbe da moltiplicare per il numero di views da creare per aggregare i dati secondo diverse logiche.
Oltre a questo le funzioni di map/reduce che creano le view sono scritti in Javascript, abbandonando completamente l’idea di un approcio dichiarativo alla programmazione, rendendo molto più complessa la scrittura delle stesse per richieste non banali. A titolo di curiosità, la logica delle map/reduce è la stessa implementata (in particolare la reduce) nella definzione di User Defined Aggregate in SQL Server 2005 e successivi, quindi nulla di nuovo sotto il sole da questo punto di vista.
“Contro: lo spazio occupato dal database è di 21GB contro i 7.5GB di mysql”
Spazio a parte, direi che il problema è mysql che mi sembra di poter dire che ha evidenti limiti di scalabilità e performance (come ci ha detto Gabriele proprio alla UGIALT.NET Conference). Sarebbe stato interessante sapere se usando Oracle (tanto per rimanere su un db “di livello” che gira anche su Unix) si sarebbe fatta la stessa scelta, ossia lasciare il mondo relazionale per il mondo documentale.
“Pro: il costo d’inserimento dei documenti in CouchDB è costante”
Questo è sicuramente il punto interessante, posto che si accetti il limite di avere un set di query “fisse” da eseguire (in pratica le query che possono beneficiare delle viste disponibili). Nel caso di un database relazionale in cui si inserisca il grafo di un oggetto come documento XML non è possibile avere delle performance ottimali nell’inserimento dell’XML e quindi la sua decomposizione e/o aggregazione. Si può ovviamente girare intorno al problema rendendo asincrona l’operazione di decomposizione e di aggregazione, ma ovviamente non è la stessa cosa ed inoltre è tutto da fare a manina.
Per quanto concerne la scalabilità CouchDB si basa su un meccanismo di replica, esattamente com’è possibile fare con SQL Server :-) Non ho ancora visto nulla in dettaglio a riguardo quindi per ora non posso parlarne, ma c’è che si ottiene come risultato finale mi sembra molto molto simile ad una replica Peer-To-Peer e quindi anche qui nulla di particolarmente innovativo.
Conclusioni
A parte le critiche fatte in precendenza, CouchDb rappresenta un esperimento curioso alla quale non affiderei mai i miei dati, dato che non c’è garanzia di integrità, ma dalla qualche si può imparare qualcosa. Ad esempio da CouchDb e dai database documentali di questo tipo c’è sicuramente una cosa che vorrei vedere nei database relazionali e che è sicuramente tecnicamente possibile: avere anche nei DB relazionali la possibilità di definire dei percorsi “prefissati” di ricerca dati ed aggregazioni degli stessi (una sorta di equivalenza delle map/reduce) cosi da poter beneficare, per questi aspetti, della possibilità di avere un costo di aggiornamento di tali dati pre-aggregati molto basso…logartimico, appunto. In teoria attualmente la possibilità c’è (parlo di SQL Server pensado alle Indexed Views) ma è troppo limitata. Sarebbe molto bello poter avere una Indexed View che faccia uso di User Defined Aggregate, simulando in toto quello che fa il reduce in un database documentale, permettendo un aggiornamento delle aggregazioni precalcolate in tempi brevissimi.
Ultima cosa prima di concludere. Ovviamente uno dei grandi punti di forza di CouchDB è che è gratis. SQL Server Express è anch’esso gratuito, ma per poter fare alcune cose (come ad esempio la replica) è necessario la versione a pagamento. Da questo punto di vista c’è poco da dire (anche se io non scambierei mai l’integrità dei dati con la scalabilità e/o le performance…piuttosto spendo un pò di più, altrimenti prima o poi la pago).
Ok, direi che ho scritto abbastanza. Sono aperto a commenti e feedback di tutti i tipi.