Перезаписывать конкретные разделы в методе записи данных в режиме искривления

Я хочу перезаписать конкретные разделы, а не все в искровом режиме. Я пытаюсь выполнить следующую команду:

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

где df - это dataframe, содержащий инкрементные данные, которые должны быть перезаписаны.

hdfs-base-path содержит основные данные.

Когда я пытаюсь выполнить приведенную выше команду, она удаляет все разделы и вставляет те, которые присутствуют в df по пути hdfs.

Какое мое требование состоит в том, чтобы перезаписать только те разделы, которые присутствуют в df по указанному пути hdfs. Может кто-нибудь, пожалуйста, помогите мне в этом?

Ответ 1

Это обычная проблема. Единственным решением с Spark до 2.0 является запись непосредственно в каталог разделов, например,

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

Если вы используете Spark до 2.0, вам необходимо остановить Spark от испускания файлов метаданных (потому что они будут разбивать автоматическое обнаружение разделов), используя:

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

Если вы используете Spark до 1.6.2, вам также потребуется удалить файл _SUCCESS в /root/path/to/data/partition_col=value или его присутствие приведет к обрыву автоматического обнаружения разделов. (Я настоятельно рекомендую использовать 1.6.2 или новее.)

Вы можете получить еще несколько подробностей о том, как управлять большими секционированными таблицами из моей беседы Spark Summit о Bulletproof Jobs.

Ответ 2

В заключение! Теперь это функция в Spark 2.3.0: https://issues.apache.org/jira/browse/SPARK-20236.

Чтобы использовать его, вам нужно установить динамический параметр spark.sql.sources.partitionOverwriteMode, набор данных должен быть разбит на части, а режим записи перезаписан. Пример:

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

Я рекомендую перед перезаписью сделать перераспределение на основе столбца вашего раздела, чтобы у вас не было 400 файлов в папке.

До появления Spark 2.3.0 лучшим решением было бы запускать операторы SQL для удаления этих разделов, а затем записывать их с помощью режима добавления.

Ответ 3

Использование Spark 1.6...

HiveContext может значительно упростить этот процесс. Ключ состоит в том, что вы должны сначала создать таблицу в Hive, используя оператор CREATE EXTERNAL TABLE с определенным разделением. Например:

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

Отсюда, скажем, у вас есть Dataframe с новыми записями в нем для определенного раздела (или нескольких разделов). Вы можете использовать оператор HiveContext SQL для выполнения INSERT OVERWRITE с использованием этого Dataframe, который перезапишет таблицу только для разделов, содержащихся в Dataframe:

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

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

Примечание: update_dataframe в этом примере имеет схему, которая соответствует схеме целевой таблицы test.

Одна простая ошибка с этим подходом состоит в том, чтобы пропустить шаг CREATE EXTERNAL TABLE в Hive и просто сделать таблицу с помощью методов записи API Dataframe. Для таблиц, основанных на Паркетах, таблица не будет определена надлежащим образом для поддержки функции Hive INSERT OVERWRITE... PARTITION.

Надеюсь, это поможет.

Ответ 4

Я попробовал следующий подход, чтобы перезаписать определенный раздел в таблице 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

Ответ 5

Если вы используете DataFrame, возможно, вы хотите использовать таблицу Hive над данными. В этом случае вам нужен только метод вызова

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

Он перезапишет разделы, которые содержит DataFrame.

Нет необходимости указывать формат (orc), потому что Spark будет использовать формат таблицы Hive.

Он отлично работает в версии Spark 1.6

Ответ 6

Вместо прямой записи в целевую таблицу, я бы предложил вам создать временную таблицу, подобную целевой таблице, и вставить туда свои данные.

CREATE TABLE tmpTbl LIKE trgtTbl LOCATION '<tmpLocation';

После создания таблицы вы должны записать свои данные в tmpLocation

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

Затем вы восстановите пути к разделам таблицы, выполнив:

MSCK REPAIR TABLE tmpTbl;

Получите пути к разделам, запросив метаданные Hive, например:

SHOW PARTITONS tmpTbl;

Удалите эти разделы из trgtTbl и переместите каталоги из tmpTbl в trgtTbl

Ответ 7

Как пишет Jatin, вы можете удалять разделы из куста и пути, а затем добавлять данные. Поскольку я тратил слишком много времени на это, я добавил следующий пример для других пользователей Spark. Я использовал Scala с искрой 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 !!!")
}

}

Ответ 8

spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.toDF().write.mode("overwrite").format("parquet").partitionBy("date", "name").save("s3://path/to/somewhere")

Это работает для меня в заданиях AWS Glue ETL (Glue 1.0 - Spark 2.4 - Python 2)

Ответ 9

Вы можете сделать что-то вроде этого, чтобы вернуть работу (идемпотент): (попробовал это на спарк 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_directory>,recurse=True)

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

Ответ 10

Я бы посоветовал вам выполнить очистку, а затем написать новые разделы в режиме 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)

Это удалит только новые разделы. После записи данных выполните эту команду, если вам нужно обновить metastore:

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

Примечание: deletePath предполагает, что команда hfds доступна в вашей системе.

Ответ 11

Протестировал это на Spark 2.3.1 с помощью Scala. Большинство ответов выше пишут в таблицу Hive. Тем не менее, я хотел записать непосредственно на диск, который имеет external hive table поверх этой папки.

Сначала требуемая конфигурация

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")

Использование здесь:

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

Ответ 12

Добавление параметра overwrite = True в оператор insertInto решает эту проблему:

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)

По умолчанию overwrite=False. Изменение его на True позволяет нам перезаписывать определенные разделы, содержащиеся в df и в partioned_table. Это помогает нам избежать перезаписи всего содержимого partioned_table с помощью df.

Ответ 13

Для> = Spark 2.3.0:

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

Ответ 14

На spark-2.2 я попробовал и успех

spark.conf.set("hive.exec.dynamic.partition.mode", "nonstrict")
df.write.mode("overwrite").insertInto("db.test")