web-dev-qa-db-fra.com

Ecraser des partitions spécifiques dans la méthode d'écriture spark dataframe

Je veux écraser des partitions spécifiques au lieu de tout en spark. J'essaie la commande suivante:

df.write.orc('maprfs:///hdfs-base-path','overwrite',partitionBy='col4')

où df est une image contenant les données incrémentielles à écraser.

hdfs-base-path contient les données de base.

Lorsque j'essaie la commande ci-dessus, elle supprime toutes les partitions et insère celles présentes dans df dans le chemin d'accès hdfs.

Ce que je demande, c’est d’écraser uniquement les partitions présentes dans df sur le chemin hdfs spécifié. Est ce que quelqu'un peut m'aider s'il vous plait?

35
yatin

C'est un problème commun. La seule solution avec Spark jusqu’à la version 2.0 consiste à écrire directement dans le répertoire de la partition, par exemple,

df.write.mode(SaveMode.Overwrite).save("/root/path/to/data/partition_col=value")

Si vous utilisez Spark avant la version 2.0, vous devez empêcher Spark d’émettre des fichiers de métadonnées (car ils annuleront la découverte automatique des partitions) en utilisant:

sc.hadoopConfiguration.set("parquet.enable.summary-metadata", "false")

Si vous utilisez Spark avant la version 1.6.2, vous devrez également supprimer le fichier _SUCCESS dans /root/path/to/data/partition_col=value ou sa présence interrompra la découverte automatique des partitions. (Je recommande fortement d'utiliser 1.6.2 ou une version ultérieure.)

Vous pouvez obtenir quelques détails supplémentaires sur la gestion de grandes tables partitionnées en consultant ma conférence Spark Summit sur Bulletproof Jobs .

33
Sim

Finalement! C’est désormais une fonctionnalité de Spark 2.3.0: https://issues.Apache.org/jira/browse/SPARK-20236

Pour l'utiliser, vous devez définir le paramètre spark.sql.sources.partitionOverwriteMode sur dynamique, le jeu de données doit être partitionné et le mode d'écriture overwrite. Exemple:

spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.write.mode("overwrite").insertInto("partitioned_table")

Je vous recommande de procéder à une répartition basée sur votre colonne de partition avant d'écrire, afin d'éviter de créer 400 fichiers par dossier.

Avant Spark 2.3.0, la meilleure solution serait de lancer des instructions SQL pour supprimer ces partitions, puis de les écrire avec le mode append.

40
Madhava Carrillo

Utilisation de Spark 1.6 ...

HiveContext peut grandement simplifier ce processus. La clé est que vous devez d'abord créer la table dans Hive en utilisant une instruction CREATE EXTERNAL TABLE avec un partitionnement défini. Par exemple:

# Hive SQL
CREATE EXTERNAL TABLE test
(name STRING)
PARTITIONED BY
(age INT)
STORED AS PARQUET
LOCATION 'hdfs:///tmp/tables/test'

A partir de là, supposons que vous ayez une Dataframe avec de nouveaux enregistrements pour une partition spécifique (ou plusieurs partitions). Vous pouvez utiliser une instruction SQL HiveContext pour effectuer un INSERT OVERWRITE à l'aide de cette image de données, ce qui écrasera la table uniquement pour les partitions contenues dans l'image de données:

# PySpark
hiveContext = HiveContext(sc)
update_dataframe.registerTempTable('update_dataframe')

hiveContext.sql("""INSERT OVERWRITE TABLE test PARTITION (age)
                   SELECT name, age
                   FROM update_dataframe""")

Remarque: update_dataframe dans cet exemple a un schéma qui correspond à celui de la table cible test.

Une erreur simple à faire avec cette approche consiste à ignorer l'étape CREATE EXTERNAL TABLE dans Hive et à créer simplement le tableau à l'aide des méthodes d'écriture de l'API Dataframe. Pour les tables à base de parquet en particulier, la table ne sera pas définie correctement pour prendre en charge la fonction INSERT OVERWRITE... PARTITION de Hive.

J'espère que cela t'aides. 

6
vertigokidd

J'ai essayé l'approche ci-dessous pour écraser une partition particulière dans la table Hive.

### load Data and check records
    raw_df = spark.table("test.original")
    raw_df.count()

lets say this table is partitioned based on column : **c_birth_year** and we would like to update the partition for year less than 1925


### Check data in few partitions.
    sample = raw_df.filter(col("c_birth_year") <= 1925).select("c_customer_sk", "c_preferred_cust_flag")
    print "Number of records: ", sample.count()
    sample.show()


### Back-up the partitions before deletion
    raw_df.filter(col("c_birth_year") <= 1925).write.saveAsTable("test.original_bkp", mode = "overwrite")


### UDF : To delete particular partition.
    def delete_part(table, part):
        qry = "ALTER TABLE " + table + " DROP IF EXISTS PARTITION (c_birth_year = " + str(part) + ")"
        spark.sql(qry)


### Delete partitions
    part_df = raw_df.filter(col("c_birth_year") <= 1925).select("c_birth_year").distinct()
    part_list = part_df.rdd.map(lambda x : x[0]).collect()

    table = "test.original"
    for p in part_list:
        delete_part(table, p)


### Do the required Changes to the columns in partitions
    df = spark.table("test.original_bkp")
    newdf = df.withColumn("c_preferred_cust_flag", lit("Y"))
    newdf.select("c_customer_sk", "c_preferred_cust_flag").show()


### Write the Partitions back to Original table
    newdf.write.insertInto("test.original")


### Verify data in Original table
    orginial.filter(col("c_birth_year") <= 1925).select("c_customer_sk", "c_preferred_cust_flag").show()



Hope it helps.

Regards,

Neeraj
2
neeraj bhadani

Au lieu d'écrire directement dans la table cible, je vous suggère de créer une table temporaire comme celle-ci et d'y insérer vos données.

CREATE TABLE tmpTbl LIKE trgtTbl LOCATION '<tmpLocation';

Une fois la table créée, vous écrivez vos données dans la variable tmpLocation.

df.write.mode("overwrite").partitionBy("p_col").orc(tmpLocation)

Ensuite, vous récupéreriez les chemins de partition de la table en exécutant:

MSCK REPAIR TABLE tmpTbl;

Obtenez les chemins de partition en interrogeant les métadonnées Hive comme suit:

SHOW PARTITONS tmpTbl;

Supprimez ces partitions de la trgtTbl et déplacez les répertoires de tmpTbl à trgtTbl

1
Joha

En tant que jatin Wrote, vous pouvez supprimer les paritions de Hive et de path, puis ajouter des données Comme je perdais trop de temps avec elle, j’ai ajouté l’exemple suivant pour d’autres utilisateurs de spark. J'ai utilisé Scala avec étincelle 2.2.1

  import org.Apache.hadoop.conf.Configuration
  import org.Apache.hadoop.fs.Path
  import org.Apache.spark.SparkConf
  import org.Apache.spark.sql.{Column, DataFrame, SaveMode, SparkSession}

  case class DataExample(partition1: Int, partition2: String, someTest: String, id: Int)

 object StackOverflowExample extends App {
//Prepare spark & Data
val sparkConf = new SparkConf()
sparkConf.setMaster(s"local[2]")
val spark = SparkSession.builder().config(sparkConf).getOrCreate()
val tableName = "my_table"

val partitions1 = List(1, 2)
val partitions2 = List("e1", "e2")
val partitionColumns = List("partition1", "partition2")
val myTablePath = "/tmp/some_example"

val someText = List("text1", "text2")
val ids = (0 until 5).toList

val listData = partitions1.flatMap(p1 => {
  partitions2.flatMap(p2 => {
    someText.flatMap(
      text => {
        ids.map(
          id => DataExample(p1, p2, text, id)
        )
      }
    )
  }
  )
})

val asDataFrame = spark.createDataFrame(listData)

//Delete path function
def deletePath(path: String, recursive: Boolean): Unit = {
  val p = new Path(path)
  val fs = p.getFileSystem(new Configuration())
  fs.delete(p, recursive)
}

def tableOverwrite(df: DataFrame, partitions: List[String], path: String): Unit = {
  if (spark.catalog.tableExists(tableName)) {
    //clean partitions
    val asColumns = partitions.map(c => new Column(c))
    val relevantPartitions = df.select(asColumns: _*).distinct().collect()
    val partitionToRemove = relevantPartitions.map(row => {
      val fields = row.schema.fields
      s"ALTER TABLE ${tableName} DROP IF EXISTS PARTITION " +
        s"${fields.map(field => s"${field.name}='${row.getAs(field.name)}'").mkString("(", ",", ")")} PURGE"
    })

    val cleanFolders = relevantPartitions.map(partition => {
      val fields = partition.schema.fields
      path + fields.map(f => s"${f.name}=${partition.getAs(f.name)}").mkString("/")
    })

    println(s"Going to clean ${partitionToRemove.size} partitions")
    partitionToRemove.foreach(partition => spark.sqlContext.sql(partition))
    cleanFolders.foreach(partition => deletePath(partition, true))
  }
  asDataFrame.write
    .options(Map("path" -> myTablePath))
    .mode(SaveMode.Append)
    .partitionBy(partitionColumns: _*)
    .saveAsTable(tableName)
}

//Now test
tableOverwrite(asDataFrame, partitionColumns, tableName)
spark.sqlContext.sql(s"select * from $tableName").show(1000)
tableOverwrite(asDataFrame, partitionColumns, tableName)

import spark.implicits._

val asLocalSet = spark.sqlContext.sql(s"select * from $tableName").as[DataExample].collect().toSet
if (asLocalSet == listData.toSet) {
  println("Overwrite is working !!!")
}

}

1
Ehud Lev

Si vous utilisez DataFrame, vous pouvez éventuellement utiliser la table Hive sur les données . Dans ce cas, vous avez simplement besoin de la méthode call.

df.write.mode(SaveMode.Overwrite).partitionBy("partition_col").insertInto(table_name)

Cela écrasera les partitions contenues dans DataFrame.

Il n'est pas nécessaire de spécifier le format (orc), car Spark utilisera le format de table Hive.

Cela fonctionne très bien dans Spark version 1.6

1
L. Viktor

Vous pouvez faire quelque chose comme ceci pour rendre le travail réentrant (idempotent): (Essayé ceci sur spark 2.2) 

# drop the partition
drop_query = "ALTER TABLE table_name DROP IF EXISTS PARTITION (partition_col='{val}')".format(val=target_partition)
print drop_query
spark.sql(drop_query)

# delete directory
dbutils.fs.rm(<partition_directoy>,recurse=True)

# Load the partition
df.write\
  .partitionBy("partition_col")\
  .saveAsTable(table_name, format = "parquet", mode = "append", path = <path to parquet>)
0
jatin

Je vous suggérerais de nettoyer puis d'écrire de nouvelles partitions avec le mode Append:

import scala.sys.process._
def deletePath(path: String): Unit = {
    s"hdfs dfs -rm -r -skipTrash $path".!
}

df.select(partitionColumn).distinct.collect().foreach(p => {
    val partition = p.getAs[String](partitionColumn)
    deletePath(s"$path/$partitionColumn=$partition")
})

df.write.partitionBy(partitionColumn).mode(SaveMode.Append).orc(path)

Cela ne supprimera que les nouvelles partitions. Après avoir écrit les données, exécutez cette commande si vous avez besoin de mettre à jour métastore:

sparkSession.sql(s"MSCK REPAIR TABLE $db.$table")

Remarque: deletePath suppose que la commande hfds est disponible sur votre système.

0
gorros