¿Cómo aislar particiones basadas en una columna?

Estoy buscando realizar un trabajo aislado dentro de una sola partición de un marco de datos Spark. Quiero iterar sobre filas dentro de una sola partición sin ningún otro contenido de partición interactuando.

repartition("column") va a tener el valor de la columna, luego modulo en el número predeterminado de particiones (spark.default.parallelism ser 200). Esto puede co-mingle por contenido de partición de columna en una partición en Spark.

Un ejemplo: 10 filas donde quiero iterar sobre registros dentro de un solo grouping in isolation (I purposefully repartition(n, col) tales que n es menor o igual al número de grupos, pero esto ilustra el punto:

val jsonString   = """[{"eventId": 1, "grouping": 1, "debug": "foo"},
{"eventId": 2, "grouping": 1, "debug": "foo"},
{"eventId": 3, "grouping": 1, "debug": "foo"},
{"eventId": 4, "grouping": 2, "debug": "foo"},
{"eventId": 5, "grouping": 2, "debug": "foo"},
{"eventId": 6, "grouping": 2, "debug": "foo"},
{"eventId": 7, "grouping": 2, "debug": "foo"},
{"eventId": 8, "grouping": 3, "debug": "foo"},
{"eventId": 9, "grouping": 3, "debug": "foo"},
{"eventId": 10, "grouping": 3, "debug": "foo"}]"""

val df = spark.read.json(Seq(jsonString).toDS)
spark.createDataFrame(df.repartition(col("grouping")).rdd.mapPartitionsWithIndex((i,itr) => {itr.map(row => {Row.fromSeq(row.toSeq.updated(0, "test"))}) }), df.schema).show

val result = spark.createDataFrame(df.repartition(df.select("grouping").distinct.count.toInt, col("grouping"))
  .rdd
  .mapPartitionsWithIndex((i,itr) => {
    val visitor = new AtomicLong()
    itr.map(row => {
      val debugIndex = row.fieldIndex("debug")
      val groupingIndex = row.fieldIndex("grouping")
      Row.fromSeq(row.toSeq.updated(debugIndex, s"Grouping: ${row.get(groupingIndex)}, Partition Index: ${i}}. Visitor: ${visitor.getAndIncrement()}"))
    }) 
  }), df.schema)

result
.sort("grouping")
.show(false)
+--------------------------------------------+-------+--------+
|debug                                       |eventId|grouping|
+--------------------------------------------+-------+--------+
|Grouping: 1, Partition Index: 2}. Visitor: 1|2      |1       |
|Grouping: 1, Partition Index: 2}. Visitor: 0|1      |1       |
|Grouping: 1, Partition Index: 2}. Visitor: 2|3      |1       |
|Grouping: 2, Partition Index: 2}. Visitor: 3|4      |2       |
|Grouping: 2, Partition Index: 2}. Visitor: 4|5      |2       |
|Grouping: 2, Partition Index: 2}. Visitor: 5|6      |2       |
|Grouping: 2, Partition Index: 2}. Visitor: 6|7      |2       |
|Grouping: 3, Partition Index: 0}. Visitor: 0|8      |3       |
|Grouping: 3, Partition Index: 0}. Visitor: 1|9      |3       |
|Grouping: 3, Partition Index: 0}. Visitor: 2|10     |3       |
+--------------------------------------------+-------+--------+

Aunque yo repartition en tres particiones basadas en mi grouping columna, dos de estos grupos terminan en una partición. Así cuando yo mapPartitions cualquier iteración se realiza sobre múltiples groupings datos.

¿Cómo puedo aislar eficazmente los subconjuntos de mi marco de datos basados en un nombre de columna, y procesar/agregar sobre filas en aislamiento? ¿Necesito invertir el ingeniero y hackear mi columna de partición de tal manera que no dos hashes caigan en el mismo número de partición modulo? ¿O debo contabilizar más de un grupo que cae en una sola partición?

Esto también parece un impactor de rendimiento si creo que estoy dividiendo mis datos en particiones n, pero podría tener colisiones de particiones de nombres donde más de un subconjunto termina en una partición, entonces mi planificación y dimensionamiento de particiones es para nada.

Pregunta hecha hace 3 años, 4 meses, 6 días - Por byteexplorer


2 Respuestas:

  • Para aislar particiones basadas en una columna específica en Spark, puedes seguir los siguientes pasos:

    • Utiliza la función `partitionBy` para particionar el DataFrame por la columna que deseas.
    • Luego, puedes realizar operaciones dentro de cada partición utilizando la función `mapPartitions`. Dentro de esta función, puedes iterar sobre las filas de cada partición y realizar transformaciones o agregaciones necesarias.

    Aquí te muestro un ejemplo de cómo puedes aislar particiones basadas en la columna "grouping" y procesar/agregar sobre filas en aislamiento:

    scala
    import org.apache.spark.sql.functions.col
    
    // Leer los datos
    val jsonString = """[{"eventId": 1, "grouping": 1, "debug": "foo"},
    {"eventId": 2, "grouping": 1, "debug": "foo"},
    {"eventId": 3, "grouping": 1, "debug": "foo"},
    {"eventId": 4, "grouping": 2, "debug": "foo"},
    {"eventId": 5, "grouping": 2, "debug": "foo"},
    {"eventId": 6, "grouping": 2, "debug": "foo"},
    {"eventId": 7, "grouping": 2, "debug": "foo"},
    {"eventId": 8, "grouping": 3, "debug": "foo"},
    {"eventId": 9, "grouping": 3, "debug": "foo"},
    {"eventId": 10, "grouping": 3, "debug": "foo"}]"""
    
    val df = spark.read.json(Seq(jsonString).toDS)
    
    // Particionar el DataFrame por la columna "grouping"
    val partitionedDF = df.repartition(col("grouping"))
    
    // Procesar/agregar sobre filas en aislamiento
    val result = partitionedDF.rdd.mapPartitions { partition =>
      partition.map { row =>
        // Realizar las operaciones necesarias en cada fila
        // En este ejemplo, simplemente mostramos la fila
        println(row)
        row
      }
    }.toDF()
    
    result.show(false)
    

    Este código particiona el DataFrame df por la columna "grouping" y luego itera sobre cada partición de forma aislada para procesar/agregar sobre las filas dentro de cada partición. Puedes reemplazar la operación dentro de mapPartitions con las transformaciones o agregaciones específicas que necesitas realizar en cada fila de la partición.

    Espero que esta información te sea útil para aislar particiones basadas en una columna en Spark. ¡Déjame saber si necesitas ayuda adicional!

    Respondida el Dec 19, 2020 a las 04:31 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • Para aislar particiones basadas en una columna y procesar/agregar sobre filas en aislamiento en Spark, puedes utilizar las siguientes técnicas:

    1. Usar mapPartitions:

    mapPartitions te permite aplicar una función a cada partición del DataFrame. Puedes usar esto para iterar sobre filas dentro de una sola partición y realizar operaciones específicas de partición.

    Ejemplo:

    
    df.mapPartitions(partition => {
      // Código para procesar filas dentro de la partición
    }).collect()
    

    2. Repartir por columna y partición por columna:

    Puedes repartir el DataFrame por la columna que desees aislar y luego volver a particionarlo por la misma columna. Esto garantiza que cada partición contenga solo filas con el mismo valor para la columna de partición.

    Ejemplo:

    
    df.repartition(col("column")).partitionBy(col("column"))
    

    3. Usar cache y persist:

    Puedes almacenar en caché o persistir el DataFrame después de repartirlo y particionarlo. Esto evitará que Spark vuelva a cargar los datos de disco cuando accedas a diferentes particiones.

    Ejemplo:

    
    df.repartition(col("column")).partitionBy(col("column")).cache()
    

    4. Usar withColumn para agregar una columna de partición:

    Puedes agregar una columna de partición basada en la columna que desees aislar utilizando withColumn. Esto te permite realizar operaciones de partición personalizadas.

    Ejemplo:

    
    df.withColumn("partition_column", hash(col("column")))
    

    5. Usar técnicas de ingeniería de características:

    En algunos casos, puede ser necesario transformar o codificar la columna de partición para garantizar una distribución uniforme entre las particiones.

    Ejemplo:

    
    df.withColumn("encoded_column", encode(col("column")))
    

    Consideraciones de rendimiento:

    Repartir y particionar puede incurrir en gastos generales de rendimiento. Considera el número de particiones necesarias y la cantidad de datos que se procesarán. El almacenamiento en caché y la persistencia pueden mejorar el rendimiento, pero también pueden consumir memoria.

    • Las técnicas de ingeniería de características pueden introducir complejidad adicional y pueden no ser siempre necesarias.

    Elige la técnica que mejor se adapte a tus requisitos específicos y considera las implicaciones de rendimiento antes de implementar.

    Respondida el Dec 19, 2020 a las 04:38 - por Gemini

    Votos positivos: 0 | Votos negativos: 0