PIG - CRS4dassia.crs4.it/wp-content/uploads/2014/11/Slide_PIG.pdf · 2015. 2. 10. · Pig...

of 91/91
PIG Pig
  • date post

    12-Sep-2020
  • Category

    Documents

  • view

    18
  • download

    0

Embed Size (px)

Transcript of PIG - CRS4dassia.crs4.it/wp-content/uploads/2014/11/Slide_PIG.pdf · 2015. 2. 10. · Pig...

  • PIG

    Pig

  • Pig

    Cosa è Pig ?

    Pig è una piattaforma per l'analisie l'elaborazione parallela di grandi quantità di dati.

    Gli elementi principali di questa piattaforma sono

    ● un linguaggio di alto livello Pig Latin● un compilatore/interprete che traduce gli scripts

    in sequenze di Job MapReduce● un'infrastruttura per l'esecuzione e la gestione dei Job● una shell per l'esecuzione interattiva di comandi Pig Latin

    E' pratica comune identificare Pig con il linguaggio Pig-Latin.

  • Pig

    Pig-Latin: un dataflow language

    Il linguaggio Pig-Latin è stato sviluppato per consentire allo sviluppatore di ragionare ad alto livello e liberarlo dall'onere di convertire il codice in termini di MapReduce.

    Pig-Latin è un linguaggio di scripting ad alto livello orientato al dataflow programming,in cui tutta l'attenzione è posta sui dati, visti come un flusso di informazioni che attraversando

    un “circuito di istruzioni” viene trasformato e modellato, in maniera simile a quanto accade alla musica che attraversa i circuiti di un impianto Hi-Fi prima di esser riprodotta in alta qualità.

    Questo approccio è diametralmente opposto a quello tradizionale, in cui si progetta il software come un flusso di istruzioni che elabora e processa dei dati esterni.

  • Pig

    Pig-Latin: un dataflow language

    Pig-Latin si ispira ai linguaggi funzionali e come tale cerca di

    esprimere le trasformazioni da eseguire sui dati ma senza dettagliare il come eseguirle

    Le trasformazioni sono espresse mediante operatori ispirati all'SQL

    join, sort, filter, group by …

    e uno script assume la forma di un dataflow graph in cui ogni trasformazione viene eseguita nel momento in cui viene raggiunta dal flusso di dati, in contrasto con il modello tradizionale in cui ogni operazione viene eseguita non appena il program

    counter la raggiunge.

  • Pig

    Pig-Latin: un dataflow language

    Questo flusso di trasformazione viene automaticamente tradotto dal compilatore in un insieme di job MapReduce ed eseguito in parallelo su un cluster.

    Alcune operazioni possono facilmente essere espresse in termini di MapReduce

    ● [group by | order by ] == shuffle + reduce● [ filter | select ] → map

    per altre, come ad esempio il join, la traduzione non è così immediata.Tuttavia Pig offre delle implementazioni in grado di garantire performance quasi ottimali anche in presenza di dati fortemente sbilanciati (skewed).

  • Pig

    Perché Pig ?

    Perché è stato realizzato Pig ? Perché può essere conveniente utilizzarlo ?

    ● Il modello di programmazione MapReduce prevede tre step di elaborazione fortemente correlati: { map, shuffle-sort, reduce } ed apprendere l'arte di convertire gli algoritmi noti secondo questo paradigma non è semplice;

    ● Spesso è necessario ideare dei percorsi complessi anche per ottenere gli equivalenti MapReduce di operazioni considerate semplici;

    ● Questo rende MapReduce costoso sia in termini di sviluppo che di manutenzione ed eventuali riutilizzo del codice;

    ● Ottimizzare il codice MapReduce può diventare un'arte per pochi eletti;

    Pig cerca di porre rimedio a queste difficoltà, ponendosi come un linguaggio semplice ed utilizzabile anche da persone non-hacker ma “esperte” sui dati.

  • Pig

    Dove si posiziona nel panorama BigData ?

    Un tipico dataflow processing associato a sistemi BigData.Dove si posiziona Pig?

  • Pig

    Dove si posiziona nel panorama BigData ?

    ● raccolta : in questa fase Pig viene utilizzato come interfaccia verso i dati acquisiti da sorgenti diverse, sia batch che real-time o near real-time;

    ● esplorazione : Pig si rivela molto utile nella gestione di dati eterogenei e ancora grezzi, viene usato per preparare filtrare i dati spuri, creare dei metadati che aiutino a

    capire il contenuto ed il valore dei dati stessi, categorizzarli, classificarli e prepararli per l'integrazione con sistemi di warehouse e analytics;

    ● elaborazione : Pig viene spesso utilizzato per preparare i dati all'integrazione con altri sistemi, uniformandoli secondo schemi e/o knowledge-bases preesistenti e iniziando la trasformazione da non-strutturati a strutturati;

  • Pig

    Extract Transform Load (ETL)

    I maiali sono onnivori e digeriscono di tutto.

    Pig è stato progettato per lavorare bene anche con dati “sporchi” e non normalizzati, in cui gli schemi non sono noti o sono inconsistenti.

    Per tale ragione le operazioni tipiche svolte con Pig sono quelle di verifica della consistenza e della qualità dei dati e della loro preparazione per sistemi che si aspettando dati strutturati o semi-strutturati. Queste operazioni vengono sintetizzate con l'acronimo

    ETL (extract – transform – load)

  • Pig

    Interagire con Pig

    Pig prevede due modalità di esecuzione:

    ● interattiva : attraverso una shell dedicata, per l'esplorazione dei file e la progettazione degli script;

    ● batch : per l'esecuzione “in produzione” degli script;

    entrambe queste modalità possono essere eseguite in modalità local, per fare dei semplici esperimenti con piccoli file eseguendo i job sulla macchina locale, e

    mapreduce per utilizzare un cluster.

  • Pig

    Grunt : la shell

    La shell di Pig si chiama grunt , come ci ricorda il suo prompt:

    grunt>

    Grunt offre alcune funzionalità di una shell tradizionale (ma non tutte):● tab-completion sui comandi (non sui file )● command history● semplice supporto all'editing

    Non fornisce invece● pipes ● redirezione I/O● esecuzione in background

    Per uscire dalla shell è sufficiente dare il comando quit o digitare CTRL+D

  • Pig

    Grunt : la shell

    Pig lavora nativamente con l'HDFS, per questo Grunt consente di interagire direttamente con esso attraverso il comando fs, le cui opzioni sono le stesse offerte dal comando hadoop fs :

    ● mkdir● cp● du● ls● cat● rm● copyFromLocal● …

    in questo modo risulta molto semplice interagire con l'HDFS direttamente dalla shell o all'interno degli script.

  • Pig

    Grunt : la shell

    L'utilizzo più naturale di Grunt è l'inserimento interattivo di comandi Pig Latin.Questa modalità può essere utile per

    ● esplorare un nuovo dataset● scrivere il prototipo di un nuovo script● sperimentare diverse pipeline alla ricerca di ottimizzazioni

    Per migliorare l'interattività Pig esegue le elaborazioni solo in seguito ad un comando che prevede

    la generazione di un output (es. dump o store ).

    In questo modo è possibile inserire i diversi comandi che compongono lo script senza dover attendere l'elaborazione parziale dei dati e “lanciare” l'esecuzione dello script attraverso la generazione dei risultati finali.

  • Pig

    Grunt : la shell

    Grunt offre inoltre alcune semplici funzioni di controllo per l'esecuzione del codice, attraverso i tre comandi

    ● kill jobid: consente di terminare un particolare job● exec script: consente di eseguire uno script● run script: consente di eseguire uno script

    Gli ultimi due si differenziano per il fatto che

    ● il comando exec esegue lo script esternamente alla shell e mostra solo il risultato finale;● il comando run esegue lo script internamente alla shell, come se i comandi fossero digitati

    in modalità interattiva e dunque al termine dell'esecuzione risulteranno disponibili anche tutti i dati intermedi generati durante l'esecuzione.

  • Pig

    Strumenti di sviluppo

    La shell Grunt è un ottimo strumento per esplorare un nuovo dataset e sperimentare vari prototipi per un nuovo script, tuttavia non è molto adatta allo sviluppo ed alla manutenzione degli script.

    Per questo è dunque conveniente utilizzare dei comuni editor di testo.Esistono tuttavia dei plugin per alcuni dei principali editor (es. Vim, TextMate, Emacs, Eclipse) che forniscono funzionalità quali syntax-higlighting, auto completamento e in alcuni casi integrazione con il framework hadoop per l'esecuzione degli script dall'interno dell'ambiente di sviluppo.

  • Pig

    Strumenti di sviluppo

    Durante la parte pratica utilizzeremo un plugin che consente di scrivere e lanciare gli script Pig direttamente da browser.

  • Pig-Latin

    Introduzione al Linguaggio

  • Pig-Latin

    Introduzione al linguaggio

    Si è detto che è un dataflow language simile ad un linguaggio funzionale.Questo significa che non si dovrà pensare in termini di

    istruzioni su come eseguire le elaborazioni

    ma si dovrà organizzare il codice in termini di

    espressioni che indicano quali elaborazioni eseguire

    E' importante ricordare che ad una espressione è sempre associato un valore (e dunque un tipo).

    In Pig-Latin ogni espressione rappresenta un dataset o relazione, ed è concettualmente equivalente ad una tabella SQL contenente tanti record, ognuno dei quali suddiviso in un numero arbitrario di campi o colonne.

  • Pig-Latin

    Introduzione al linguaggio

    Data l'espressione

    input = load 'data'

    input è il nome dell'espressione ottenuta dal caricamento del dataset 'data'.Il nome della relazione è anche detto alias, ma a discapito delle apparenze un alias non è una variabile in quanto l'assegnamento è definitivo.

    Sebbene sia possibile riutilizzare un alias, questo equivale alla cancellazione della vecchia relazione ad alla creazione di una nuova relazione, con conseguenti ambiguità nell'interpretazione di uno script. E' dunque sconsigliato riutilizzare gli alias, in quanto potenziale fonte di errori.

    Le keywords di Pig non sono case-sensitive (es load == LOAD), mentre tutto il resto lo è, quindi è bene considerarlo un linguaggio case-sensitive.

  • Pig-Latin

    Introduzione al linguaggio

    Anche i campi (colonne) delle relazioni hanno dei nomi; questi assomigliano di più a delle variabili perché possono assumere valori diversi in base al record corrente. Tuttavia si distinguono dalle variabili in quanto non è possibile assegnare un valore ad un campo di un singolo record.

    Tutti i nomi devono iniziare con un carattere alfabetico, seguito da una sequenza arbitraria di caratteri (ASCII), numeri o '_' (underscore).

    In Pig Latin esistono due tipologie di commento

    ● quello multilinea di Java /* … */ (eventualmente anche su riga singola)

    ● quello su riga singola in stile SQL '--' (doppio meno)

  • Pig-Latin

    Il modello dati

    Come tutti i linguaggi anche Pig-Latin ha un suo modello dati con cui rappresentare le informazioni su cui deve operare.I tipi supportati si possono suddividere in due categorie: scalari e complessi.I tipi scalari sono gli equivalenti di alcuni tipi nativi in Java

    { int, long, float, double }chararray → Stringbytearray → byte[] (in Java) , Blob (in SQL)null → null in SQL

    I tipi complessi sono tre:

    map, tuple, bag

    e richiedono una descrizione più dettagliata.

  • Pig-Latin

    Il tipo Map

    Il tipo map modella un'associazione tra due elementi

    ● una chiave (chararray) utilizzata per l'indicizzare il contenuto della collezione● un valore (di tipo qualunque, scalare o complesso) utilizzato per contenere i singoli dati

    Può essere descritto da un'espressione letterale racchiusa tra parentesi quadre

    ['name' # 'bob' , 'age' # 25]

    Se non viene specificato un tipo, per default Pig assegna al valore il tipo bytearray.

    A differenza degli equivalenti per altri linguaggi come Java o C++, valori corrispondenti a chiavi diverse possono avere tipi diversi.

  • Pig-Latin

    Il tipo Tuple

    Una tupla è una sequenza ordinata di dimensione fissata di elementi, detti campi, ed è rappresentata da un'espressione letterale racchiusa tra parentesi tonde

    ( 'bob', 55 )

    E' equivalente ad una riga SQL in cui i campi corrispondono alle colonne di una tabella e possono contenere uno qualunque dei tipi supportati da Pig e ogni campo può essere di tipo diverso.

    E' possibile accedere ai campi per nome o per posizione tramite l'espressione $N dove N è la posizione del campo nella tupla.

    E' possibile associare ad una tupla uno schema che definisce il nome ed il tipo di ogni campo.

  • Pig-Latin

    Il tipo Bag

    Un bag è una collezione non ordinata di tuple, e può essere descritto da un'espressione letterale racchiusa tra parentesi graffe

    { ('bob', 55) , ('sally', 52), ('john', 25) }

    E' la più generale delle collezioni supportate in Pig e può salvare parte dei suoi dati su file in maniera automatica, mentre gli altri tipi complessi (map e tuple) devono necessariamente esserecontenuti in RAM.

    Può avere uno schema associato che descrive le tuple al suo interno.Tuttavia essendo un insieme non-ordinato non è possibile accedere ai campi per posizione.

  • Pig-Latin

    Schemi

    La filosofia di Pig è quella di “mangiare di tutto” dunque anche se una relazione non ha uno schema associato Pig cercherà di fare del suo meglio per stimare la tipologia di dati in base alle operazioni richieste nello script.Tuttavia, quando possibile, è bene definire lo schema perché aiuta Pig nel controllo degli errori e può aumentare le performance. Gli schemi possono essere ricorsivi.

    Il modo più semplice per comunicare a Pig uno schema è definirlo esplicitamente al caricamento dei dati

    dividends = load 'NYSE_dividends' as (exchange: chararray, symbol: chararray, date:chararray, dividend: float);

  • Pig-Latin

    Schemi

    Se si definisce uno schema Pig cercherà di adattare i dati allo schema fornito

    ● attraverso dei cast● inserendo dei null se mancano i dati● scartando eventuali campi in eccesso

    Se nella definizione dello schema si omettono i tipi dei dati

    dividends = load 'NYSE_dividends' as (exchange, symbol, date, dividend);

    Pig assegnerà a ciascun campo il tipo più generico, bytearray, e successivamente cercherà di stimare il tipo più adatto in base all'uso che ne verrà fatto all'interno dello script.

  • Pig-Latin

    Definizione di uno schema

    Per specificare lo schema di una relazione si deve usare la seguente sintassi

    Tipo Sintassi Esempioint int as ( a: int )

    long long as ( a: long )

    ... ... ...

    map map[ ] o map[tipo] as ( a: map[ ], b: map[int] )

    tuple tuple() o tuple( lista di campi) as ( a: tuple(), b: tuple(x: int, y: int ) )

    bag bag {} o bag{ t: (lista di campi ) } as ( a: bag{}, b: bag{ t: ( x:int, t:int) }

  • Pig-Latin

    Definizione di uno schema

    Nel caso relazioni utilizzate spesso, magari in più script, e con schemi complessi specificare ogni volta lo schema può essere scomodo e fonte di errori.

    In tal caso è possibile utilizzare per la memorizzazione dei dati su disco formati che conservino al

    loro interno lo schema oppure sistemi dedicati alla gestione degli schemi come HCatalog

    dividends = load 'NYSE_dividends' using HCatLoader();

    In generale è bene non mescolare le due modalità.

  • Pig-Latin

    Deduzione di uno schema

    Vediamo ora come Pig cerchi di dedurre lo schema di una relazione.

    daily = load 'NYSE_daily';calcs = foreach daily generate $7/1000, $3*100.0, SUBSTRING($0, 0,1), $3 - $6;

    Pur non essendo stato fornito uno schema Pig può assumere che

    ● $7 sia un intero perché diviso per un intero ● $3 sia un double perché moltiplicato per un double ● $0 sia un chararray perché si estrae una sottostringa ● $6 sia un numero perché utilizzato in una sottrazione. Tuttavia non sapendo se si tratta di un

    intero o di un double Pig sceglie l'ipotesi più prudente e assume sia un double

  • Pig-Latin

    Cast

    Il cast in Pig è analogo a quello in Java e segue la stessa sintassi

    daily = load 'NYSE_daily';calcs = foreach daily generate (int)$7 /1000, (double)$3 * 100.0,

    (chararray)SUBSTRING($0, 0,1), (double)$6 – (double)$3;

    e segue (quasi) le stesse regole:

    ● è possibile eseguire un cast automatico dal tipo più piccolo a quello più grande● il cast inverso grande → piccolo deve essere esplicito e può causare troncamenti● si può fare il cast da chararray a un tipo numerico ma se la stringa non ha un formato corretto

    il risultato sarà un null

  • Pig-Latin

    Supporto allo sviluppo

  • Pig-Latin

    Supporto allo sviluppo

    Pig-Latin include un set di comandi pensati per aiutare lo sviluppatore nell'esplorazione dei dati e nello sviluppo degli script

    ● describe → descrive lo schema associato ad una relazione● illustrate→ mostra alcuni record rappresentativi del dataset per aiutare lo

    sviluppatore a capirne il formato e la struttura del dataset stesso ● sample → campiona il dataset creandone una versione di dimensione ridotta● limit → selezione i primi N record di un dataset● explain → mostra come il compilatore compilerà ed eseguirà lo script in

    termini di mapreduce

  • Pig-Latin

    describe

    describe mostra lo schema associato ad una relazione e può essere molto utile nell'apprendimento di Pig-Latin e nello sviluppo degli script.In uno script possono esserci più comandi describe.

    divs = load 'NYSE_dividends' as (exchange:chararray, symbol:chararray, date:chararray, dividends:float);

    trimmed = foreach divs generate symbol, dividends;grpd = group trimmed by symbol;avgdiv = foreach grpd generate group, AVG(trimmed.dividends);describe trimmed;describe grpd;describe avgdiv;

    trimmed: {symbol: chararray, dividends: float}

    grpd: {group: chararray, trimmed: {(symbol: chararray, dividends: float)}}

    avgdiv: {group: chararray, double}

  • Pig-Latin

    sample / limit

    Spesso uno dei modi migliori per testare uno script è quello di lanciarlo sui nostri dati e verificare che i risultati prodotti siano conformi alle attese.Ma operando su BigData questo può richiedere parecchio tempo, per cui è utile avere dei meccanismi per ridurre le dimensioni dei dataset.

    L'operatore sample consente di selezionare una percentuale di record scelta a caso dal nostro dataset.

    L'operatore limit invece consente di specificare il numero di record di una relazione da selezionare.

  • Pig-Latin

    sample / limit

    --sample.pigdivs = load 'NYSE_dividends';some = sample divs 0.1;

    some conterrà circa il 10% dei record presenti in divs.

    --limit.pigdivs = load 'NYSE_dividends';first10 = limit divs 10;

    first10 conterrà i primi 10 record presenti in divs.

  • Pig-Latin

    illustrate

    Non sempre un semplice campionamento è in grado di cogliere tutti gli aspetti di un

    dataset. Ad esempio se lo script esegue un join è necessario assicurarsi che esistano dei record con la stessa chiave altrimenti il join restituirà una relazione vuota.

    L'operatore illustrate serve ad evidenziare il dataflow assicurando di selezionare dei record che arrivino al termine della trasformazione.

    Per farlo inizia con un sample dei dati ed esegue su di essi lo script assicurandosi tuttavia che ogniqualvolta viene incontrato un operatore che scarta dei risultati

    (filter, join, …) almeno qualche record superi il filtro.Se tra quelli campionati non ne trova nessuno, ne costruisce uno simile, cioè che rispetti lo schema e che abbia delle caratteristiche tali da fargli superare il filtro.

  • Pig-Latin

    illustrate

    divs = load 'NYSE_dividends' as (e:chararray, s:chararray, d:chararray, div:float);recent = filter divs by d > '2009-01-01';trimmd = foreach recent generate s, div;grpd = group trimmd by s;avgdiv = foreach grpd generate group, AVG(trimmd.div);

    illustrate avgdiv;

  • Pig-Latin

    illustrate

  • Pig-Latin

    explain

    Uno dei principali obiettivi di Pig è quello di consentire allo sviluppatore di pensare in termini di dataflow e dimenticare il MapReduce.

    Tuttavia in alcuni casi è utile sapere “cosa succede dietro le quinte”.

    L'operatore explain mostra come Pig compilerà lo script e quindi come il nostro dataflow verrà espresso in termini di MapReduce.

    Questo può esser utile sia per fare il debug di uno script che per ottimizzarne le performance.

    L'outupt di explain non è molto intuitivo e per trarne beneficio è richiesto un certo sforzo da parte dello sviluppatore.

  • Pig-Latin

    explain

    L'operatore explain può esser utilizzato in due modi:● su un alias: in maniera analoga a describe si può chiedere a Pig di mostrare il

    dataflow con cui verranno generati i dati da inserire in una relazione;

    ● su uno script: in alternativa si può chiedere a Pig di illustrare l'intero dataflow associato ad uno script

    L'output prodotto dall'operatore explain consiste di tre grafici in modalità testuale che illustrano le tre fasi di compilazione di uno script:

    ● il logical plan● il physical plan● l'execution plan

  • Pig-Latin

    explain – logical plan

    Dopo una preliminare verifica grammaticale e sintattica dello script viene prodotto il logical plan, che illustra gli operatori logici che Pig utilizzerà per eseguire lo script.

    Già in questa fase verranno eseguite delle ottimizzazioni, ad esempio anticipando il più

    possibile l'esecuzione di operatori come filter che riducono le dimensioni dei dati su cui lavorare e consentono di velocizzare l'esecuzione complessiva dello script.Applichiamo l'operatore explain allo script seguente

    --explain.pigdivs = load 'NYSE_dividends' as (exchange, symbol, date, dividends);grpd = group divs by symbol;avgdiv = foreach grpd generate group, AVG(divs.dividends);store avgdiv into 'average_dividend';

  • Pig-Latin

    explain – logical plan#-----------------------------------------------# New Logical Plan:#-----------------------------------------------avgdiv: (Name: LOStore Schema: group#11:bytearray,#27:double)||---avgdiv: (Name: LOForEach Schema: group#11:bytearray,#27:double) | | | (Name: LOGenerate[false,false] Schema: group#11:bytearray,#27:double)ColumnPrune:InputUids=[23, 11]ColumnPrune:OutputUids=[27, 11] | | | | | group:(Name: Project Type: bytearray Uid: 11 Input: 0 Column: (*)) | | | | | (Name: UserFunc(org.apache.pig.builtin.AVG) Type: double Uid: 27) | | | | | |---(Name: Dereference Type: bag Uid: 26 Column:[3]) | | | | | |---divs:(Name: Project Type: bag Uid: 23 Input: 1 Column: (*)) | | | |---(Name: LOInnerLoad[0] Schema: group#11:bytearray) | | | |---divs: (Name: LOInnerLoad[1] Schema: exchange#10:bytearray,symbol#11:bytearray,date#12:bytearray,dividends#13:bytearray) | |---grpd: (Name: LOCogroup Schema: group#11:bytearray,

    divs#23:bag{ #28:tuple(exchange#10:bytearray,symbol#11:bytearray,date#12:bytearray,dividends#13:bytearray)}) | | | symbol:(Name: Project Type: bytearray Uid: 11 Input: 0 Column: 1) | |---divs: (Name: LOLoad Schema: exchange#10:bytearray,symbol#11:bytearray,date#12:bytearray,dividends#13:bytearray)RequiredFields:[0, 1, 2, 3]

  • Pig-Latin

    explain – physical plan

    Dopo aver ottimizzato il logical plan, Pig produce il physical plan in cui illustra gli operatori fisici che utilizzerà per eseguire lo script.

    Questo piano assomiglia molto al precedente salvo il fatto che ora sono ben definite sia le funzioni di load – store (es. PigStorage) che i percorsi dei file di input ed output.

  • Pig-Latin

    explain – physical plan

    #-----------------------------------------------# Physical Plan:#-----------------------------------------------avgdiv: Store(fakefile:org.apache.pig.builtin.PigStorage) - scope-12||---avgdiv: New For Each(false,false)[bag] - scope-11 | | | Project[bytearray][0] - scope-5 | | | POUserFunc(org.apache.pig.builtin.AVG)[double] - scope-9 | | | |---Project[bag][3] - scope-8 | | | |---Project[bag][1] - scope-7 | |---grpd: Package[tuple]{bytearray} - scope-2 | |---grpd: Global Rearrange[tuple] - scope-1 | |---grpd: Local Rearrange[tuple]{bytearray}(false) - scope-3 | | | Project[bytearray][1] - scope-4 | |---divs: Load(hdfs://..../user/hue/NYSE_dividends:org.apache.pig.builtin.PigStorage) - scope-0

  • Pig-Latin

    explain – execution plan

    Come ultimo step Pig partendo dal physical plan decide la miglior strategia per distribuire i vari operatori nel minor numero possibile di job MapReduce.

    Come prima cosa scorre il plan alla ricerca di tutti gli operatori che impongono una fase di reduce (local / global rearrange, package).

    Poi verifica se è possibile effettuare delle ottimizzazioni sul piano fisico, ad esempio utilizzando dei combiner o includendo alcune delle operazioni di sort nel sort intrinseco fornito dal Hadoop.

    Al termine di queste analisi Pig ha preparato il suo execution plan con il dettaglio del numero di job e dei vari operatori da eseguire nelle diverse fasi.

  • Pig-Latin

    explain – execution plan

    #--------------------------------------------------# Map Reduce Plan #--------------------------------------------------MapReduce node scope-13Map Plangrpd: Local Rearrange[tuple]{bytearray}(false) - scope-26| || Project[bytearray][0] - scope-27||---avgdiv: New For Each(false,false)[bag] - scope-14 | | | Project[bytearray][0] - scope-15 | | | POUserFunc(org.apache.pig.builtin.AVG$Initial)[tuple] - scope-16 | | | |---Project[bag][3] - scope-17 | | | |---Project[bag][1] - scope-18 | |---Pre Combiner Local Rearrange[tuple]{Unknown} - scope-28 | |---divs: Load(hdfs://sandbox.hortonworks.com:8020/user/hue/NYSE_dividends:org.apache.pig.builtin.PigStorage) - scope-0--------

  • Pig-Latin

    explain – execution plan

    Combine Plangrpd: Local Rearrange[tuple]{bytearray}(false) - scope-30| || Project[bytearray][0] - scope-31||---avgdiv: New For Each(false,false)[bag] - scope-19 | | | Project[bytearray][0] - scope-20 | | | POUserFunc(org.apache.pig.builtin.AVG$Intermediate)[tuple] - scope-21 | | | |---Project[bag][1] - scope-22 | |---POCombinerPackage[tuple]{bytearray} - scope-24--------Reduce Planavgdiv: Store(fakefile:org.apache.pig.builtin.PigStorage) - scope-12||---avgdiv: New For Each(false,false)[bag] - scope-11 | | | Project[bytearray][0] - scope-5 | | | POUserFunc(org.apache.pig.builtin.AVG$Final)[double] - scope-9 | | | |---Project[bag][1] - scope-23 | |---POCombinerPackage[tuple]{bytearray} - scope-32--------Global sort: false

  • Pig-Latin

    Comandi Base

  • Pig-Latin

    Comandi Base

    Alla base di un qualunque flusso di elaborazione dati c'è la definizione delle sorgenti di input e di output dei dati.

    Pig latin offre tre comandi per la loro definizione:

    ● load per la definizione delle sorgenti di input

    ● store per la definizione delle sorgenti di ouptut

    ● dump per mostrare i risultati a video (su console)

  • Pig-Latin

    Load

    Il formato base del comando è

    input = load 'data';

    Per default il comando load lavora su HDFS e assume come directory di partenza la cartella /user/user_name .

    E' possibile utilizzare sia path assoluti che relativi o anche specificare la URL

    completa con l'indirizzo del NameNode: hdfs://my.namenode.org/data/

    Per default viene usata la funzione di lettura PigStorage che si aspetta in inputfile testuali separati da tabulazioni.

  • Pig-Latin

    Load

    E' possibile possibile specificare opzioni diverse attraverso la clausola using

    /* comma separated values */input = load 'data' using PigStorage(',');

    E' possibile specificare sorgenti diverse dall'HDFS, es. Hbase

    input = load 'data' using HbaseStorage();

    o, se noto, specificare uno schema da associare alla relazione

    input = load 'data' as ( exchange: chararray, symbol: chararray, date: chararray, dividends: float);

  • Pig-Latin

    Load: globs

    Se si sostituisce il nome del file con quello di una cartella verranno letti tutti i file presenti nella cartella e il loro contenuto sarà accorpato in un'unica relazione.E' possibile filtrare i nomi con delle regular expressions semplificate (globs)

    Glob Significato? match con ogni singolo carattere

    * match con zero o più caratteri

    [abc] match con un singolo carattere tra quelli specificati

    [a-z] match con un singolo carattere tra quelli specificati dall'intervallo (estremi inclusi)

    [^abc] match con un singolo carattere che non sia tra quelli specificati

    [^a-z] match con un singolo carattere che non sia nell'intervallo (estremi inclusi)

    \c Rimuove ( escapes ) ogni significato speciale del carattere c

    {aa, bb} match con una delle stringhe indicate nell'elenco

  • Pig-Latin

    store

    Operazione duale alla lettura è la scrittura che può essere eseguita con il comando

    store che segue le stesse regole del comando load.Il formato standard è

    store 'NomeRelazione' into 'nome_file';

    che richiamerà la funzione di default PigStorage. Anche in questo caso il comportamento di deafult può essere modificato con la clausola using

    store 'NomeRelazione' into 'nome_file' using HBaseStorage();

  • Pig-Latin

    dump

    L'ultima operazione di output è dump utile sopratutto in modalità interattiva per visualizzare a schermo il contenuto di una relazione.Il suo formato è estremamente semplice

    dump 'NomeRelazione';

    Il comando dump forza l'esecuzione di tutti i comandi dati fino ad ora nella shell.

  • Pig-Latin

    Operatori Relazionali

  • Pig-Latin

    Operatori Relazionali

    Il punto di forza di Pig-Latin sono gli operatori relazionali, che si ispirano agli equivalenti SQL, ma hanno un comportamento lievemente diversi.I principali sono

    ● foreach che applica un insieme di espressioni a ciascun record di una relazione● filter che consente di selezionare solo i record di interesse● group che consente di accorpare i dati secondo● order by che consente di riordinare i record di una relazione● join che consente di unificare il contenuto di due relazioni

  • Pig-Latin

    foreach

    L'operatore foreach applica un insieme di espressioni a ciascun record di una relazione e, come risultato, genera la prossima relazione della pipeline.E' equivalente all'operatore di proiezione del SQL.Il suo formato base è piuttosto semplice

    A = load 'input' as (user: chararray, id: long, address: chararray,phone: chararray, preferences: map[]);

    B = foreach A generate user, id;

    in questo esempio si estraggono da A i campi user e id e con essi viene generata la nuova relazione B.

  • Pig-Latin

    foreach

    Il vero potenziale dell'operatore foreach emerge con l'applicazione di espressioni sui campi di una relazione.Le più semplici espressioni disponibili sono

    ● valori costanti● e riferimenti ai campi (per nome o posizione con $pos )

    prices = load 'NYSE_daily' as (exchange, symbol, date, open, high, low,close, volume, adj_close);

    gain = foreach prices generate close-open;gain2 = foreach prices generate $6 - $3;

    Le due relazioni gain e gain2 avranno gli stessi valori

  • Pig-Latin

    foreach

    E' possibile specificare gruppi di campi attraverso i simboli● * per indicare tutti i campi● .. per indicare un intervallo di campi

    prices = load 'NYSE_daily' as (exchange, symbol, date, open, high, low,close, volume, adj_close);

    beginning = foreach prices generate ..open;-- produces exchange, symbol, date, open

    middle = foreach prices generate open..close;-- produces open, high, low, close

    end = foreach prices generate volume..;-- produces volume, adj_close

  • Pig-Latin

    foreach

    Sono inoltre disponibili le operazioni standard tra numeri

    + - * / %

    e l'operatore ? equivalente all' if-then-else (con il vincolo che i due valori restituiti siano dello stesso tipo).

    Nella definizione delle espressioni è bene prestare attenzione ai null perché qualunque operazione aritmetica con un null ha come risultato null.

    2 == 2 ? 1 : 4 --returns 12 == 3 ? 1 : 4 --returns 4null == 2 ? 1 : 4 -- returns null2 == 2 ? 1 : 'fred' -- type error; both values must be of the same type

  • Pig-Latin

    foreach

    Gli schemi sono strutture ricorsive, dunque è probabile che si abbia la necessità di estrarre informazioni annidate all'interno di tipi complessi.Per farlo è necessario usare gli operatori di proiezione

    ● # per le map● . per le tuple

    A = load 'baseball' as (name:chararray, team:chararray,position:bag{t:(p:chararray)}, bat:map[]);

    avg = foreach A generate bat#'batting_average';

    B = load 'input' as (t:tuple(x:int, y:int));C = foreach B generate t.x, t.$1;

  • Pig-Latin

    foreach

    Estrarre informazioni da un bag è invece più complesso perché questo non ha un ordine tra i suoi elementi.L'unica cosa che si può fare è applicare la proiezione ai campi delle tuple contenute nel bag e creare un nuovo bag.

    A = load 'input' as (b:bag{t:(x:int, y:int, Z:int)});

    B = foreach A generate b.(x, y);

  • Pig-Latin

    foreach

    L'aspetto più interessante è tuttavia la possibilità di richiamare delle User Defined Functions (UDF) per eseguire delle operazioni personalizzate.

    divs = load 'NYSE_dividends' as (exchange, symbol, date, dividends);

    --make sure all strings are uppercaseupped = foreach divs generate UPPER(symbol) as symbol, dividends;grpd = group upped by symbol;

    --output a bag upped for each value of symbol--take a bag of integers, produce one result for each groupsums = foreach grpd generate group, SUM(upped.dividends);

  • Pig-Latin

    foreach

    Il risultato di un foreach è una nuova tupla.Per definire i nomi dei campi della nuova relazione Pig userà le informazioni in suo possesso, tuttavia non sempre sarà in grado di farlo in maniera efficace (ad esempio per i campi risultanti dall'applicazione di un'espressione).

    In questi casi è possibile specificare il nome del campo con la clausola as

    divs = load 'NYSE_dividends' as (exchange:chararray, symbol:chararray,date:chararray, dividends:float);

    in_cents = foreach divs generate dividends * 100.0 as dividend, dividends * 100.0;

    describe in_cents;

    in_cents: {dividend: double,double}

  • Pig-Latin

    filter

    L'operatore filter seleziona i record da inviare al resto della pipeline.La selezione viene effettuata applicando un predicato definito dall'utente che potrà

    contenere i tipici operatori di confronto < = >E' possibile creare predicati complessi attraverso gli operatori booleani and or not o l'utilizzo di regular expressions.Verranno conservati tutti quei record per cui il predicato risulta vero.

    Per gestire il valore null è bene utilizzare is null e/o is not null

    divs = load 'NYSE_dividends' as (exchange:chararray, symbol: chararray,date:chararray, dividends:float);

    startsWithCM = filter divs by symbol matches 'CM.*';

  • Pig-Latin

    group by

    L'operatore group raggruppa tutti i record accomunati da una stessa chiave.I record risultanti dall'espressione group sono composti da due elementi

    ● i campi chiave, denominati group● un bag con il nome dell'alias su cui si è effettuato il raggruppamento contenente i

    record raccolti

    daily = load 'NYSE_daily' as (exchange, stock, date, dividends);grpd = group daily by (exchange, stock);describe grpd;

    grpd: {group: (exchange: bytearray, stock: bytearray),daily: {exchange:bytearray, stock:bytearray, date: bytearray,

    dividends:bytearray}}

  • Pig-Latin

    group by

    L'operatore group forza l'esecuzione di una fase reduce, ovvero dalla fase di

    ● map → verrà forzato lo shuffle e il successivo reduce● reduce → verrà forzato un intero ciclo map-shuffle-reduce

    Il raggruppamento dei record può portare allo sbilanciamento dei dati, in cui un reducer

    riceve la gran parte dei dati da elaborare (riducendo di fatto il parallelismo).

    E' possibile usare la clausola all per raggruppare tutti i record di una relazione.

    grpd = group daily all;cnt = foreach grpd generate COUNT(daily);

  • Pig-Latin

    order by

    L'operatore order by garantisce un ordinamento totale dei dati sulla base di uno o più campi. Per default viene effettuato un ordinamento ascendente, tuttavia si può

    invertire la logica specificando la clausola desc.

    daily = load 'NYSE_daily' as (exchange:chararray, symbol:chararray,date:chararray, open:float, high:float, low:float, close:float,volume:int, adj_close:float);

    bydate = order daily by date;

    bydatensymbol = order daily by date, symbol;

    byclose = order daily by close desc, open;

  • Pig-Latin

    distinct

    L'espressione distinct si comporta in maniera simile all'equivalente SQL:

    elimina i record duplicati e funziona solo su record interi non su singoli campi.

    daily = load 'NYSE_daily' as (exchange:chararray, symbol:chararray);uniq = distinct daily;

    distinct forza una fase di reduce e fa uso dei combiner.

  • Pig-Latin

    join

    Analogamente all'SQL, anche in Pig uno degli operatori più importanti è il join, che consente di combinare i record di due relazioni accomunati da una stessa chiave.

    I record per cui non viene trovata una corrispondenza vengono scartati.

    daily = load 'NYSE_daily' as (exchange, symbol, date, open, high, low,close, volume, adj_close);

    divs = load 'NYSE_dividends' as (exchange, symbol, date, dividends);

    jnd = join daily by symbol, divs by symbol;

  • Pig-Latin

    join

    join conserva i nomi dei campi delle relazioni di input ma antepone il nome della relazione di provenienza per evitare conflitti nei nomi dei campi.Il risultato del join precedente sarebbe

    describe jnd;

    jnd: {daily::exchange:bytearray, daily::symbol:bytearray, daily::date:bytearray, daily::open:bytearray, daily::high:bytearray,daily::low:bytearray, daily::close:bytearray, daily::volume:bytearray,daily::adj_close:bytearray,divs::exchange:bytearray, divs::symbol:bytearray, divs::date:bytearray,divs::dividends:bytearray}

  • Pig-Latin

    join

    E' possibile fare il join con più chiavi a patto che i campi delle due relazioni abbiano tipi compatibili (ovvero su cui è ammissibile un cast implicito).

    jnd = join daily by (symbol, date), divs by (symbol, date);

  • Pig-Latin

    outer join

    Pig supporta anche l' outer join. che consente di non scartare i record per cui non è stata trovata una corrispondenza in una delle due relazioni, sostituendo la parte mancante

    con dei null.Gli outer join possono essere di tre tipi

    ● left → vengono inclusi i record della relazione a sinistra

    ● right → vengono inclusi i record della relazione a destra

    ● full → vengono inclusi i record di entrambe le relazioni

  • Pig-Latin

    outer join

    Per poter realizzare un outer join Pig deve conoscere lo schema della relazione che si trova nel lato in cui deve sostituire i valori con dei null.

    --leftjoin.pigdaily = load 'NYSE_daily' as (exchange, symbol, date, open, high, low,

    close, volume, adj_close);divs = load 'NYSE_dividends' as (exchange, symbol, date, dividends);

    jnd = join daily by (symbol, date) left outer, divs by (symbol, date);

    Analogamente all'SQL un record con un null nella chiave non ha corrispondenze con nessun record (nemmeno con altri null). Negli equi-join questi record vengono scartati, mentre negli outer-join vengono corservati.

  • Pig-Latin

    self join

    E' possibile effettuare anche dei self-join ovvero dei join sulla stessa relazione, a patto che la relazione sia caricata due volte.

    divs1 = load 'NYSE_dividends' as (exchange:chararray, symbol:chararray,date:chararray, dividends:float);

    divs2 = load 'NYSE_dividends' as (exchange:chararray, symbol:chararray,date:chararray, dividends:float);

    jnd = join divs1 by symbol, divs2 by symbol;

    Il problema è legato all'ambiguità dei nomi nella relazione risultante, non ancora correttamente gestita da Pig, per cui l'unica soluzione è la duplicazione dell'operazione

    di load.

  • Pig-Latin

    Funzionalità avanzate

  • Pig-Latin

    union

    In alcuni casi può esser necessario unire due relazioni non attraverso un join ma concatenando i rispettivi record. Questo può esser fatto attraverso l'operatore union.Ad esempio avendo due file distinti che non possono essere accorpati con un glob

    A = load '/user/me/data/files/input1';B = load '/user/other/info/input2';C = union A, B;

    Pig è meno rigido dell'equivalente SQL per cui non è richiesto che entrambe le relazioni abbiano lo stesso schema.

  • Pig-Latin

    union

    Possono verificarsi 3 casi

    ● entrambe le relazioni hanno lo stesso schema → in tal caso la relazione risultante avrà lo stesso schema

    ● le relazioni hanno schemi diversi ma compatibili (uno dei due può esser derivato dall'altro attraverso dei cast impliciti) → anche in questo caso la relazione risultante avrà lo stesso schema

    ● le relazioni hanno schemi incompatibili → in questo caso la relazione risultante non avrà schema (ovvero record diversi avranno campi diversi)

    Nel confronto degli schemi Pig include anche i nomi dei campi non solo i tipi.

  • Pig-Latin

    union

    Poiché i dati tendono a cambiare nel tempo, è possibile che lo schema cambi nel tempo (magari con l'aggiunta di qualche campo). Questo renderebbe impossibile utilizzare l'operatore union perché gli schemi non sarebbero più identici.E' tuttavia possibile utilizzare il modificatore onschema per forzare i dati in uno schema comune. Questo richiede che entrambe le relazioni abbiano uno schema associato.

    A = load 'input1' as (w:chararray, x:int, y:float);B = load 'input2' as (x:int, y:double, z:chararray);C = union onschema A, B;describe C;

    C: { w: chararray, x: int, y: double, z: chararray }

  • Pig-Latin

    flatten

    Diversi operatori generano come risultato delle loro trasformazioni delle tuple o dei bag dando vita a schemi annidati che possono rendere complesso l'accesso alle singole informazioni.Per tale ragione può esser comodo rimuovere qualche annidamento.Ad esempio il dataset baseball presente sulla VM contiene delle informazioni su dei giocatori di baseball, tra le quali un bag con alcune delle loro tipiche posizioni di gioco.

    Se si volesse effettuare un raggruppamento dei giocatori per posizione di gioco, questo non sarebbe possibile a meno di estrarre queste informazioni dal bag stesso.

    Per raggiungere questo scopo Pig offre il modificatore flatten, applicabile all'interno dell'operatore foreach.

  • Pig-Latin

    flatten

    Il modificatore flatten genera l'equivalente di un prodotto scalare dei campi di un record con ogni elemento presente nel bag, producendo di fatto N record (dove N è il numero di elementi presenti nel bag).

    players = load 'baseball' as (name:chararray, team:chararray,position:bag{t:(p:chararray)}, bat:map[]);

    pos = foreach players generate name, flatten(position) as position;bypos = group pos by position;

  • Pig-Latin

    flatten

    Ad esempio l'operatore flatten applicato al record

    Jorge Posada,New York Yankees,{(Catcher),(Designated_hitter)},...

    produrrebbe i seguenti record

    Jorge Posada,CatcherJorge Posada,Designated_hitter

    Se applicato ad una tupla flatten non produce N record ma trasforma i campi della tupla in campi della relazione.

    Nel caso di più bag (es. con N e M elementi) verranno generati N * M record.

  • Pig-Latin

    flatten

    Un aspetto del modificatore flatten che sorprende all'inizio è che che in caso di bag vuoto il record viene eliminato. Questo in realtà è coerente con il fatto che il bag ha 0 elementi dunque vengono prodotti 0 record.

    Per ovviare a questo fatto è possibile trasformare preventivamente i record con un foreach per gestire il caso di bag vuoti.

    players = load 'baseball' as (name:chararray, team:chararray,position:bag{t:(p:chararray)}, bat:map[]);

    noempty = foreach players generate name, ((position is null or IsEmpty(position)) ? {('unknown')} : position)

    as position;pos = foreach noempty generate name, flatten(position) as position;bypos = group pos by position;

  • Pig-Latin

    nested foreach

    L'operatore foreach è in realtà più potente di quanto visto fino ad ora. Infatti consente di applicare un intero insieme di operatore ad ogni record da elaborare.Supponiamo di voler contare quanti simboli distinti sono presenti nel dataset d'esempio sugli scambi finanziari.

    daily = load 'NYSE_daily' as (exchange, symbol); grpd = group daily by exchange;uniqcnt = foreach grpd {

    sym = daily.symbol;uniq_sym = distinct sym;generate group, COUNT(uniq_sym);

    };

  • Pig-Latin

    nested foreach

    La prima differenza rispetto alle espressioni standard del foreach è che il nome della relazione non è seguito immediatamente dalla keyword generate ma da un gruppo di parentesi graffe ad indicare che la generazione di un nuovo record richiederà l'applicazione di un set di operazioni e non sarà immediata:

    Formato standard:gain = foreach prices generate close-open;

    Formato avanzato:

    uniqcnt = foreach grpd { …

    generate … };

  • Pig-Latin

    nested foreach

    Un altro aspetto interessante è la prima istruzione che troviamo all'interno delle parentesi:

    sym = daily.symbol;

    assomiglia all'assegnazione di una variabile in un linguaggio tradizionale e sarebbe illegale all'esterno del blocco di parentesi.

    In realtà questa operazione prende il daily (che è un bag) e genera una nuova relazione sym che contiene il solo campo symbol.

  • Pig-Latin

    nested foreach

    La seconda operazione

    uniq_sym = distinct sym;

    applica l'operatore distinct ai record della relazione sym (che ha il solo campo symbol) e genera la nuova relazione uniq_sym contenente solo i simboli distinti.

    Infine l'ultima operazione di un nested foreach dev'essere sempre una generate che dice a Pig come usare i valori nelle relazioni annidate per generare i record della relazione esterna:

    generate group, COUNT(uniq_sym);

  • Pig-Latin

    User Defined Function

    Uno dei punti di forza di Pig è la sua estensibilità che consente di

    combinare gli operatori e le funzioni predefinite con codice scritto dall'utente.

    A partire dalla versione 0.8 le UDF possono essere scritte in Java o Python e oltre gli operatori standard SQL-like e le funzioni aggregate (max, count, avg...) sono state aggiunte un buon numero di funzioni predefinte per l'elaborazione di stringhe (concat, substring...) e l'esecuzione di calcoli matematici.

    Inoltre viene distribuita una collezione di UDF create dagli utenti denominata PiggyBank che arricchisce di ulteriori funzionalità il linguaggio Pig Latin.

  • Pig-Latin

    User Defined Function

    Per utilizzare le UDF è necessario registrare l'archivio che le contiene.

    Può inoltre essere comodo definire un alias per evitare di usare il fully-qualified-name..

    register 'your_path_to_piggybank/piggybank.jar';define reverse org.apache.pig.piggybank.evaluation.string.Reverse();

    divs = load 'NYSE_dividends' as (exchange:chararray, symbol:chararray,date:chararray, dividends:float);

    backwards = foreach divs generateorg.apache.pig.piggybank.evaluation.string.Reverse(symbol);

    backwards2 = foreach divs generate reverse(symbol);

  • Pig-Latin

    User Defined Functions

    Se si lavora in team, o nello sviluppo di grossi progetti, per evitare di dover importare in ogni script le stesse funzioni e gli stessi alias, è possibile definire uno o più script in cui conservare queste definizioni ed importare questi script con la clausola import .

    import '../myProject/imports_and_definitions.pig';

    A partire dalla versione 0.8 register accetta anche path su HDFS e dalla 0.9sono ammessi anche i globs.

    register 'hdfs://user/jar/acme.jar';register '/usr/local/share/pig/udfs/*.jar';

  • Pig-Latin

    Sottotitolo

    testo

    Diapositiva 1Diapositiva 2Diapositiva 3Diapositiva 4Diapositiva 5Diapositiva 6Diapositiva 7Diapositiva 8Diapositiva 9Diapositiva 10Diapositiva 11Diapositiva 12Diapositiva 13Diapositiva 14Diapositiva 15Diapositiva 16Diapositiva 17Diapositiva 18Diapositiva 19Diapositiva 20Diapositiva 21Diapositiva 22Diapositiva 23Diapositiva 24Diapositiva 25Diapositiva 26Diapositiva 27Diapositiva 28Diapositiva 29Diapositiva 30Diapositiva 31Diapositiva 32Diapositiva 33Diapositiva 34Diapositiva 35Diapositiva 36Diapositiva 37Diapositiva 38Diapositiva 39Diapositiva 40Diapositiva 41Diapositiva 42Diapositiva 43Diapositiva 44Diapositiva 45Diapositiva 46Diapositiva 47Diapositiva 48Diapositiva 49Diapositiva 50Diapositiva 51Diapositiva 52Diapositiva 53Diapositiva 54Diapositiva 55Diapositiva 56Diapositiva 57Diapositiva 58Diapositiva 59Diapositiva 60Diapositiva 61Diapositiva 62Diapositiva 63Diapositiva 64Diapositiva 65Diapositiva 66Diapositiva 67Diapositiva 68Diapositiva 69Diapositiva 70Diapositiva 71Diapositiva 72Diapositiva 73Diapositiva 74Diapositiva 75Diapositiva 76Diapositiva 77Diapositiva 78Diapositiva 79Diapositiva 80Diapositiva 81Diapositiva 82Diapositiva 83Diapositiva 84Diapositiva 85Diapositiva 86Diapositiva 87Diapositiva 88Diapositiva 89Diapositiva 90Diapositiva 91