CouchDB Performance

11 October, 2009 (10:36) | couchdb, erlang, programming | 6 comments

Finalmente sono riuscito a provare CouchDB con una quantità di dati interessante e su una macchina interessante. L’obiettivo era quello di verificare se CouchDB poteva reggere un carico di milioni di documenti e se il tempo per il calcolo delle view è effettivamente incrementale e con complessità logaritmica.

Una brevissima introduzione: CouchDB è un database documentale, ogni documento (il record di una tabella in un database relazionale) è insieme di coppie chiave/valore, non esiste uno schema dei dati, ogni documento può contenere qualsiasi insieme di coppie chiave/valore, ad ogni database possono essere associati una o più view (query in un database relazione), ogni view è composta da due funzioni javascript, una funzione map che consente di trasformare ogni documento contenuto nel database in un altro insieme di coppie chiave/valore e di associarle ad una chiave, e una funzione reduce (opzionale) che prende in pasto l’output della funzione map precedente raggruppata per chiave (una sorta di group by dei database relazionali) e che può essere utilizzata per computare valori aggregati

Un piccolissimo esempio che è molto vicino al test che ho fatto. Supponiamo di avere un database (ovvero un insieme di documenti) di questo tipo

[
{
"url": "http://salute.corriere.it/news/some-news.html",
"visits": 3
},
{
"url": "http://lavoro.corriere.it/index.html",
"visits": 10
}
]

Ovvero associamo ad ogni url il numero di volte che è stata visitata. Noi vogliamo sapere il numero di visite per ogni “sezione” delle url presenti nel nostro database. Definiamo intuitivamente il significato di “sezione” dicendo che l’url “http://salute.corriere.it/news/some-news.html” appartiene a tre sezioni “corriere.it”, “salute.corriere.it” e “salute.corriere.it/news”. Supponiamo di avere a disposizione una funzione “forEachSectionOf(url,doSomething)” che prede in pasto una url e richiama la funzione doSomething per ogni sezione dell’url passandogli la sezione stessa come parametro. La nostra funzione map sarebbe una cosa del tipo

function(doc) {
forEachSectionOf(doc['url'], function(section) {
emit(section, doc['visits'])
});
}

La funzione emit viene chiamata tutte le volte che vi vuole produrre un output, il primo parametro è la chiave del risultato e il secondo è il valore (sia la chiave che il valore possono essere strutture dati complesse, non devono essere per forza valori come in questo caso, ma questa è un’altra storia). L’output prodotto dalla map applicata al database di cui sopra sarà

[
{ "corriere.it": 3 },
{ "salute.corriere.it": 3 },
{ "salute.corriere.it/news": 3 },
{ "corriere.it": 10 },
{ "lavoro.corriere.it": 10 }
]

Abbiamo detto che l’imput alla reduce è l’output della map raggruppato per chiave, quindi

[
{ "corriere.it": [ 3, 10 ] },
{ “salute.corriere.it”: [ 3 ] },
{ “salute.corriere.it/news”: [ 3 ] },
{ “lavoro.corriere.it”: [ 10 ] }
]

Ricordandoci che vogliamo calcolare il numero di visite per ogni sezione, la reduce è molto semplice

function(keys, values, rereduce) {
return sum(values)
}

Che produce il risultato atteso

[
{ "corriere.it": 13 },
{ "salute.corriere.it": 3 },
{ "salute.corriere.it/news": 3 },
{ "lavoro.corriere.it": 10 }
]

Il grosso vantaggio di CouchDB in termini di perfomance è che le view vengono calcolate in maniera incrementale, ovvero tutte le volte che interroghi una view CouchDB ricalcola solo i valori relativi ai documenti che sono cambiati o che sono stati aggiunti dall’ultima volta che la stessa view era stata calcolata (la cosa più vicina a questa nel mondo dei database relazionali sono le materialized view di Oracle). Inoltre il costo del calcolo dell’incremento dovrebbe aumentare in maniera logaritmica rispetto all’aumentare della dimensione del database.

Ho voluto toccare con mano e quindi ho fatto il seguente esperimento:

  • Fetch di 10000 record da una tabella di mysql contenente 15 milioni di record
  • Store di ogni record come documento in CouchDB (i documenti non sono stati salvati singolarmente, ma in modalità batch che è molto più performante)
  • Query della view di CouchDB che equivale al ricalcolo della view stessa

Ho misurato ognuna delle tre fasi e l’ho ripetuto fino a consumare tutti e 15 i milioni di record, di seguito i risultati

urls_per_domain.times

  • Pro: la complessità del ricalcolo della view è effettivamente logaritmico
  • Pro: il costo d’inserimento dei documenti in CouchDB è costante (la struttura dati utilizzata è append-only, quindi c’era da aspettarselo, ma fa comunque piacere verificarlo)
  • Pro: una volta calcolata la view, i tempi di risposta sono stupefacenti, praticamente istantanei
  • Pro: il tempo totale d’inserimento di 15 milioni di documenti è stato di 26 minuti
  • Pro: l’occupazione di memoria durante tutto il processo non ha mai superato i 50MB
  • Contro: il tempo totale di calcolo della view è stato di circa 17 ore. Bisogna tener conto che le view vengono calcolate da funzioni javascript in un processo separato e che la comunicazione fra CouchDB e l’interprete javascript è stdin/stdout, quindi a parte la velocità dell’interprete javascript (di default spidermonkey) c’è anche un costo notevole di serializzazione/deserializzazione. Scommetto che scrivendo le map/reduce direttamente in Erlang questo numero cambierebbe sensibilmente
  • Contro: lo spazio occupato dal database è di 21GB contro i 7.5GB di mysql (anche se ci metterei un bel chissene visto il costo degli storage)
  • Contro: lo spazio occupato dalla view è di 32GB (again chissene)
  • Contro: per ogni database CouchDB usa uno ed un solo processore, quindi anche se avete 16 processori come nel mio caso, non ve ne fate niente a meno di non avere database multipli. Il modello map/reduce implementato da CouchDB potrebbe tranquillamente consentire il partizionamento dei dati su più database, ma attualmente gli sviluppatori si stanno concentrando solamente sulla replicazione, se volete dovete implementare voi il meccanismo di reduce finale

Conclusione: se vi trovate in una situazione per cui avete una grande quantità di dati e le query che fate non cambiano spesso, un database come CouchDB potrebbe essere un bel passo in avanti rispetto ad un database tradizionale

Code Katas

4 October, 2009 (17:55) | kata, programming | No comments

Coders at Work Avendo apprezzato Peter Seibel in “Founders at Work” ( consigliatissimo) in questi giorni sto leggendo con piacere il suo ultimo lavoro “Coders at Work”, stando all’ultimo suo post anche Joel Spolsky lo sta leggendo.

Joel ha elogiato Jamie Zawinski (uno dei programmatori intervistati da Seibel nel suo libro) per la sua capacità di scrivere velocemente codice funzionante e fruibile da un utente finale. Joel ha chiamato questo tipo di programmatore “Duct Tape Programmer”, un’etichetta che ha suscitato un bel polverone.

Insomma si parla del solito tradeoff “time, quality, money – pick two”, che poi ufficialmente si traduce sempre in “scegliamo tempo e denaro, per la qualità speriamo di farla franca”.

Cosa centra tutto questo con i kata? Quando qualcuno si lamenta del fatto che le tecniche che propongo non sono praticabili nella loro realtà perchè non c’è il tempo (la solita storia del “bello, ma da noi non si può fare”), mi ricordo quando anch’io mi lamentavo della stessa cosa, vedevo la carenza di tempo come la prima ragione di tutti i miei fallimenti, poi però ho iniziato a chiedermi: “se fino ad oggi non ho mai avuto tempo per fare le cose bene, come faccio ad essere sicuro di saperle fare? Come faccio ad essere sicuro di riuscire a scrivere codice pulito se non ho mai avuto il tempo di scriverlo?”… Interessante quesito che ci porta ai kata e alla nozione generale di esercizio

Gli esercizi di programmazione hanno due obiettivi

  • Darci la possibilità di lavorare in un ambiente controllato e privo di vincoli. Il fallimento è visto in maniera positiva, venire a conoscenza dei nostri limiti è l’unico modo per poterli superare
  • Visto che non avrete mai il tempo che volete, l’unica cosa che potete fare è diventare più veloci nello scrivere codice di qualità

Il secondo punto ci riporta al tema iniziale: dove sta scritto che per scrivere del buon codice serve tanto tempo? Io sono fermamente convinto che l’unica ragione per la quale intuitivamente lo pensiamo è perchè quando ci proviamo facciamo fatica, e l’unica ragione per la quale facciamo fatica è perchè non siamo abituati/allenati.

Ultimamente ho dedicato un po’ di tempo a pensare ai kata e ad esercitarmi, venerdì della settimana scorsa ho partecipato al primo javascript camp italiano organizzato da Ideato e ho presentato il kata “the game of life” in javascript, è stato molto divertente ed istruttivo

Gli ingredienti per un buon kata/esercizio sono

  • Un problema sfidante per le vostre capacità e per la vostra preparazione
  • Una o più persone pronte a darvi il loro feedback, fondamentale per capire come e dove migliorarsi
  • Ripetere l’esercizio più e più volte finchè sentite che ormai il problema non ha più niente da insegnarvi

Il mio consiglio è di provare e di mettervi in gioco, per quanto mi riguarda le prossime mosse saranno: pubblicare gli screencast dei miei kata ed organizzare dei gruppi di esercizio/studio, se siete interessati contattatemi o iscrivetevi milano-codingdojo (non preoccupatevi se non siete di Milano, stiamo organizzandoci per fare qualcosa di distribuito ;-))

Non c’entra niente, ma…

6 August, 2009 (12:25) | fun, stuff | 1 comment

Stimolato dal professore l’ho fatto anch’io. Di solito i risultati di questi test su di me funzionano poco, quando facevo il militare ci facevano regolarmente dei test attitudinali/caratteriali/? e regolarmente su 12 profili possibili io risultavo appartenere in egual misura a 6/7 profili contemporaneamente… :-)

My Political Views
I am a centrist social moderate
Left: 0.14, Libertarian: 0.95

Splittare le User Story come strategia di business

4 August, 2009 (09:30) | agile, business, user story | 1 comment

Oggi ho visto l’ennesimo strepitoso intervento di Eric Ries a proposito della pratica del MVP (Minimum Viable Product). Il problema è molto chiaro: la nostra vision di prodotto potrebbe portare ad un business non sostenibile; la strategia proposta lo è altrettanto: trovare il minimo set di funzionalità in grado di poterci dare un feedback reale sulle esigenze dei nostri utenti e in che misura il nostro prodotto le soddisfa.

Maggior conoscenza acquisiamo sui nostri utenti, maggiori saranno le probabilità che la prossima funzionalità pensata/implementata sarà apprezzata (aumentando il valore dell’applicazione). Minore sarà il tempo/costo d’implementazione di una nuova funzionalità, maggiori saranno le funzionalità che potremmo utilizzare per acquisire conoscenza sui nostri utenti

Cosa possiamo fare per minimizzare i tempi/costi d’implementazione? Chiaramente possiamo intervenire da un punto di vista tecnico migliorando la nostra capacità di produrre software, mantenendo sempre pulita la nostra base di codice, migliorando costantemente gli aspetti di project automation e di autonomation, ecc… insomma avete capito

L’altra cosa che possiamo fare è splittare in maniera creativa ed aggressiva le user story esistenti del prodotto. Prima possiamo iniziare a selezionere un minimo set di user story in grado di dare dignità di prodotto al nostro progetto, poi possiamo iniziare a considerarle singolarmente e a capire cosa possiamo togliere da ogni user story conservando la possibilità di poter utilizzare il risultato come MVP

Un esempio? Avevo 10 giorni (nessuna domanda please sul perchè di questo numero magico…) di tempo per realizzare un’applicativo piuttosto complesso, ovviamente c’era di mezzo uno storage, ovviamente il cliente aveva richiesto un’interfaccia di amministrazione/gestione di questo storage e ovviamente non aveva tralasciato nessun “goodies”: utenti, gruppi, ruoli, permessi, editing, viste configurabili sui dati, ecc… praticamente mi sarebbero serviti molti più giorni di quelli a disposizione solo per questa parte accessoria del sistema. Volevo iniziare dal vero prodotto, ma volevo anche che il cliente lo potesse utilizzare da subito, così ho installato phpMyAdmin e ho fatto vedere al cliente come utilizzarlo: ho creato utenti, viste, maschere, ecc… alla fine il cliente mi ha detto che è esattamente quello che voleva :-D Ok, sono stato fortunato, ma è stata una vera epifania

Un’altro esempio un po’ più creativo? Kent Beck qualche hanno fa ha tenuto un workshop sullo split delle user story usando un esempio alquanto “ardito”: il gioco del tetris… qual’è secondo voi la prima user story per Beck? Una colonna, un quadratino 1×1 che scende a velocià costante, quando un quadratino tocca il fondo parte un altro quadratino, il nuovo fondo è il quadratino precedente, quando il fondo arriva in cima alla colonna “game over” :-D

Ancora una volta una conferma del fatto che il business (il problem team come lo chiama Ries) e la tecnologia (solution team) non si possono/devono ignorare a vicenda