apache-spark rdd

apache spark - ¿Se prefiere groupByKey alguna vez sobre reduceByKey?



apache-spark rdd (3)

Siempre uso reduceByKey cuando necesito agrupar datos en RDD, porque realiza una reducción del lado del mapa antes de mezclar datos, lo que a menudo significa que se mezclan menos datos y, por lo tanto, obtengo un mejor rendimiento. Incluso cuando la función de reducción del lado del mapa recopila todos los valores y en realidad no reduce la cantidad de datos, sigo usando reduceByKey , porque supongo que el rendimiento de reduceByKey nunca será peor que groupByKey . Sin embargo, me pregunto si esta suposición es correcta o si hay situaciones en groupByKey debería preferirse groupByKey


Creo que hay otros aspectos del problema ignorados por climbage y eliasah :

  • legibilidad del código
  • mantenibilidad de código
  • tamaño de la base de código

Si la operación no reduce la cantidad de datos, tiene que ser semánticamente equivalente a GroupByKey . Supongamos que tenemos RDD[(Int,String)] :

import scala.util.Random Random.setSeed(1) def randomString = Random.alphanumeric.take(Random.nextInt(10)).mkString("") val rdd = sc.parallelize((1 to 20).map(_ => (Random.nextInt(5), randomString)))

y queremos concatenar todas las cadenas para una clave dada. Con groupByKey es bastante simple:

rdd.groupByKey.mapValues(_.mkString(""))

La solución ingenua con reduceByKey ve así:

rdd.reduceByKey(_ + _)

Es breve y posiblemente fácil de entender, pero tiene dos problemas:

  • es extremadamente ineficiente ya que crea un nuevo objeto String cada vez *
  • sugiere que la operación que realiza es menos costosa de lo que es en realidad, especialmente si analiza solo DAG o cadena de depuración

Para tratar el primer problema, necesitamos una estructura de datos mutable:

import scala.collection.mutable.StringBuilder rdd.combineByKey[StringBuilder]( (s: String) => new StringBuilder(s), (sb: StringBuilder, s: String) => sb ++= s, (sb1: StringBuilder, sb2: StringBuilder) => sb1.append(sb2) ).mapValues(_.toString)

Todavía sugiere algo más que realmente está sucediendo y es bastante detallado, especialmente si se repite varias veces en su secuencia de comandos. Por supuesto, puede extraer funciones anónimas

val createStringCombiner = (s: String) => new StringBuilder(s) val mergeStringValue = (sb: StringBuilder, s: String) => sb ++= s val mergeStringCombiners = (sb1: StringBuilder, sb2: StringBuilder) => sb1.append(sb2) rdd.combineByKey(createStringCombiner, mergeStringValue, mergeStringCombiners)

pero al final del día todavía significa un esfuerzo adicional para comprender este código, mayor complejidad y ningún valor agregado real. Una cosa que encuentro particularmente preocupante es la inclusión explícita de estructuras de datos mutables. Incluso si Spark maneja casi toda la complejidad, significa que ya no tenemos un código elegante, referencialmente transparente.

Mi punto es que si realmente reduce la cantidad de datos por todos los medios, use reduceByKey . De lo contrario, hará que su código sea más difícil de escribir, más difícil de analizar y no obtenga nada a cambio.

Nota :

Esta respuesta se centra en la API Scala RDD . La implementación actual de Python es bastante diferente de su contraparte JVM e incluye optimizaciones que proporcionan una ventaja significativa sobre la implementación ingeniosa de reduceByKey en el caso de operaciones similares a groupBy .

Para la API de Dataset consulte Marco de datos / Grupo de conjuntos de datos Por comportamiento / optimización .

* Vea el rendimiento de Spark para Scala vs Python para un ejemplo convincente


No inventaré la rueda, de acuerdo con la documentación del código, la operación groupByKey agrupa los valores de cada clave en el RDD en una sola secuencia que también permite controlar la partición del par clave-valor RDD resultante al pasar un Partitioner .

Esta operación puede ser muy costosa. Si está agrupando para realizar una agregación (como una suma o promedio) sobre cada clave, el uso de reduceByKey o reduceByKey proporcionará un rendimiento mucho mejor.

Nota: Tal como se implementa actualmente, groupByKey debe poder contener todos los pares clave-valor para cualquier clave en la memoria. Si una clave tiene demasiados valores, puede resultar en un OOME.

De hecho, prefiero la operación combineByKey , pero a veces es difícil entender el concepto del combinador y la fusión si no está muy familiarizado con el paradigma de reducción de mapas. Para esto, puede leer la biblia yahoo map-reduce here , que explica bien este tema.

Para obtener más información, le aconsejo que lea el código PairRDDFunctions .


reduceByKey y groupByKey usan combineByKey con diferentes semánticas de combinación / fusión.

La diferencia clave que veo es que groupByKey pasa la bandera ( mapSideCombine=false ) al motor aleatorio. A juzgar por el problema SPARK-772 , esta es una pista para que el motor aleatorio no ejecute el combinador de mapas cuando el tamaño de los datos no va a cambiar.

Entonces, diría que si está tratando de usar reduceByKey para replicar groupByKey , es posible que vea un ligero impacto en el rendimiento.