web-dev-qa-db-de.com

Überschreiben bestimmter Partitionen in der Spark-Datenrahmen-Schreibmethode

Ich möchte bestimmte Partitionen anstelle von allen Funken überschreiben. Ich versuche den folgenden Befehl:

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

dabei ist df ein Datenrahmen mit den zu überschreibenden inkrementellen Daten.

hdfs-base-path enthält die Stammdaten.

Wenn ich den obigen Befehl ausprobiere, werden alle Partitionen gelöscht und die in df vorhandenen in den HDFS-Pfad eingefügt.

Meine Anforderung ist, nur die Partitionen zu überschreiben, die in df im angegebenen hdfs-Pfad vorhanden sind. Kann mir bitte jemand dabei helfen?

35
yatin

Dies ist ein häufiges Problem. Die einzige Lösung mit Spark bis 2.0 ist das direkte Schreiben in das Partitionsverzeichnis, z.

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

Wenn Sie Spark vor 2.0 verwenden, müssen Sie Spark daran hindern, Metadatendateien zu senden (da diese die automatische Erkennung von Partitionen verhindern).

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

Wenn Sie Spark vor 1.6.2 verwenden, müssen Sie auch die Datei _SUCCESS in /root/path/to/data/partition_col=value löschen, da sonst die automatische Erkennung der Partition beeinträchtigt wird. (Ich empfehle dringend die Verwendung von 1.6.2 oder höher.)

Weitere Informationen zur Verwaltung großer partitionierter Tabellen finden Sie in meinem Spark Summit-Vortrag unter Bulletproof Jobs .

33
Sim

Endlich! Dies ist jetzt eine Funktion in Spark 2.3.0: https://issues.Apache.org/jira/browse/SPARK-20236

Um es verwenden zu können, müssen Sie die Einstellung spark.sql.sources.partitionOverwriteMode auf dynamisch setzen, die Datenmenge muss partitioniert sein und der Schreibmodus overwrite. Beispiel:

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

Ich empfehle, vor dem Schreiben eine Partitionierung basierend auf Ihrer Partitionsspalte durchzuführen, damit Sie nicht mit 400 Dateien pro Ordner enden.

Vor Spark 2.3.0 besteht die beste Lösung darin, SQL-Anweisungen zu starten, um diese Partitionen zu löschen und sie dann im Modus Anhängen zu schreiben.

40

Spark 1.6 verwenden.

Der HiveContext kann diesen Prozess erheblich vereinfachen. Der Schlüssel ist, dass Sie die Tabelle zuerst in Hive erstellen müssen, indem Sie eine CREATE EXTERNAL TABLE-Anweisung mit definierter Partitionierung verwenden. Zum Beispiel:

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

Angenommen, Sie haben ein Dataframe mit neuen Datensätzen für eine bestimmte Partition (oder mehrere Partitionen). Sie können eine SQL-Anweisung von HiveContext verwenden, um einen INSERT OVERWRITE mit diesem Dataframe auszuführen, wodurch die Tabelle nur für die im Dataframe enthaltenen Partitionen überschrieben wird:

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

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

Hinweis: update_dataframe verfügt in diesem Beispiel über ein Schema, das dem der Zielvariabletest entspricht.

Ein einfacher Fehler bei diesem Ansatz besteht darin, den Schritt CREATE EXTERNAL TABLE in Hive zu überspringen und die Tabelle einfach mit den Schreibmethoden der Dataframe-API zu erstellen. Insbesondere für Parkett-basierte Tabellen wird die Tabelle nicht entsprechend definiert, um die INSERT OVERWRITE... PARTITION-Funktion von Hive zu unterstützen.

Hoffe das hilft. 

6
vertigokidd

Ich habe versucht, eine bestimmte Partition in der Hive-Tabelle zu überschreiben.

### 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

Anstatt direkt in die Zieltabelle zu schreiben, würde ich vorschlagen, dass Sie eine temporäre Tabelle wie die Zieltabelle erstellen und Ihre Daten dort einfügen.

CREATE TABLE tmpTbl LIKE trgtTbl LOCATION '<tmpLocation';

Sobald die Tabelle erstellt wurde, schreiben Sie Ihre Daten in die Variable tmpLocation.

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

Dann würden Sie die Tabellenpartitionspfade wiederherstellen, indem Sie Folgendes ausführen:

MSCK REPAIR TABLE tmpTbl;

Rufen Sie die Partitionspfade ab, indem Sie die Hive-Metadaten wie folgt abfragen:

SHOW PARTITONS tmpTbl;

Löschen Sie diese Partitionen aus der trgtTbl und verschieben Sie die Verzeichnisse von tmpTbl nach trgtTbl.

1
Joha

Als jatin Wrote können Sie Paritionen aus Hive und aus path löschen und dann Daten anhängen Da ich zu viel Zeit damit verschwendete, fügte ich das folgende Beispiel für andere Spark-Benutzer hinzu. Ich habe Scala mit Funken benutzt 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
spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.toDF().write.mode("overwrite").format("parquet").partitionBy("date", "name").save("s3://path/to/somewhere")

Dies funktioniert für AWS Glue ETL-Jobs (Glue 1.0 - Spark 2.4 - Python 2)).

1
Zach

Wenn Sie DataFrame verwenden, möchten Sie möglicherweise die Hive-Tabelle über Daten verwenden. In diesem Fall benötigen Sie nur eine Methode zum Aufruf

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

Dadurch werden die in DataFrame enthaltenen Partitionen überschrieben.

Es ist nicht erforderlich, das Format (orc) anzugeben, da Spark das Hive-Tabellenformat verwendet.

Es funktioniert gut in Spark-Version 1.6

1
L. Viktor

Sie könnten etwas tun, um den Job wiedereintrittsfähig zu machen (idempotent): 

# 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

Ich würde vorschlagen, dass Sie das Aufräumen durchführen und dann neue Partitionen mit dem Modus Append schreiben:

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)

Dadurch werden nur neue Partitionen gelöscht. Führen Sie nach dem Schreiben der Daten diesen Befehl aus, wenn Sie den Metastore aktualisieren müssen:

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

Hinweis: deletePath setzt voraus, dass der hfds-Befehl auf Ihrem System verfügbar ist.

0
gorros

Für> = Spark 2.3.0:

spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.write.insertInto("partitioned_table", overwrite=True)
0
Allan Feliph

Das Hinzufügen des Parameters 'overwrite = True' in der Anweisung insertInto löst Folgendes:

hiveContext.setConf("Hive.exec.dynamic.partition", "true")
hiveContext.setConf("Hive.exec.dynamic.partition.mode", "nonstrict")

df.write.mode("overwrite").insertInto("database_name.partioned_table", overwrite=True)

Standardmäßig overwrite=False. Durch Ändern in True können wir bestimmte Partitionen überschreiben, die in df und in der partitionierten_Tabelle enthalten sind. Dies hilft uns zu vermeiden, dass der gesamte Inhalt der partitionierten Tabelle mit df überschrieben wird.

0
SuCena

Getestet auf Spark 2.3.1 mit Scala. Die meisten der obigen Antworten werden in eine Hive-Tabelle geschrieben. Ich wollte jedoch direkt auf disk schreiben, was hat ein external Hive table über diesem Ordner.

Zuerst die erforderliche Konfiguration

val sparkSession: SparkSession = SparkSession
      .builder
      .enableHiveSupport()
      .config("spark.sql.sources.partitionOverwriteMode", "dynamic") // Required for overwriting ONLY the required partitioned folders, and not the entire root folder
      .appName("spark_write_to_dynamic_partition_folders")

Verwendung hier:

DataFrame
.write
.format("<required file format>")
.partitionBy("<partitioned column name>")
.mode(SaveMode.Overwrite) // This is required.
.save(s"<path_to_root_folder>")
0
Skandy