Big Data Big Data … data that exceeds RDBMS capabilities … Big Data Origini Il termine BigData è stato “coniato” da Google intorno al 2004 per descrivere una quantità di dati talmente grande da non poter essere gestita ed elaborata con le tradizionali tecnologie, file-system e RBDMS, a sua disposizione In queste pubblicazioni Google indicò anche la sua soluzione al problema : ● GFS : un file-system distribuito ● MapReduce: un nuovo approccio alla programmazione ed all'esecuzione del codice Big Data Origini GFS è un file-system distribuito progettato da Google per la memorizzazione e la gestione di file di dimensioni enormi, dell'ordine di svariati Petabyte = 1.000 Tbyte. MapReduce è un framework che, traendo spunto da un approccio tipico dei linguaggi funzionali (LISP), propone un modello di programmazione (DataFlow Programming) che consente di produrre facilmente applicazioni estremamente scalabili. Ma prima di entrare nei dettagli della soluzione cerchiamo di capire il problema... Big Data Caratteristiche e difetti dei sistemi RDBMS I sistemi RDBMS sono basati su un rigido formalismo matematico che consente di: evidenziare le caratteristiche essenziali dei dati e le relazioni che tra essi intercorrono, eliminando tutte le ridondanze. Processo che prende il nome di normalizzazione e il cui risultato è un modello astratto dei dati, lo schema che definisce univocamente la struttura e il tipo dei dati. Per tale ragione i dati cui è stato applicato uno schema vengono definiti strutturati. Senza nulla togliere ai suoi innumerevoli pregi, il modello relazionale in ambito BigData presenta due difetti principali : ● rigidità dello schema ● “perdita di informazione” rispetto ai dati RAW Big Data Implementazione dello schema Il processo di normalizzazione porta a suddividere i dati in tabelle, in cui ogni riga rappresenta un'istanza di un dato. I sistemi RDBMS sono progettati per consentire un accesso sicuro e un aggiornamento continuo di ogni singolo record presente sul database. Questo rende necessario ● l'uso di lock, per garantire che tutte le informazioni interessate da una modifica siano protette da modifiche concorrenti ● l'uso di chiavi ed indici per individuare rapidamente la posizione dei record di interesse ● l'uso di JOIN, per consentire la correlazione di informazioni conservate in tabelle diverse (eventualmente su DB diversi, anche remoti) Big Data Conseguenze delle ottimizzazioni Alcune delle ottimizzazioni introdotte nel passaggio dal modello teorico a quello concreto dei sistemi RDBMS, hanno delle conseguenze negative rispetto ai BigData: ● lock → serializzazione degli accessi ai dati e limitazione al parallelismo ● indici ed accesso casuale ai record → random read access verso il disco ● JOIN → perdita di località dei dati ( eventuale attesa per il trasferimento degli stessi da DB remoti) Big Data una nuova prospettiva.... La soluzione proposta da Google per risolvere questi problemi è di invertire la logica : Se le dimensioni dei dati sono molto più grandi di quelle del codice è più efficiente spostare il codice dove sono i dati da elaborare In questo modo è possibile garantire: ● la località dei dati (evitando i tempi di attesa necessari al loro trasferimento) ● l'assenza di lock e quindi il massimo parallelismo possibile ● un accesso sequenziale ai dati ed al disco, con velocità prossime al transfer-rate massimo consentito dall'hardware e indipendenti dal seek-time ● l'assenza di uno schema rigido che consente maggiore flessibilità e riduce il rischio di “perdita di dati” (schema-on-read) ● un sistema scalabile e fault-tolerant Big Data Random Disk Access VS Sequential Disk Access Per farci un'idea dell'impatto reale dei limiti individuati da Google nei sistemi RDBMS, confrontiamo le velocità di accesso al disco in modalità: ● random-read (tipica dei sistemi RDBMS) ● sequential read (caratteristica di GFS + MapReduce) La differenza di velocità è di circa 3 ordini di grandezza. GFS + MapReduce RDBMS Big Data Tabella Riassuntiva : MapRed VS RDBMS RDBMS MapReduce Dimensione dati Gigabytes Petabytes Tipo di accesso Interattivo e batch batch Aggiornamenti Frequenti letture e scritture Una scrittura tante letture Struttura Schema rigido Schema dinamico Scalabilità Non lineare lineare Con scalabilità lineare si intende che: se raddoppio i dati raddoppia il tempo di esecuzione se raddoppio i dati e le macchine il tempo di esecuzione è costante MapReduce non è un sostituto dei tradizionali RDBMS ma è complementare ad essi. Hadoop here he comes! Tra il 2003 e il 2004 Google fece alcune pubblicazioni in cui annunciò al pubblico questi suoi risultati, mantenendo tuttavia chiuso il codice delle proprie implementazioni. Esplose così la rincorsa alle emulazioni che vide vincitore Hadoop, una implementazione open-source in Java sviluppata da Dough Cutting, l'autore di Lucene. Dopo la prima release Yahoo! che lavorava ad un progetto analogo si rese conto che Hadoop era più efficiente, abbandonò il suo progetto ed ingaggio Dough Cutting per proseguire lo sviluppo di Hadoop. Il progetto rimase open-source per attirare la collaborazione di altre aziende e consentire di competere con il sistema sviluppato da Google. Hadoop tratti distintivi I principali di Hadoop riflettono, ovviamente, le sue origini e si rifanno alle soluzioni proposte da Google : Accessibilità: è in grado di funzionare sia su grandi cluster di macchine a basso costo (COTS), sia su infrastrutture cloud come Amazon EC2; Robustezza: ha funzionalità innate di fault-tolerance; essendo stato pensato, sin dal principio, per gestire eventuali malfunzionamenti dell'hardware (tipici dei sitemi COTS) e quindi ; Scalabilità: è in grado di scalare linearmente con l'aumentare dei dati da gestire; raddoppiando il numero dei nodi è in grado di processare il doppio dei dati nello stesso tempo; Semplicità: consente di scrivere facilmente e in maniera affidabile codice che viene automaticamente distribuito ed eseguito su un cluster (indipendentemente dal numero di macchine, da un minimo di 1 fino ad N ) Hadoop Componenti Fondamentali Il framework Hadoop si compone di tre elementi: • Commons : un insieme di utility su cui si basano gli altri componenti • HDFS : un file-system distribuito utilizzato per la memorizzazione dei dati • Map-Reduce: il framework vero e proprio per il supporto alla programmazione distribuita Hadoop HDFS HDFS (acronimo di Hadoop Distributed File System ) è un file system distribuito progettato sul modello del Google File System ed orientato alla gestione file di grandi dimensioni, dell'ordine dei petabyte 1PB = 1.000 Terabyte = 1.000.000 Gigabyte HDFS Un file-system block oriented E' un file-system minimale, ispirato al Google FS, sviluppato per supportare Hadoop. Dunque con pochi semplici obiettivi da realizzare: ● possibilità di gestire file di dimensione superiore al petabyte ● alta velocità in lettura/scrittura (prossima al max-transfer rate del disco) ● fault-tolerance ● ridondanza dei dati Per far questo ogni file è rappresentato come un insieme non contiguo di blocchi e a differenza di un file-system tradizionale ciascun blocco non contiene informazioni relative al file, ai permessi, ai blocchi adiacenti o altri tipi di metadati, ma solo dati RAW. HDFS Un file-system block oriented Un'altra differenza fondamentale, per ridurre l'impatto del seek-time sull'accesso al disco, è la dimensione dei blocchi. In un file-system tradizionale la dimensioni tipiche dei blocchi variano tra 512 bytes e 4Kbytes, su HDFS le dimensioni tipiche variano tra 64Mbyte e 256Mbyte. Inoltre ogni singolo blocco può essere replicato su più macchine diverse, garantendo così un semplice ma efficace meccanismo di ridondanza che aumenta la sicurezza del sistema. HDFS architettura interna HDFS è caratterizzato da un'architettura interna di tipo master-slave: ● il master, detto NameNode, ha il compito di conservare e gestire la struttura del file-system e in generale tutto il sistema di metadati: ● suddivisione in cartelle (e loro gestione) ● posizionamento logico dei file nelle cartelle ● posizionamento fisico dei file sulle macchine (suddivisione in blocchi e posizionamento di ogni replica di ogni singolo blocco) ● ● regole di accesso (molto semplificate rispetto a Posix) gli slave, detti DataNode, hanno invece il solo compito di memorizzare i blocchi e consentirne l'accesso in lettura/scrittura alla massima velocità possibile HDFS Architettura interna Hadoop MapReduce MapReduce si rifà ad un modello di programmazione tipico dei linguaggi funzionali, ed in particolare del Lisp (ideato nel 1958 da John McCarthy). In Lisp esistono infatti due funzioni, mapcar e reduce, che si occupano di : mapcar : applicare una generica funzione ad ogni elemento di un insieme; reduce : applicare un operatore di aggregazione agli elementi di un insieme per combinarne i valori ( restituendo solitamente un insieme con un numero ridotto di elementi) MapReduce Architettura del Framework Job Tracker Master Slave Task Tracker Slave Task Tracker Slave Task Tracker MapReduce Architettura del Framework Come HDFS, il framework MapReduce è costituito da più tool organizzati secondo un'architettura master-slave: ● il nodo master, o JobTracker, ha il compito di gestire il ciclo di vita dei job : ● permanenza in coda ● interazione con il NameNode ● suddivisione in task ● scheduling del job ● riesecuzione di eventuali task falliti ● notifica sul progresso/fine del job MapReduce Architettura del Framework ● i nodi slave, o TaskTracker, hanno il compito di gestire l'esecuzione dei singoli task : ● configurazione ● avvio e gestione di un numero limitato di tentativi ● notifica del progredire dell'elaborazione ● notifica del completamento / fallimento del task MapReduce MapCar Operation Facciamo un semplice esempio per capire meglio il funzionamento di queste funzioni. Date due liste di numeri e vogliamo calcolare la somma dei rispettivi elementi. X 6 2 4 3 2 4 2 ... Y 3 8 1 3 7 5 1 ... X+Y 9 10 5 6 9 9 3 ... MapReduce ... in stile procedurale Con i linguaggi di programmazione procedurali diciamo al compilatore cosa deve fare. Tipicamente per un problema di questo tipo si definisce un ciclo e si indica al compilatore ogni singola operazione da eseguire ad ogni ripetizione ● inizializza un contatore prendi la prima coppia di numeri ● calcola la somma ● scrivi il risultato nella nuova lista ● incrementa il contatore ● prendi la seconda coppia di numeri ● calcola la somma ● scrivi il risultato nella nuova lista ● … ● … ● 3 8 1 3 7 5 6 2 4 3 2 4 9 10 5 6 9 9 MapReduce ...in stile funzionale: es. mapcar in Lisp Con i linguaggi di programmazione funzionali diciamo al compilatore come calcolare determinati risultati ma lasciamo a lui il compito di decidere come farlo. Tipicamente in un problema di questo 3 6 9 8 2 10 i due valori a sinistra. 1 4 5 In questo modo il compilatore può 3 3 6 7 2 9 5 4 9 tipo si dice al compilatore: il valore a destra è ottenuto sommando scegliere la strategia con cui realizzare le operazioni. Ad esempio in parallelo se ha più processori a disposizione. deve MapReduce Reduce L'operazione di Reduce combina gli elementi di un insieme e, in generale, restituisce un insieme con un numero di elementi minore. Ad esempio potremmo utilizzare Reduce per sapere quanti elementi erano dispari e quanti pari, oppure quanti risultati erano minori di 8: X+Y 9 10 5 6 9 9 Reduce(' count(pari/dispari) ', X +Y) Reduce ( ' count(<8 )', X + Y ) 3 ... 2 5 3 4 MapReduce Job Come il nome del framework suggerisce, l'unità elementare di programmazione in Hadoop, denominata JOB, è data dalla combinazione di un operazione di map e di un'operazione di reduce. In alcuni casi è possibile/conveniente omettere l'operazione di Reduce. In tal caso i Job sono detti map-only. Non è invece possibile omettere l'operazione di Map, anche se non altera in alcun modo i dati, una funzione di map va sempre specificata. Quindi un Job hadoop realizza un parallelismo di tipo SIMD (Single Instruction Multiple Data) in quanto applica in parallelo una stessa trasformazione su tutti i blocchi che costituiscono il file di input. MapReduce map + reduce Input (map (reduce '( )) ( map <op> ' ( k-v , k-v, k-v ) ) ( ) ( k-v , k-v, k-v ) '( )) ( reduce <op> ' ( k-v , k-v, k-v ) ) Result MapReduce Ciclo di vita di un Job MapReduce Word Count MapReduce Definizione formale di un Job Un job è costituito da un'operazione di map ed una di reduce. Formalmente queste si definiscono nel seguente modo: map : (K1, V1) → list(K2, V2) reduce: (K2, list(V2)) → list(K3, V3) dove (K,V) indica una generica coppia chiave valore. Grazie a questa definizione formale e sfruttando i generics di Java il Framework Hadoop è in grado di gestire ed eseguire qualunque Job indipendentemente dal tipo di dati reale su cui opererà. Definizione formale Job Vincoli sui tipi dei dati L'analisi del ciclo di vita di un Job ha illustrato una sequenza di operazioni che vengono effettuate sui dati prima, durante e dopo l'esecuzione delle operazioni di map e reduce. 1. i dati vengono letti dalla sorgente (solitamente un file HDFS) e passati al map 2. i dati emessi dal map vengono ordinati e raggruppati e trasferiti al reduce 3. i dati emessi dal reduce vengono salvati sulla destinazione (solitamente un file HDFS) Queste operazioni pongono alcuni vincoli (relativamente leggeri) sui tipi di dati su cui le operazioni di map e reduce possono operare. Definizione formale Job Vincoli sui tipi dei dati I vincoli imposti sui dati sono: ● deve esistere un meccanismo standardizzato per la lettura/scrittura dei dati ● deve esistere un meccanismo standardizzato per il confronto di due dati in modo da consentirne l'ordinamento In termini di linguaggio Java questo si traduce nell'implementazione di due interfacce ● Writable - void write(DataOutput d) void readFields(DataInput di) ● Comparable - int compareTo(T o) Che per semplicità vengono raggruppate nell'interfaccia WritableComparable. Definizione formale Job Tipi di dati nativi Il framework fornisce dei tipi che rappresentano i tipi nativi di Java e che implementano queste interfacce: ● int → IntWritable ● long → LongWritable ● … ● String → Text Qualora si dovesse aver necessità di tipi personalizzati sarà necessario implementare queste interfacce per renderli compatibili con il framework. MapReduce Dai dati RAW alle coppie Chiave-Valore Input Format Logical HDFS Input Format Job Input Format Da RAW a WritableComparable Un InputFormat svolge due compiti fondamentali ● suddividere i dati di input in un insieme di split (ciascuno dei quali contiene un certo numero di record) ● fornire un RecordReader che legga i dati RAW presenti in uno split e restituisca degli oggetti di tipo WritableComparable Non bisogna confondere gli split con i blocchi HDFS. Gli split rappresentano un gruppo di record, ovvero una divisione logica dell'input, mentre i blocchi rappresentano una suddivisione fisica tipica di un particolare formato di memorizzazione. Uno split può indicare anche un gruppo di righe in una tabella di un DB. Input Format Principali input format forniti dal framework I principali InputFormat forniti dal framework sono: ● FileInputFormat – rappresenta un file su HDFS ed è in grado di gestire correttamente la presenza di split a cavallo tra due blocchi. ● TextInputFormat – discende da FileInputFormat e fornisce un RecordReader che restituisce i dati in formato (LongWritable, Text) ● [LongWritable] = distanza in byte dall'inizio del file ● key ● value [Text] = una riga di testo KeyValueTextInputFormat - discende da FileInputFormat; usato per file testuali in cui ogni riga rappresenta una coppia chiave valore delimitata da un separatore; restituisce i dati in formato (Text, Text) ● key [Text] ● value [Text] = una stringa rappresentante la chiave = una stringa rappresentante il valore Output Format Da WritableComparable a RAW Gli OutputFormat sono organizzati in maniera duale agli InputFormat ed eseguono l'operazione inversa. Una nota va spesa sul TextOutputFormat che converte i dati in stringhe, chiamando il metodo toString() e li salva su file. Nel farlo tuttavia memorizza in ogni riga una coppia chiave valore, per cui il corrispondente TextInputFormat. InputFormat è il KeyValueTextInputFormat e non il MapReduce Da RAW a Result MapReduce Word Count MapReduce Word Count : Pseudocodice InputFormat = KeyValueTextInputFormat Map input: key = linea di testo value = “” map(String key, String value): for each word w in key: EmitIntermediate(w, "1"); [..Framework .. Shuffle & Sort ] Reduce input: key = word values = lista di occorrenze es: {1, 1, 1, ….. } reduce(String key, Iterator values): int result = 0; for each v in values: result = result + v; Emit( key, result); MapReduce Algoritmi Complessi In generale non è possibile tradurre un algoritmo complesso in un singolo Job e quest'ultimo va visto come una singola unità di trasformazione dei dati di ingresso. L'algoritmo complessivo sarà dunque costituito da un grafo orientato (DAG: Direct Acyclic Graph) in cui ogni nodo rappresenta un singolo Job. Input Output MapReduce Driver L'esecuzione di un algoritmo complesso, costituito da un DAG di Job, consente di realizzare un parallelismo più ampio, indicato con il termine MIMD (Multiple Instructions Multiple Data ) in quanto applica più operazioni contemporaneamente su più dati. Spesso per eseguire un algoritmo complesso e controllare l'intera esecuzione del DAG viene scritto un tool apposito, che viene denominato Driver per sottolineare il fatto che pilota l'intera pipeline. Questo modello di programmazione viene denominato DataFlow Programming, in quanto descrive il percorso che i dati seguono nell'attraversare le varie operazioni o job. Interoperabilità Hadoop ed altri linguaggi di programmazione Hadoop è scritto in Java ed è quindi naturale l'interazione più diretta avvenga attraverso questo linguaggio. Tuttavia Hadoop offre due meccanismi che consentono la scrittura di Job Hadoop in molti altri linguaggi di programmazione. Questi due meccanismi sono ● Hadoop Streaming – che fa uso degli stream per il passaggio delle coppie chiave valore ● Hadoop Pipes – che usa dei socket per il passaggio delle coppie chiave valore Interoperabilità Hadoop Streaming Hadoop Streaming uso gli stream di Unix (stdin e stdout) come interfaccia tra il framework e il Job, che dunque può esser scritto in un qualunque linguaggio che sia in grado di leggere dallo standard input e di scrivere sullo standard output. Hadoop Streaming ha una “visione” text-oriented dei dati, per cui ● il Task converte i dati di input in righe di testo e li passa al map attraverso lo standard input ● il map li elabora e scrive i risultati sullo standard output ● questi vengono poi raccolti e ordinati (come testo) ● un altro Task suddivide i dati provenienti dai vari map in righe di testo e le inoltra al reduce attraverso lo standard input ● il reduce le elabora e scrive i risultati sullo standard output ● questi vengono raccolti e memorizzati su file Interoperabilità Hadoop Pipes Hadoop Pipes funziona in maniera simile a Streaming ma utilizza dei socket per il passaggio dei dati ed è principalmente orientata agli sviluppatori C++. Sia la chiave che il valore vengono forniti come stringhe ( std::string ). Un Job in chiaveC++ deve sempre fornire due classi Mapper e Reducer ma i metodi map() e reduce() hanno un solo parametro d'ingresso MapContext e ReduceContext i cui metodi principali sono const std::string& getInputKey(); const std::string& getInputValue(); void emit(const std::string& key, const std::string& value); il ReduceContext ha il metodo addizionale bool nextValue(); per passare al valore successivo. Interoperabilità Python + Hadoop = Pydoop A partire dal 2009 il CRS4 ha sviluppato Pydoop[1], una API per consentire la scrittura di Job MapReduce e l'interazione con HDFS anche a chi utilizza il linguaggio Python. È possibile scaricarlo da: http://pydoop.sourceforge.net/docs/ Ed è compatibile con le maggiori distribuzioni di Hadoop: Apache Hadoop 0.20.2, 1.0.4, 1.1.2, 1.2.1 or 2.2.0 CDH 3u{4,5} or 4.{2,3,4,5}.0 [1] S. Leo and G. Zanetti. Pydoop: a Python MapReduce and HDFS API for Hadoop. International Symposium on High Performance Distributed Computing, 819-825, 2010. In Proceedings of the 19th ACM Pydoop Architettura del sistema Pydoop utilizza Hadoop Pipes e le librerie Boost per realizzare la comunicazione tra i codice Python ed il framework Map Reduce o l'HDFS Pydoop Python API Attraverso le API di Pydoop è possibile avere accesso diretto dal codice Python alla gran parte delle funzionalità offerte dal framework Hadoop: pydoop.pipes — MapReduce API pydoop.hdfs — HDFS API pydoop.hdfs.path — Path Name Manipulations pydoop.hdfs.fs — File System Handles pydoop.hdfs.file — HDFS File Objects pydoop.utils — Utility Functions pydoop.jc — Pydoop Script Configuration Access pydoop.hadut – Run Hadoop Shell Commands http://pydoop.sourceforge.net/docs/api_docs/index.html Pydoop pydoop script Tuttavia l'accesso diretto alle API Pydoop richiede una profonda conoscenza del sistema e delle tecnologie utilizzate per integrarlo con Hadoop. Per semplificare la scrittura di job è stato creato Pydoop Script, che consente di scrivere un Job Hadoop semplicemente definendo le due funzioni mappre e reducer. A questo punto per lanciare il Job sarà sufficiente digitare sulla shell il comando : pydoop script myJob.py hdfs_input hdfs_output Pydoop Script lancia il Job utilizzando una configurazione di default. E' possibile modificare la configurazione specificando sulla riga di comando i parametri nel formato chiave=valore secondo lo standard tipico di Java (opzione -D property_name=property_value ). Pydoop WordCount Utilizzando Pydoop script, possiamo realizzare il WordCount in Python, un unico script wordcount.py in cui definiamo le funzioni mapper e reducer def mapper(key, text, writer): for word in text.split(): writer.emit(word, "1") def reducer(word, icounts, writer): writer.emit(word, sum(map(int, icounts))) Per eseguire lo script è sufficiente digitare sul terminale pydoop script wordcount.py INPUT_PATH OUTPUT_PATH dove INPUT_PATH e OUTPUT_PATH sono dei path all'interno dell'HDFS
© Copyright 2024 Paperzz