Saltar al contenido principal

Arquitectura de Apache Spark: Guía para profesionales de los datos

Comprende cómo Apache Spark procesa datos a escala, desde sus componentes básicos hasta las funciones avanzadas que impulsan los modernos flujos de trabajo de big data.
Actualizado 20 jun 2025  · 15 min de lectura

¿Has intentado alguna vez depurar un trabajo de Spark que ha fallado de repente y te has dado cuenta de que estás completamente perdido debido a lo profundo que llega la madriguera del conejo de Spark? 

Cuando trabajé por primera vez con Apache Spark, pensé que sólo tenía que escribir unas cuantas transformaciones PySpark y Spark escalaría "mágicamente" a través del clúster. Me equivoqué. El rendimiento de Spark depende por completo de comprender lo que ocurre entre bastidores.

Esta guía es para cualquiera que no quiera tratar a Spark como una caja negra. Repasaremos cómo está diseñada la arquitectura de Spark, desde el modelo de trabajador maestro y el flujo de trabajo de ejecución, hasta sus mecanismos de gestión de memoria y tolerancia a fallos. 

Si quieres crear aplicaciones de big data rápidas, tolerantes a fallos y eficientes, ¡estás en el lugar adecuado!

Arquitectura básica de Apache Spark

Antes de que escribas tu primera línea en PySpark, Spark ya ha tomado algunas decisiones arquitectónicas por ti. Spark no sólo es rápido gracias a la computación en memoria, sino porque está construido sobre una arquitectura de trabajador maestro que escala y sobrevive al caos del mundo real, como las caídas de nodoshes, los problemas de la Máquina Virtual Java (JVM) y los volúmenes de datos inconsistentes.

Desglosemos la arquitectura central de Spark y por qué sigue siendo tan potente y está tan presente en los flujos de trabajo modernos de big data.

Paradigma del maestro-trabajador

En el núcleo de Spark está el modelo de maestro-trabajador . Piénsalo así:

  • Conductor (maestro): Este es el cerebro de Spark. Ejecuta tu función main(), crea el contexto Spark, gestiona la programación del DAG y le dice al clúster lo que tiene que hacer.
  • Ejecutores (trabajadores): Estos son los músculos. Ejecutan tus tareas, mantienen los datos en memoria e informan al controlador.

Esta configuración te permite concentrarte en definir las transformaciones, y Spark decide dónde y cómo ejecutarlas en paralelo en los ejecutores. 

Lo que me gusta de este diseño es que es independiente del despliegue. El mismo código se ejecuta, independientemente de que lo despliegues en tu máquina local, en Kubernetes o en Mesos. Esto hace que sea fácil desarrollarlo y probarlo localmente, y luego escalarlo a clusters sin reescribir tu código.

Y he aquí otra poderosa ventaja de la separación conductor-trabajador de Spark: Mejora el aislamiento de fallos. Si un nodo trabajador muere mientras ejecuta una tarea, Spark puede reasignar esa tarea a otro trabajador sin bloquear tu aplicación.

Componentes básicos

Vamos a desglosar lo que ocurre dentro del controlador y los nodos. 

Diagrama de la arquitectura Spark

Arquitectura Spark. Imagen del autor.

Contexto Spark

Cuando llamas a SparkContext() o utilizas SparkSession.builder.getOrCreate(), estás abriendo la puerta a toda la magia interna de Spark.

El contexto Spark:

  • Conecta con tu gestor de clústeres
  • Asigna albaceas
  • Realiza un seguimiento del estado del trabajo y de los planes de ejecución

Spark construye un grafo acíclico dirigido (DAG) de transformaciones entre bastidores. Ese DAG se descompone en etapas y tareas, y luego se ejecuta en paralelo.

El programador de DAG averigua qué tareas pueden ejecutarse juntas, y el programador de Tareas las asigna a los ejecutores. Mientras tanto, el Gestor de Bloques garantiza que los datos se almacenen en caché, se barajen o se recarguen según sea necesario. 

Este diseño en capas hace que Spark sea increíblemente flexible, ya que puedes ajustar la memoria, el almacenamiento y el cálculo de forma independiente.

Si trabajas con transformaciones Spark o ingeniería de características, consulta el curso Ingeniería de características con PySpark para ver esta arquitectura en acción.

Tiempo de ejecución del ejecutor

Los ejecutores están donde se hace el trabajo.

Cada ejecutor se ejecuta:

  • Una o más tareas (enhebradas)
  • Un trozo de memoria para almacenar datos en caché y barajar la salida
  • Su propia instancia JVM, aislada de las demás

Puedes configurar cuánta memoria obtiene cada ejecutor, cuántos núcleos utiliza y si debe escribir en disco cuando se agote la memoria. 

Pero, ten cuidado: Si no asignas suficiente memoria, te encontrarás continuamente con errores de falta de memoria. Sin embargo, también debes evitar asignar demasiada memoria, ya que esto desperdicia recursos. La supervisión y el ajuste son esenciales en este caso.

Flujo de trabajo de ejecución: Del código al clúster

Escribir código PySpark es bastante sencillo. Filtras un DataFrame, haces una unión, agregas algo y le das a ejecutar. Pero detrás de esa limpia API, Spark está poniendo en marcha silenciosamente un motor de ejecución que puede repartir el trabajo entre varios nodos. 

Veamos qué ocurre entre bastidores.

Conversión de plan lógico a físico

Esto es de lo que la mayoría de los usuarios de Spark no se dan cuenta al principio: Cuando escribes código PySpark, no estás ejecutando nada inmediatamente. Estás construyendo un plan, y el Optimizador de Catalizadores de Spark toma ese plan y lo transforma en una estrategia de ejecución eficiente.

Funciona en cuatro fases:

  1. Análisis: Spark resuelve los nombres de las columnas, los tipos de datos y las referencias a tablas, asegurándose de que todo es válido.
  2. Optimización lógica: Aquí es donde Spark aplica reglas como el pushdown de predicados y el plegado de constantes. Optimiza los filtros y combina las proyecciones.
  3. Planificación física: Spark considera múltiples estrategias de ejecución y elige la más eficiente (en función del tamaño de los datos, la partición, etc.).
  4. Generación de código: Por último, utiliza la generación de código de etapa completa para producir bytecode JVM. 

Imagen que muestra un diagrama del Optimizador de Catalizadores de Spark

Optimizador de catalizadores de Spark. Imagen de Databricks.

Así que esa cadena de .select(), .join(), y .groupBy() no se ejecuta simplemente línea a línea. Se analiza, se optimiza y se compila en algo que se ejecuta rápidamente en un clúster.

Echa un vistazo a esta Hoja de Trucos de PySpark si quieres una hoja de trucos con los comandos más útiles de PySpark.

Programador DAG y creación de etapas

Cuando termina el plan, el programador DAG toma el relevo.

Divide el trabajo en etapas basadas en los límites de barajado, donde Spark decide qué ocurre secuencialmente y qué puede ejecutarse en paralelo.

Hay dos tipos principales de etapas:

  • ShuffleMapStage: Esto implica un barajado, que suele estar provocado por transformaciones amplias como groupBy() o join(). A continuación, los datos se dividen y se envían a través de la red. Este tipo de etapa es necesario para calcular la EtapaResultado.
  • EtapaResultado: Estas etapas producen salida, como escribir en el disco o devolver resultados al controlador.

Una cosa clave que he aprendido es a minimizar los barajados. Una barajada tiene que tener lugar antes de que termine una etapa y es costosa. Tienes que saber dónde se producen en tu DAG y si puedes optimizar más tu código para reducir el número de barajadas. 

Ciclo de vida de la ejecución de tareas

Una vez que el programador DAG ha creado todas las etapas, pueden ejecutarse en los distintos ejecutores. 

El ciclo de vida de la ejecución de la tarea tiene este aspecto:

  1. Serialización de tareas: El controlador serializa las instrucciones de la tarea y las envía a los ejecutores.
  2. Fase de escritura aleatoria: Spark escribe la salida particionada en el disco local.
  3. Fase de búsqueda: Los ejecutores de la siguiente etapa obtienen los archivos aleatorios relevantes de otros a través del clúster.
  4. Deserialización y ejecución: Los ejecutores deserializan los datos, ejecutan tu lógica y, potencialmente, almacenan en caché o escriben los resultados.
  5. Recogida de basura: La JVM recupera automáticamente la memoria que ya no utilizan las aplicaciones Spark. Este paso es esencial para evitar fugas de memoria y garantizar que las aplicaciones Spark funcionen sin problemas.

Una pequeña pista por experiencia propia: si tu trabajo Spark se bloquea después de haber funcionado bien antes, a menudo se debe a retrasos en la recogida de basura o en la obtención aleatoria. Comprueba siempre tu código y asegúrate de que entiendes la arquitectura de Spark para poder optimizar estos temas de forma eficaz.

Arquitectura de gestión de memoria

La gestión de la memoria de Spark es un tema muy complejo y puede costarte horas de depuración si no lo entiendes. 

Por lo tanto, echemos un vistazo a cómo Spark gestiona la memoria bajo el capó para que seas consciente de ello y puedas evitar horas de depuración de código lento o errores de falta de memoria.

Modelo unificado de memoria

Antes de Spark 1.6, la memoria se dividía estrictamente entre ejecución (para mezclas y uniones) y almacenamiento (para caché). Eso cambió con Spark 1.6, que introdujo el modelo de memoria unificada. 

En el modelo de memoria unificada, los datos se dividen en tres pools clave:

  • Memoria reservada: Se utiliza una pequeña cantidad de memoria para las funciones internas de Spark y el sistema. 
  • Memoria Spark: Se utiliza para almacenar datos de ejecución y para el almacenamiento en caché. Se comparte dinámicamente. Si tu trabajo necesita más memoria para barajar y menos para almacenar en caché (o viceversa), Spark se adapta.
  • Memoria de usuario: Espacio para estructuras de datos definidas por el usuario, necesarias para ejecutar código de usuario dentro de las aplicaciones Spark.

El pool de memoria de Spark se divide a su vez en dos pools:

  1. Memoria ejecutora: Almacena los datos temporales necesarios durante las etapas de las tareas de procesamiento (por ejemplo, mezclas, uniones, agregaciones, ...). 
  2. Fondo de memoria de almacenamiento: Se utiliza para almacenar datos en caché y estructuras de datos internas. 

Esta elasticidad permite a Spark ser más flexible con volúmenes de datos impredecibles. 

Sin embargo, esto también significa perder un poco el control cuando no sabes lo que está pasando. Por ejemplo, si cache() un DataFrame grande pero también tiene agregaciones costosas en la misma etapa, Spark podría desalojar tus datos almacenados en caché para hacer sitio a la agregación.

Almacenamiento fuera del montón y en columnas

En el almacenamiento fuera del montón y columnar de Spark, entra en juego el motor de Tungsteno. 

Tungsteno introdujo varias optimizaciones que mejoraron el rendimiento de Spark:

  • Gestión de la memoria fuera del montón: Spark ahora almacena algunos datos fuera del montón de la JVM, reduciendo la sobrecarga de la recogida de basura y haciendo que la gestión de la memoria sea más predecible.
  • Almacenamiento en formato binario: Los datos se almacenan en una forma binaria compacta y fácil de almacenar en caché, lo que mejora el uso de la CPU y permite la ejecución vectorizada.
  • Algoritmos conscientes de la memoria caché: Ahora Spark puede utilizar las cachés de la CPU de forma más eficaz, evitando lecturas innecesarias de la RAM o del disco.

Y si trabajas con DataFrames, ya estás utilizando estas optimizaciones bajo el capó. Esa es una de las razones por las que insisto en que la gente utilice DataFrames y APIs SQL en lugar de RDDs en bruto. Obtendrás toda la potencia de Catalizador y Tungsteno sin ningún ajuste adicional.

Si trabajas con pipelines de limpieza de datos, verás esto en acción en Limpieza de datos con PySpark.

Mecanismos de tolerancia a fallos

Si trabajas con sistemas distribuidos, sabes una cosa a ciencia cierta: Fracasan. Los nodos se bloquean. Los errores de red ocurren. Los ejecutores se quedan sin memoria y se apagan.

Pero Spark está construido para manejar estos problemas y garantiza que tus trabajos sigan teniendo éxito. 

Profundicemos en cómo Spark garantiza que tus trabajos sigan teniendo éxito, aunque se produzcan algunas inestabilidades.

Seguimiento del linaje RDD

Los Conjuntos de Datos Distribuidos Resistentes (RDD) son la estructura de datos fundamental en Spark. Y se llaman resistentes por una razón. 

Spark utiliza el linaje para garantizar que cada RDD pueda volver a calcularse en caso de fallo de un nodo y pérdida de datos. 

Así, cuando un nodo falla, Spark simplemente vuelve a calcular los datos perdidos utilizando el gráfico de linaje. 

Así es como funciona en la práctica: 

  • Dependencias estrechas (como map() o filter()): Spark sólo necesita la partición perdida para volver a calcular.
  • Dependencias amplias (como groupBy() o join()): Spark puede necesitar obtener datos de varias particiones, ya que puede necesitar la salida de varias etapas. 

Lineage evita la necesidad de gestionar los fallos manualmente. Sin embargo, si tu gráfico de linaje se hace demasiado largo, ya que puede contener cientos de transformaciones, volver a calcular los datos perdidos resulta caro. Ahí es donde entra en juego el punto de control.

Puntos de control y registros de escritura anticipada

Cuando te encuentres con flujos de trabajo complejos o trabajos de streaming, Spark no puede depender únicamente del linaje. Ahí es donde entra en juego el punto de control.

Puedes llamar a rdd.checkpoint() para persistir el estado actual del RDD en una ubicación de almacenamiento fiable (como HDFS). 

A continuación, Spark trunca el linaje. Si se produce un error, recarga los datos directamente en lugar de volver a calcularlos.

En el streaming estructurado, Spark también utiliza registros de escritura anticipada (WAL) para garantizar que los datos no se pierdan en tránsito. 

Esto es lo que la hace tan estable: 

  • Receptores fiables: Escriben los datos entrantes en los registros antes de procesarlos.
  • Latidos del ejecutor: Estas señales regulares confirman que los ejecutores están vivos y sanos.
  • Directorios de puntos de control: Para los trabajos de streaming, mantienen los desplazamientos, los metadatos y el estado de salida para que puedas reanudarlos donde los dejaste.

El punto de control es opcional para los trabajos de procesamiento por lotes, pero necesario para los procesos de flujo. 

Supongamos que tienes un trabajo Spark que ha fallado después de 10 horas de ejecución, pero puedes reanudarlo donde lo dejaste, gracias a los puntos de control y a los WAL. 

Características arquitectónicas avanzadas

A estas alturas, ya has visto cómo Spark procesa los trabajos y gestiona la memoria y los fallos.

En esta sección, nos sumergiremos en algunas de las mejoras arquitectónicas avanzadas que hacen que Spark sea más dinámico, más en tiempo real y más adaptable.

Ejecución adaptativa de consultas (AQE)

AQE se introduce en Spark 3.0 y mejora el rendimiento de las consultas ajustando dinámicamente los planes de ejecución en tiempo de ejecución basándose en las estadísticas recopiladas durante la ejecución.

Entre las características de AQE se incluyen:

  • Cambia dinámicamente las estrategias de unión: Si tu unión de difusión no cabe en la memoria, AQE cambia a una unión de ordenación-fusión.
  • Unificar particiones aleatorias: Fusiona pequeñas particiones aleatorias en otras mayores, lo que reduce la sobrecarga.
  • Maneja datos sesgados: AQE puede dividir particiones sesgadas para equilibrar el tiempo de ejecución.

Esta función cambia las reglas del juego, ya que permite adaptar en tiempo real trabajos que antes requerían ajustes manuales y ensayo y error.

Sólo asegúrate de activarlo explícitamente a través de la configuración (spark.sql.adaptive.enabled = true). Y si utilizas Spark 3.0+, no hay razón para no hacerlo.

Arquitectura de streaming estructurada

El Streaming Estructurado toma el motor de Spark y lo amplía al dominio del tiempo real, sin que tengas que aprender una API completamente nueva.

Entre bastidores, sigue aplicando la micromezcla. Pero se maneja:

  • Gestión de la compensación: Spark hace un seguimiento exacto de los datos que se han leído de su fuente (Kafka, socket, archivo, etc.). Esto proporciona sólidas garantías de "exactamente una vez" si se configura correctamente.
  • Marca de agua: Con las agregaciones basadas en el tiempo, Spark utiliza marcas de agua para decidir cuándo los datos tardíos son demasiado tardíos para incluirlos. Esto es fundamental para el procesamiento en tiempo real.
  • Tiendas estatales: Cuando haces agregaciones en ventana o uniones en flujo, Spark mantiene el estado a través de microlotes. Este estado se almacena en disco y se comprueba para evitar la pérdida de datos.

Lo poderoso aquí es cómo el streaming se parece a la dosificación. Tú escribes un groupBy() o un filter() y Spark se encarga de todo lo demás, haciendo accesible el análisis de flujos sin una cadena de herramientas especializada.

Arquitectura de seguridad

Si estás ejecutando Spark en producción, especialmente en finanzas, sanidad o áreas de negocio similares, necesitas saber cómo gestiona Spark la autenticación, el cifrado y la auditabilidad.

Así que vamos a profundizar en estos temas y en cómo Spark se ocupa de ellos.

Autenticación y encriptación

Spark tiene muchas funciones de seguridad que primero debes activar. Pero una vez activado, Spark ofrece una sólida caja de herramientas para la comunicación y la autenticación seguras: 

  • Autenticación (SASL): Spark utiliza la Capa Simple de Autenticación y Seguridad (SASL) para verificar que sólo los usuarios y servicios autorizados pueden enviar trabajos o conectarse al clúster.
  • Cifrado en tránsito (AES-GCM, SSL/TLS): Spark encripta la comunicación entre nodos utilizando AES-GCM (encriptación autenticada) o TLS. Esto protege los datos de trabajo contra el espionaje, lo que es especialmente importante en entornos de varios inquilinos o en la nube.
  • Integración de Kerberos: Si estás ejecutando en Hadoop/YARN, Spark se integra con Kerberos para la autenticación segura de usuarios. Esto vincula tus trabajos de Spark directamente a los sistemas de gestión de identidad y acceso de la empresa.
  • Control de acceso a la IU: La interfaz web de Spark puede filtrar información sensible (como registros, rutas de entrada, consultas SQL), así que configura spark.acls.enable=true y spark.ui.view.acls y spark.ui.view.acls.groups para restringirla.

Puedes consultar todas las funciones de seguridad en la documentación oficial de Spark. Compruébalo y asegúrate de que activas las funciones que necesitas para proteger tus aplicaciones Spark.

Auditoría y cumplimiento

También es fundamental registrar quién hizo qué y cuándo. 

Compatible con Spark: 

  • Registro de sucesos: Cuando está activada (spark.eventLog.enabled=true), Spark graba en disco cada evento de trabajo, etapa y tarea. Puedes utilizar estos registros para reproducir el historial de trabajos o cumplir requisitos de auditoría.
  • Control de acceso basado en roles (RBAC): Spark no proporciona RBAC, pero si utilizas Spark a través de una plataforma como Databricks, EMR u OpenShift, normalmente obtendrás RBAC en la capa de infraestructura. Spark envía trabajos utilizando una identidad definida, que controla el acceso tanto a los datos como a los recursos informáticos.
  • Enmascaramiento de datos y control de acceso en origen: Spark lee de muchas fuentes(Parquet, Delta Lake, Hive, etc.), y tu control de acceso debe aplicarse allí.

Patrones de optimización del rendimiento

Spark es bastante potente y rápido, y se puede optimizar para que sea aún más rápido si sabes dónde hacer los ajustes necesarios. 

Hay varias áreas en las que puedes intentar optimizar para sacar el máximo partido a Spark. Así que vamos a profundizar en cada área.

Optimización aleatoria

Si Spark tiene un punto débil, es la mezcla. Las barajadas se producen cuando hay que mover datos entre particiones, normalmente después de transformaciones amplias como groupByKey(), distinct(), o join().

Y cuando las mezclas van mal, puedes tener una E/S masiva del disco, largas pausas en la recogida de basura o tareas desviadas que nunca terminan. 

He aquí cómo puedes mejorar las barajadas:

  • Prefiere reduceByKey() a groupByKey(): reduceByKey() agrega localmente antes de barajar. groupByKey() envía todo por la red.
  • Reparte inteligentemente: Utiliza .repartition(n) para aumentar el paralelismo, o .coalesce(n) para reducirlo. No lo dejes en manos de la partición por defecto de Spark.
  • Utiliza las uniones de difusión (sabiamente): Si un conjunto de datos es lo suficientemente pequeño, difúndelo a todos los trabajadores. Configura spark.sql.autoBroadcastJoinThreshold para controlar el límite de tamaño.
  • Evita collect(): Evítalo siempre que sea posible, ya que llevar los datos al conductor mata el rendimiento.

Pautas de configuración de la memoria

Ajustar la memoria de Spark puede ser toda una ciencia, pero puedes utilizar la siguiente lista de comprobación para hacerlo más fácil:

  • Asigna memoria suficiente: Empieza con al menos 6 GB de memoria para el clúster Spark y ajústalo en función de tus necesidades específicas.
  • Considera la fracción de memoria de Spark: Por defecto, el 60% es la fracción de memoria en Spark. Auméntala si tus aplicaciones dependen mucho de las operaciones DataFrame/Conjunto de datos o si necesitas más memoria de usuario. 
  • Utiliza el número correcto de núcleos por ejecutor: Normalmente, 3-5 es lo óptimo. Demasiados pocos conduce a la infrautilización, mientras que demasiados conduce a la contención de tareas.
  • Activa la asignación dinámica (si es compatible): Spark puede ampliar o reducir los ejecutores en función de la carga de trabajo. 
spark.dynamicAllocation.enabled=true
spark.dynamicAllocation.minExecutors=2
spark.dynamicAllocation.maxExecutors=20
  • Ajusta la fracción de almacenamiento: Si necesitas más caché, aumenta el valor de spark.memory.storageFraction.
  • Monitoriza y perfila el uso de la memoria: Utiliza herramientas como la interfaz de usuario de Spark o VisualVM para realizar un seguimiento del consumo de memoria y localizar los cuellos de botella.

Ajustar la configuración de la memoria puede ayudar significativamente. Una vez reduje un trabajo de 30 minutos a 8 minutos adaptando la configuración de la memoria, sin cambiar una sola línea de código.

Fórmulas de dimensionamiento de grupos

Esta es la parte en la que la mayoría de los equipos se equivocan, porque adivinan el tamaño del grupo en lugar de estimarlo correctamente. 

Pero puedes hacerlo mejor utilizando las fórmulas siguientes: 

  1. Determina el número de particiones: 
    • Calcula el número de particiones necesarias en función del tamaño de tus datos y del tamaño de partición deseado. 
    • Una pauta estándar es tener una partición por cada 128 MB a 256 MB de datos sin comprimir.
    • Fórmula: Número de Particiones = Redondear (Tamaño total de los datos ÷ Tamaño de la partición).
  2. Calcula el número total de núcleos: 
    • El número de núcleos necesarios debe ser suficiente para procesar todas las particiones en paralelo.
    • Fórmula: Total Núcleos = Redondeo(Número de Particiones ÷ Particiones por Núcleo).
  3. Determina la memoria por ejecutor: 
    • Calcula la cantidad de memoria que necesita cada ejecutor en función de sus núcleos, el tamaño de la partición y la sobrecarga.
    • Fórmula: Memoria por Ejecutor = Memoria Base × (1 + Porcentaje de Sobrecarga).
  4. Calcula el número de ejecutores: 
    • Determina el número de ejecutores en función del número total de núcleos y de núcleos por ejecutor.
    • Fórmula: Número de Ejecutores = Redondeo(Total Núcleos ÷ Núcleos por Ejecutor).
  5. Calcula la memoria total: 
    • Calcula la memoria total necesaria para el clúster en función del número de ejecutores y de la memoria por ejecutor.
    • Fórmula: Memoria total = Número de Ejecutores × Memoria por Ejecutor.

Por ejemplo: 

  • Entrada: 500 GB de datos y un tamaño de partición de ~128 MB
  • Particiones: ~4.000 particiones
  • Núcleos: 4.000 particiones / 4 particiones por núcleo = 1.000
  • Memoria por ejecutor: Supón 8 GB por ejecutor y un 20% de gastos generales. 8 GB * 1,20 = 9,6 GB
  • Ejecutores: 1.000 núcleos / 4 núcleos por ejecutor = 250 ejecutores
  • Memoria total: 250 ejecutores * 9,6 GB = 2.400 GB

Pero recuerda: Esto es sólo una estimación. Puedes utilizarlo como punto de partida y luego optimizarlo aún más mediante la elaboración de perfiles.

Tendencias arquitectónicas emergentes

Spark existe desde hace una década, pero sigue siendo bastante actual. Está evolucionando más rápido que nunca, gracias a las plataformas nativas de la nube, la aceleración de la GPU y una mayor integración del ML.

Si hoy utilizas Spark de la misma forma que hace tres años, probablemente estés dejando el rendimiento sobre la mesa y perdiéndote nuevas funciones geniales.

Veamos algunas de las más recientes.

Motor Photon (Databricks)

Si trabajas con Databricks, probablemente ya hayas trabajado con Photon y hayas oído hablar de él.

Si quieres saber más sobre Databricks, te recomiendo el curso Introducción a Databricks.

Photon es el motor de nueva generación de la plataforma Lakehouse de Databricks que proporciona un rendimiento de consulta rápido a bajo coste. Es compatible con las API de Spark, por lo que no necesitas adaptar tu código Spark para utilizarlo. 

Ayuda a potenciar significativamente tu código SQL y PySpark.

Photon incluye las siguientes funciones: 

  • Ejecución vectorizada: Photon procesa los datos en lotes columnares, aprovechando las instrucciones SIMD (Single Instruction, Multiple Data) de la CPU para realizar operaciones sobre varios valores simultáneamente. Spark tradicional utiliza la ejecución fila a fila y depende en gran medida de la JVM para la asignación de memoria y la recogida de basura.
  • Tiempo de ejecución de C++ (sin sobrecarga de JVM): No hay recolección de basura de Java, que puede ser un cuello de botella en grandes trabajos de Spark. La memoria se gestiona con precisión en C++.
  • Optimizaciones de consulta mejoradas: Photon se integra profundamente con el optimizador Catalyst de Spark, pero también incluye sus optimizaciones durante la ejecución (como filtrado en tiempo de ejecución, rutas de código adaptables, optimizaciones de unión y agregación). 
  • Aceleración por hardware: Compatibilidad con hardware moderno (como GPUs NVIDIA, conjuntos de instrucciones AVX-512 para CPUs Intel, procesadores Graviton (ARM) en AWS). 

Spark sin servidor

Sin servidor es fantástico, ya que significa que no tienes que gestionar clusters, preaprovisionar recursos, y sólo pagas por el tiempo que Spark está funcionando. 

Y los servicios sin servidor para Spark ya están en marcha, como Databricks Serverless, AWS Glue y GCP Dataproc Serverless.

Y he aquí por qué es increíble:

  • Escala automática: La plataforma escala la computación en función de las necesidades reales de tu trabajo, lo que significa que no tienes que adivinar cuántos nodos necesitas.
  • Coste-eficacia: Sólo pagas por lo que utilizas. Se acabó pagar por servidores inactivos. 
  • Simplicidad: No es necesario que te ocupes de la instalación, configuración o mantenimiento del clúster, ya que esto se hace por ti.
  • Rendimiento: Es posible conseguir tiempos de ejecución más rápidos, ya que la configuración y la puesta a punto están optimizadas para ti.

Spark sin servidor es ideal para análisis interactivos, trabajos ad hoc o cargas de trabajo impredecibles.

Pero ten cuidado: los pipelines consistentes y de larga duración pueden seguir siendo más baratos en clusters fijos. Mide siempre tanto el coste como la latencia.

Integración de MLflow

Si estás haciendo machine learning a escala y tu objetivo es llevar los modelos a producción, Spark por sí solo no es suficiente. Necesitas principios MLOps, como el seguimiento de experimentos, el versionado de modelos y la reproducibilidad. Ahí es donde encaja MLflow

MLflow se integra ahora con Spark y aporta una pila completa de MLOPs a tus pipelines.

Puedes hacerlo: 

  • Experimentos del programa: Registra parámetros, métricas y artefactos de los trabajos de Spark ML utilizando mlflow.log_param() y mlflow.log_metric().
  • Modelos de versión: Guarda modelos de pyspark.ml o sklearn directamente en el registro de modelos de MLflow.
  • Sirve modelos: Despliega modelos entrenados en puntos finales REST utilizando el servicio de modelos de MLflow.

No necesitas cambiar de herramienta. Sigues utilizando Spark para el entrenamiento, la ingeniería de características y la puntuación, mientras utilizas MLflow para las tareas MLOPs.

Conclusión

Si no sabes mucho sobre Spark, es como una gigantesca caja negra. Escribes algo de código PySpark, le das a ejecutar y esperas que funcione. 

A veces me funcionaba bien, otras me llevaba a largas sesiones de depuración y de averiguar qué fallaba. 

No fue hasta que empecé a mirar entre bastidores que las cosas cobraron sentido para mí. Y tardé bastante en entender lo que pasaba.

Esto es en lo que me centraría si volviera a empezar de cero: 

  • Aprende cómo Spark divide tu código en trabajos, etapas y tareas.
  • Comprende la memoria.
  • Cuidado con los barajados.
  • Empieza poco a poco y haz las cosas en modo local. Ensúciate las manos.

Eso es precisamente lo que hemos aprendido en este artículo.

Si quieres seguir aprendiendo, aquí tienes algunos recursos para principiantes que te recomiendo:

Aprende PySpark desde cero

Aprende a aprovechar los grandes conjuntos de datos y el aprendizaje automático.
Empieza a hacer Upskilling gratis

Preguntas frecuentes

¿Cómo elijo el gestor de clústeres adecuado para mi despliegue de Spark?

Spark admite varios gestores de clústeres (YARN, Mesos, Kubernetes y autónomo). Tu elección depende de la infraestructura existente, las necesidades de compartir recursos y la experiencia operativa: YARN se integra bien en los clústeres Hadoop, Kubernetes ofrece portabilidad en contenedores, y Mesos destaca en el aislamiento multiinquilino.

¿Qué es el servicio de reproducción aleatoria externa y cómo mejora el rendimiento?

El servicio shuffle externo desacopla el servicio de archivos shuffle de los ciclos de vida de los ejecutores, lo que permite una asignación dinámica y reduce la pérdida de datos durante el desalojo de los ejecutores. Mantiene disponibles los archivos aleatorios incluso después de que se cierren los ejecutores, lo que acelera los reintentos de etapas y conserva la E/S del disco cuando hay mucha carga.

¿Cómo funcionan internamente las uniones de difusión y cuándo debo utilizarlas?

Para las uniones de difusión, Spark envía una pequeña tabla de búsqueda a cada ejecutor para evitar barajar todos los datos. Utilízalos cuando uno de los lados de la unión esté por debajo de spark.sql.autoBroadcastJoinThreshold (por defecto 10 MB), ya que reducen drásticamente la E/S de red y aceleran las uniones en distribuciones de claves sesgadas.

¿Cuáles son las mejores prácticas para ajustar la recolección de basura de la JVM en Spark?

Supervisa las pausas de GC mediante la interfaz de usuario de Spark o herramientas como VisualVM y prefiere el colector G1GC por sus bajos tiempos de pausa. Asigna memoria al ejecutor con margen para gastos generales (spark.executor.memoryOverhead ) y ajusta -XX:InitiatingHeapOccupancyPercent para que active la GC antes, evitando largas pausas de parada del mundo.

¿Cómo puedo aprovechar la aceleración de la GPU para acelerar los trabajos de Spark?

Utiliza el Acelerador NVIDIA RAPIDS para Apache Spark para descargar de forma transparente las operaciones SQL y DataFrame a las GPU. Se conecta al motor de ejecución de Spark, sustituyendo los operadores basados en la CPU por equivalentes acelerados en la GPU y ofreciendo un procesamiento hasta 10 veces más rápido para las cargas de trabajo adecuadas.

¿Cuál es la diferencia entre la asignación de recursos estática y dinámica en Spark?

La asignación estática fija el número de ejecutores durante toda la vida del trabajo, ofreciendo previsibilidad a costa de posibles recursos ociosos. La asignación dinámica permite a Spark solicitar o liberar ejecutores en función de las tareas pendientes y la carga de trabajo, mejorando la utilización del clúster para trabajos fluctuantes, ideal para entornos compartidos.

¿Cómo debo configurar Spark para obtener un rendimiento óptimo en sistemas de almacenamiento en la nube como S3?

Activa la aceleración de transferencia S3, ajusta spark.hadoop.fs.s3a.connection.maximum, y utiliza la vista consistente (S3A v2) para manejar la consistencia eventual. Une los archivos pequeños antes de escribir y ten en cuenta los committers de S3A para reducir la sobrecarga de la operación de lista y mejorar el rendimiento de escritura.

¿Cómo puedo asegurar las comunicaciones de Spark con Kerberos y TLS?

Activa TLS para RPC (spark.ssl.enabled) y configura SASL/Kerberos (spark.authenticate and spark.kerberos.keytab) para reforzar la autenticación mutua. Almacena las credenciales en un keytab seguro, accesible desde HDFS, y restringe el acceso a la interfaz de usuario de Spark mediante la configuración ACL para evitar la exposición no autorizada de los datos.

¿Qué son las UDF de Pandas y cuándo son más eficientes que las UDF normales?

Las UDFs de Pandas (UDFs vectorizadas) utilizan Apache Arrow para intercambiar datos por lotes entre la JVM y Python, reduciendo drásticamente la sobrecarga de serialización. Superan a las UDF tradicionales fila a fila en operaciones numéricas complejas, especialmente al procesar grandes lotes columnares en PySpark.

¿Qué ventajas aporta la API DataSource V2 sobre la V1 para las fuentes de datos personalizadas?

DataSource V2 ofrece una interfaz más limpia y modular que admite filtros push-down, poda de particiones y fuentes de streaming de forma nativa. Permite un control de lectura/escritura de grano fino y una mejor integración con el optimizador Catalyst de Spark, lo que se traduce en un mayor rendimiento y una mayor facilidad de mantenimiento para los conectores a medida.


Patrick Brus's photo
Author
Patrick Brus
LinkedIn

Soy un Ingeniero de la Nube con una sólida base de Ingeniería Eléctrica, aprendizaje automático y programación. Mi carrera comenzó en visión por ordenador, centrándome en la clasificación de imágenes, antes de pasar a MLOps y DataOps. Me especializo en la creación de plataformas MLOps, el apoyo a los científicos de datos y la entrega de soluciones basadas en Kubernetes para agilizar los flujos de trabajo de aprendizaje automático.

Temas

¡Aprende más sobre Spark con estos cursos!

Curso

Machine learning con PySpark

4 h
26.3K
Aprende a hacer predicciones a partir de datos con Apache Spark, utilizando árboles de decisión, regresión logística, regresión lineal, conjuntos y pipelines.
Ver detallesRight Arrow
Comienza el curso
Ver másRight Arrow
Relacionado

blog

Contratos de datos desmitificados: Todo lo que necesitas saber

Lograr la escalabilidad en los sistemas de datos distribuidos y reducir los errores.
Mike Shakhomirov's photo

Mike Shakhomirov

11 min

Tutorial

Tutorial de Pyspark: Primeros pasos con Pyspark

Descubre qué es Pyspark y cómo se puede utilizar, con ejemplos.
Natassha Selvaraj's photo

Natassha Selvaraj

10 min

Tutorial

Instalación de PySpark (Todos los sistemas operativos)

Este tutorial mostrará la instalación de PySpark y cómo gestionar las variables de entorno en los sistemas operativos Windows, Linux y Mac.

Olivia Smith

8 min

Tutorial

Sinapsis Azure: Guía paso a paso para principiantes

Una guía fácil de seguir para que los principiantes aprendan Azure Synapse, que abarca desde la configuración de tu espacio de trabajo hasta la integración de datos y la ejecución de análisis.
Moez Ali's photo

Moez Ali

13 min

Tutorial

Multiprocesamiento en Python: Guía de hilos y procesos

Aprende a gestionar hilos y procesos con el módulo de multiprocesamiento de Python. Descubre las técnicas clave de la programación paralela. Mejora la eficacia de tu código con ejemplos.
Kurtis Pykes 's photo

Kurtis Pykes

7 min

Tutorial

Primeros pasos con AWS Athena: Guía práctica para principiantes

Esta guía práctica te ayudará a empezar a utilizar AWS Athena. Explora su arquitectura y características y aprende a consultar datos en Amazon S3 utilizando SQL.
Tim Lu's photo

Tim Lu

15 min

Ver másVer más