¡Saludos, camaradas! 👋
En este artículo vamos a analizar el código del "Hola mundo" que expusimos en el artículo anterior. Antes de nada, vamos a recordarlo:
from pyspark.sql import SparkSession
# Iniciamos Spark de manera local
spark = (SparkSession
.builder
.master("local[*]")
.appName("Hola Mundo")
.getOrCreate())
# Paralelizamos una lista del 0 al 9 (incluido)
# Computamos la suma de los números
# Obtenemos el resultado en una variable
result = (spark
.sparkContext
.parallelize(range(10))
.reduce(lambda x, y: x + y))
# Imprimimos el resultado
print(f"La suma es {result}")
Al ejecutarlo a través del IDE o mediante una terminal escribiendo python hola_mundo.py
veremos el siguiente output tras una serie de warnings:
La suma es 45
Process finished with exit code 0
Vale, muy bonito todo pero, ¿qué es cada cosa?
Creando la sesión de Spark
Para continuar vamos a dar una vuelta por nuestro código anotando los tipos de nuestras variables.
Friendly reminder: Anotar con tipos en Python es meramente informativo de cara a quien desarrolla el código, no tiene el efecto que pueda tener en lenguajes como Java.
Vamos a anotar con su tipo la variable spark
y también vamos a poner un comentario en cada una de las llamadas encadenadas durante la creación de dicho objeto, para que veamos de qué tipo es cada una.
spark: SparkSession = (SparkSession # SparkSession
.builder # Builder
.master("local[*]") # Builder
.appName("Hola Mundo") # Builder
.getOrCreate()) # SparkSession
Todo empieza con la referencia a la clase SparkSession
, ésta nos permite crear un objeto Builder
al cual le iremos indicando qué configuración queremos.
En primer lugar, indicaremos que el master()
es local usando todos los cores que dispongamos. Esto es típico para hacer pruebas en local, cuando no disponemos de un clúster donde ejecutar código productivo (y de momento nos sirve perfectamente).
En segundo lugar especificamos el nombre de nuestra ejecución mediante appName()
. Como no podía ser de otra manera, se llama "Hola mundo" (cuánta imaginación, ¿verdad? 🙄).
Tanto las llamadas a master()
como a appName()
devuelven un objeto Builder, que indica que está a medio construir, nos faltaría un paso más.
Por último le indicamos a Spark que nos devuelva (en caso de existir) o que nos cree (en caso contrario) una SparkSession
con la que podamos hacer computación distribuida.
La SparkSession
que nos devuelve la plasmamos en la variable spark
para que podamos utilizarla más adelante.
¡Llegados a este punto ya podemos empezar a hacer computación distribuida!
Pinto y coloreo mis primeras operaciones distribuidas 🤓
Ahora vamos a hacer lo mismo con el segundo bloque de código, anotando el tipo de la variable result
y comentando cada paso.
result: int = (spark # SparkSession
.sparkContext # SparkContext
.parallelize(range(10)) # RDD[int]
.reduce(lambda x, y: x + y)) # int
Primero partimos de la variable spark
creada previamente. Y a partir de ella obtenemos un objeto SparkContext
. Podemos entender este objeto como un helper de Spark para realizar ciertas maniobras. En este caso nos facilita la creación de un RDD a través de su método parallelize
, que toma una lista como argumento.
Con parallelize()
tomamos una lista clásica (un array de toda la vida, si queréis verlo así) y crea un RDD a partir de ella, del mismo tipo de la lista. Nosotros le hemos pasado la lista [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
resultante de invocar range(10)
, que es de tipo int
. Por lo tanto el RDD
será también de tipo int
.
Explícame un poco los RDDs, porfa 🥺
Los RDD
s son las unidades básicas de Spark a partir de las cuales podemos hacer computación distribuida. En próximos artículos entraremos en más profundidad, de momento nos sirve pensar en ellos como listas cuyo contenido está troceado y repartido por diferentes servidores de Spark.
De esta manera, evitamos cargar a un único servidor con todo el trabajo, ya que todos los nodos que tengamos trabajarán a la par. ¿Y cuál es ese trabajo tan tedioso que va a requerir computación distribuida? 🤔
¡Nada más y nada menos que la acción reduce()
! 🤩
Importante: Este
reduce()
no es el del módulofunctools
pero se comporta parecido, solo que de manera distribuida.
Acción reduce()
Vale, ¿entonces qué hace exactamente el reduce()
de un RDD
?
Esta función coge una lista distribuida (RDD
) y va combinando sus valores mediante la función que le indiquemos. En este caso ha sido una simple función anónima que suma 2 números que le pase reduce()
.
Si no estáis familiarizados con funciones combinatorias de programación funcional, os dejo una breve explicación del
reduce()
del módulo functools. Recordad que no es lo mismo, pero nos sirve para hacernos una idea general de cuál es su mecánica.
Así pues, lo que hará es sumar todos los valores de la lista y devolverá un resultado, un simple int
de toda la vida. En este proceso intervendrían todos nuestros servidores de Spark, comunicándose entre ellos para ir sumando los diferentes valores, hasta tener completada la suma de todos ellos y devolver el resultado.
Una vez tenemos ese número en nuestro poder, lo imprimimos por pantalla para conocer el resultado de tamaña operación. ¡Buen trabajo! 😎
Espero que os haya sido útil este artículo. En el próximo hablaremos en más profundidad de las operaciones de Spark, que se dividen en transformaciones y acciones.
Es muy importante entender bien el rol y efectos que las diferencian, ¡así que os espero en el próximo artículo! 🤗
¡Nos vemos, equipo! 🙌
Top comments (0)