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.