-->

Intelligenza Artificiale

A un certo punto non fu più la biologia a dominare il destino dell'uomo, ma il prodotto del suo cervello: la cultura.
Cosicché: "Le uniche leggi della materia sono quelle che la nostra mente deve architettare e le uniche leggi della mente sono architettate per essa dalla materia".
JAMES CLERK MAXWELL

Spark Data Processing (RDD)


L'RDD è il cuore di Spark e rappresenta la sua astrazione principale per il calcolo distribuito.

Cos'è un RDD in Apache Spark

Introduzione

Apache Spark è uno dei framework più popolari per l'elaborazione dei big data. Uno degli elementi chiave che rende Spark così potente è il concetto di **RDD (Resilient Distributed Dataset)**. L'RDD è il cuore di Spark e rappresenta la sua astrazione principale per il calcolo distribuito. In questo articolo, esploreremo cosa sono gli RDD, come funzionano e perché sono così fondamentali per il funzionamento di Spark.

Cos'è un RDD

Un **RDD** (Resilient Distributed Dataset) è una collezione distribuita di elementi immutabili che può essere elaborata in parallelo.
Questa collezione può essere distribuita su più nodi di un cluster, consentendo l'elaborazione parallela e scalabile di grandi quantità di dati.
Gli RDD sono progettati per essere tolleranti ai guasti e possono essere ricostruiti in caso di perdita di dati.

Caratteristiche principali degli RDD

1. **Immutabilità**: Una volta creato, un RDD non può essere modificato. Tuttavia, è possibile creare nuovi RDD trasformando quelli esistenti. Questo approccio immutabile facilita la gestione dei dati e la tolleranza ai guasti.

2. **Distribuzione**: Gli RDD sono distribuiti su più nodi in un cluster, permettendo l'elaborazione dei dati su larga scala. Questa distribuzione è automatica e gestita da Spark.

3. **Tolleranza ai guasti**: Gli RDD sono resilienti. Ogni RDD mantiene un DAG (Directed Acyclic Graph) di operazioni che possono essere riapplicate per ricostruire i dati persi in caso di guasto del nodo.

4. **Lazy Evaluation**: Le trasformazioni sugli RDD sono valutate pigramente, cioè l'operazione non viene eseguita immediatamente. Spark attende fino a quando non è necessario un risultato (in un'operazione chiamata **action**) per eseguire effettivamente il calcolo. Questo permette ottimizzazioni, come la combinazione di operazioni multiple in un'unica scansione dei dati.

5. **Tipo di operazioni**: Gli RDD supportano due tipi di operazioni:
- **Transformations**: Operazioni che trasformano un RDD in un altro, come `map()`, `filter()`, `flatMap()`, `groupByKey()`, ecc. Queste operazioni sono lazy.
- **Actions**: Operazioni che restituiscono un valore o un nuovo RDD, come `reduce()`, `collect()`, `count()`, ecc. Queste operazioni innescano l'esecuzione del DAG delle trasformazioni.


Creazione di un RDD


Da un file o una collezione esistente

È possibile creare un RDD leggendo un file di testo o caricando una collezione di dati esistente in memoria.

   sc = SparkContext("local", "RDD Example")
   rdd = sc.textFile("path/to/file.txt")


Questo esempio crea un RDD da un file di testo.

Trasformando un altro RDD

Gli RDD possono essere creati applicando trasformazioni su un RDD esistente.
Ad esempio, applicando una mappa a un RDD esistente:
  
   words = rdd.flatMap(lambda line: line.split(" "))
  
  
Questo divide ogni riga del file in parole, creando un nuovo RDD di parole.

Operazioni su RDD


Trasformazioni

Le trasformazioni sono operazioni che prendono un RDD come input e ne restituiscono uno nuovo.
Sono lazy, il che significa che l'RDD risultante non viene realmente calcolato finché non viene eseguita un'azione.

map()

Applica una funzione a ciascun elemento dell'RDD.
  
  rdd2 = rdd.map(lambda x: x * 2)
  
  


filter()

Filtra gli elementi dell'RDD in base a una condizione.

  rdd3 = rdd.filter(lambda x: x % 2 == 0)



- **flatMap()**: Simile a `map()`, ma ogni elemento della funzione di mappa può essere "appiattito" in più di un elemento nell'RDD risultante.

  rdd4 = rdd.flatMap(lambda x: x.split(" "))


Azioni


Le azioni sono operazioni che restituiscono un valore o un nuovo RDD. Quando viene eseguita un'azione, Spark valuta tutte le trasformazioni che portano all'RDD attuale. Ecco alcune azioni comuni:

collect()

Raccoglie tutti gli elementi di un RDD nel driver.
 
  result = rdd.collect()
 
 


count()

Restituisce il numero di elementi nell'RDD.

  numItems = rdd.count()

reduce()

Combina gli elementi dell'RDD utilizzando una funzione associativa e commutativa.
 
  sum = rdd.reduce(lambda a, b: a + b)
 
 

Tolleranza ai guasti negli RDD


La tolleranza ai guasti è una caratteristica fondamentale degli RDD.
Spark riesce a mantenere la resilienza degli RDD attraverso un meccanismo chiamato **lineage**.
Ogni RDD ricorda le operazioni che sono state applicate per generarlo.
Se una partizione dell'RDD va persa (ad esempio, a causa di un guasto hardware), Spark può ricostruire quella partizione applicando la serie di operazioni memorizzate nel DAG a partire dai dati originali.

RDD vs DataFrame vs Dataset


Mentre gli RDD sono l'astrazione di base in Spark, Spark offre anche altre due astrazioni più avanzate: **DataFrame** e **Dataset**.
Queste astrazioni offrono ulteriori vantaggi come ottimizzazioni più aggressive e una sintassi più dichiarativa.
- **DataFrame**: Una collezione distribuita di dati organizzati in colonne denominate. È simile a un RDD, ma con API più ottimizzate e un migliore supporto per le query SQL.
- **Dataset**: Una combinazione dei vantaggi degli RDD e dei DataFrame, con un forte tipaggio e ottimizzazioni automatiche.