Pig Latin

Transcript

Pig Latin
PIG
Pig
Pig
Cosa è Pig ?
Pig è una piattaforma per l'analisi
e 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
lanciare
gli
scrivere
script
direttamente da browser.
e
Pig
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 → String
bytearray → 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 essere
contenuti 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
Sintassi
Tipo
int
Esempio
int
as ( a: int )
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) }
long
...
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
→ descrive lo schema associato ad una relazione
●
describe
●
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.pig
divs = load 'NYSE_dividends';
some = sample divs 0.1;
some conterrà circa il 10% dei record presenti in divs.
--limit.pig
divs = 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.pig
divs = 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-13
Map Plan
grpd: 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) - scope0--------
Pig-Latin
explain – execution plan
Combine Plan
grpd: 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 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$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 input
file 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)
Significato
Glob
?
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
{aa, bb}
Rimuove ( escapes ) ogni significato speciale del carattere c
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 1
2 == 3 ? 1 : 4 --returns 4
null == 2 ? 1 : 4 -- returns null
2 == 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 uppercase
upped = 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 group
sums = 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.pig
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, 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,Catcher
Jorge 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 generate
org.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.9
sono ammessi anche i globs.
register 'hdfs://user/jar/acme.jar';
register '/usr/local/share/pig/udfs/*.jar';
Pig-Latin
Sottotitolo
testo