Sparkのすべてではなく、特定のパーティションを上書きしたい。私は次のコマンドを試しています:
df.write.orc('maprfs:///hdfs-base-path','overwrite',partitionBy='col4')
ここで、dfは上書きされる増分データを持つデータフレームです。
hdfs-base-pathにはマスターデータが含まれます。
上記のコマンドを実行すると、すべてのパーティションが削除され、dfのhdfsパスに存在するパーティションが挿入されます。
私の要件は、指定されたhdfsパスのdfに存在するパーティションのみを上書きすることです。誰かがこれで私を助けてくれますか?
これはよくある問題です。 Spark 2.0までの唯一の解決策は、パーティションディレクトリに直接書き込むことです。たとえば、
df.write.mode(SaveMode.Overwrite).save("/root/path/to/data/partition_col=value")
2.0より前のSparkを使用している場合、次を使用してSparkがメタデータファイルを送信するのを停止する必要があります(自動パーティション検出が破損するため)。
sc.hadoopConfiguration.set("parquet.enable.summary-metadata", "false")
1.6.2より前のSparkを使用している場合、_SUCCESS
の/root/path/to/data/partition_col=value
ファイルも削除する必要があります。削除しないと、自動パーティション検出が中断されます。 (1.6.2以降を使用することを強くお勧めします。)
Bulletproof Jobs のSpark Summit talkから、大きなパーティションテーブルを管理する方法について、さらに詳細を入手できます。
最後に!これはSpark 2.3.0の機能になりました: https://issues.Apache.org/jira/browse/SPARK-20236
これを使用するには、spark.sql.sources.partitionOverwriteMode設定をdynamicに設定する必要があります。データセットはパーティション化する必要があり、書き込みモードoverwrite。例:
spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.write.mode("overwrite").insertInto("partitioned_table")
書き込む前にパーティション列に基づいてパーティションを再作成することをお勧めします。そうすれば、フォルダーごとに400個のファイルが作成されることはありません。
Spark 2.3.0の前の最良の解決策は、SQLステートメントを起動してそれらのパーティションを削除し、モード追加で書き込みます。
Spark 1.6を使用しています...
HiveContextは、このプロセスを大幅に簡素化できます。重要なのは、パーティションを定義したCREATE EXTERNAL TABLE
ステートメントを使用して、最初にHiveでテーブルを作成する必要があることです。例えば:
# Hive SQL
CREATE EXTERNAL TABLE test
(name STRING)
PARTITIONED BY
(age INT)
STORED AS PARQUET
LOCATION 'hdfs:///tmp/tables/test'
ここから、特定のパーティション(または複数のパーティション)の新しいレコードを含むDataframeがあるとします。 HiveContext SQLステートメントを使用して、このDataframeを使用してINSERT OVERWRITE
を実行できます。これにより、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
テーブルのスキーマと一致するスキーマがあります。
このアプローチで犯す間違いの1つは、HiveのCREATE EXTERNAL TABLE
ステップをスキップし、Dataframe APIの書き込みメソッドを使用してテーブルを作成するだけです。特にParquetベースのテーブルでは、テーブルはHiveのINSERT OVERWRITE... PARTITION
関数をサポートするために適切に定義されません。
お役に立てれば。
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
ターゲットテーブルに直接書き込むのではなく、ターゲットテーブルのような一時テーブルを作成し、そこにデータを挿入することをお勧めします。
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
にディレクトリを移動します
Jatin Wroteとして、Hiveとパスからパーティションを削除し、データを追加することができます。時間を浪費していたため、他のsparkユーザー用に次の例を追加しました。 Scalaとsparkを使用しました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 !!!")
}
}
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)
DataFrameを使用する場合は、データに対してHiveテーブルを使用することもできます。この場合、メソッドを呼び出すだけです
df.write.mode(SaveMode.Overwrite).partitionBy("partition_col").insertInto(table_name)
DataFrameに含まれるパーティションを上書きします。
SparkはHiveテーブル形式を使用するため、形式(orc)を指定する必要はありません。
Sparkバージョン1.6で正常に動作します
次のようなことをして、ジョブを再入可能(べき等)にすることができます:(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>)
クリーンアップを行い、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)
これにより、新しいパーティションのみが削除されます。データを書き込んだ後、メタストアを更新する必要がある場合は、次のコマンドを実行します。
sparkSession.sql(s"MSCK REPAIR TABLE $db.$table")
注:deletePath
は、hfds
コマンドがシステムで使用可能であることを前提としています。
> = Spark 2.3.0の場合:
spark.conf.set("spark.sql.sources.partitionOverwriteMode","dynamic")
data.write.insertInto("partitioned_table", overwrite=True)
InsertIntoステートメントに「overwrite = True」パラメーターを追加すると、これが解決します。
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
で上書きされることを回避できます。
これをSpark 2.3.1 Scalaでテストしました。上記の回答のほとんどは、Hiveテーブルへの書き込みです。ただし、このフォルダーの上部にexternal Hive table
があるdiskに直接書き込みたいと思いました。
最初に必要な構成
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>")