Big Data y Procesamiento Distribuido · Ingeniería de Datos

Particionamiento, shuffle y datos sesgados en Spark

Explicar el costo del shuffle en Spark, diagnosticar datos sesgados y aplicar técnicas (broadcast join, salted key, AQE) para equilibrar el paralelismo.

Particionamiento, shuffle y datos sesgados en Spark

El costo real está en mover, no en calcular En Spark el cuello de botella rara vez es el CPU: es mover datos de un nodo a otro. Entender el particionado y el shuffle es lo que separa un pipeline que escala de uno que se atasca en el tercer día.

Un DataFrame se divide en particiones: bloques de filas que los ejecutores procesan en paralelo. El número de particiones fija el grado de paralelismo. Mientras los datos caben dentro de una partición y se procesan donde están, todo va rápido; el problema aparece cuando una operación necesita reagrupar filas por clave y estas viven en nodos distintos.

Qué es una partición

La partición es la unidad de trabajo en paralelo: una tarea por partición. Muy pocas particiones dejan núcleos ociosos; demasiadas generan un overhead de planificación que ahoga al scheduler. El valor por defecto de spark.sql.shuffle.partitions (200) es un punto de partida, no un acierto garantizado.

El shuffle: cuando los datos cambian de nodo

Operaciones como groupBy, join o repartition rompen la localidad: para reunir todas las filas de una misma clave, Spark debe barajar (shuffle) datos entre ejecutores, escribiendo salidas a disco, transfiriéndolas por red y volviendo a particionarlas por clave.

Diagrama de tres bandas: particiones de entrada repartidas en nodos; en el centro la fase de shuffle con escritura a disco, red y ordenamiento; abajo, particiones de salida reagrupadas por clave donde una clave domina el volumen.
El shuffle redistribuye filas por clave: la banda central es la más cara porque toca disco y red, y la de abajo muestra cómo una clave dominante concentra el trabajo.

Por qué el shuffle cuesta tanto

Datos sesgados (data skew)

El skew ocurre cuando una clave concentra muchísimas más filas que las demás: una sola partición de salida se vuelve enorme mientras el resto termina rápido. Esa tarea lentísima es el straggler que define el tiempo total del job, y suele terminar en memoria insuficiente (OOM) o volcado a disco (spill).

De la señal en la UI de Spark al correctivo típico.
SíntomaCausa probableRemediación
Una tarea del stage dura 100x másClave con skew extremo (o nulas)Salted key o AQE skew join
Executor muere por OOMPartición de salida gigantescaAumentar particiones, aislar la clave
Spill a disco constantePoca memoria para el agregadoMemorizar/optimizar, filtrar antes
Job lento sin skew claroDemasiadas o muy pocas particionesAjustar shuffle.partitions, coalesce

Estrategias para evitar o aliviar el shuffle

Ajustar el paralelismo: repartition vs coalesce

repartition(n) redistribuye por completo (hace shuffle) y sirve para aumentar o balancear particiones. coalesce(n) reduce el número de particiones sin shuffle completo, ideal para juntar muchas particiones pequeñas antes de escribir. Usar repartition donde bastaba coalesce añade un shuffle costoso e innecesario.

Errores frecuentes

Ejemplo resuelto: join sesgado por clave nula

Un join de millones de filas se atasca en una sola tarea. Diagnóstico y corrección por etapas:

  1. Inspeccionar la UI: un task del stage de join domina la duración.
  2. Confirmar el skew: contar filas por clave y detectar una dominante (o nulas).
  3. Separar las claves nulas y procesarlas aparte o rellenarlas con un prefijo aleatorio.
  4. Aplicar salted key o habilitar AQE skew join sobre la clave grande.
  5. Repetir la medición y verificar que el straggler desapareció.

¿Para qué sirve en la realidad?

La mayoría de los jobs de producción que "se ponen lentos de repente" no cambian de datos: cambian de distribución. Reconocer el shuffle y el skew permite arreglar el pipeline sin tirar de presupuesto en más clúster.

¿Cuál es normalmente la operación más costosa en un job distribuido de Spark?

Usar coalesce(n) para reducir particiones suele ser más barato que repartition(n) porque evita un shuffle completo.

Cuando una sola clave concentra la mayoría de las filas y vuelve una tarea un straggler, decimos que hay sesgo de (skew).

Clasifica cada medida según si reduce o provoca tráfico de shuffle.

Reduce shuffle
Provoca shuffle

Une cada término con su definición.

Shuffle y skew en Spark

Unidad de trabajo en paralelo
partición
Redistribuir filas por clave entre nodos
shuffle
Una clave domina el volumen
data skew
Join sin shuffle para tabla pequeña
broadcast join
Reduce particiones sin shuffle completo
coalesce

Rendimiento distribuido: partición, shuffle y skew

# Spark a escala
## Particion
### unidad de paralelismo
### equilibrio y tamano
## Shuffle
### groupBy y join
### disco + red + orden
## Skew
### clave dominante
### straggler y OOM
## Mitigacion
### broadcast join
### salted key
### AQE y cache

Spark SQL: tuning de rendimiento (documentación oficial)
Referencia oficial de particionado, broadcast, AQE y configuración de shuffle.

Adaptive Query Execution en Spark 3 (Databricks)
Cómo Spark detecta skew y reoptimiza joins y particiones en tiempo de ejecución.

Tienes un job de join que antes corría en 10 minutos y ahora tarda 3 horas sin que haya crecido el volumen total. ¿Qué revisarías primero en la UI de Spark y qué hipótesis sobre la distribución de claves pondrías a prueba antes de tocar memoria o particiones?