我正在使用Spark SQL,需要找出两个大CSV之间的差异。
差异应提供:-
插入行或新记录//仅比较ID
更改的行(不包括插入的行)-比较所有列值
Spark 2.4.4 + Java
我正在使用Databricks读取/写入CSV
async function getAllmd5()
{
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
var bucket = storage.bucket('example');
var [files] = await bucket.getFiles();
for (var i = 0; i < files.length; i++)
{
console.log(Buffer.from(files[i].metadata.md5Hash, 'base64').toString("ascii"))
}
}
这对于列数最多为50左右的CSV非常适用。
Dataset<Row> insertedDf = newDf_temp.join(oldDf_temp,oldDf_temp.col(key)
.equalTo(newDf_temp.col(key)),"left_anti");
Long insertedCount = insertedDf.count();
logger.info("Inserted File Count == "+insertedCount);
Dataset<Row> deletedDf = oldDf_temp.join(newDf_temp,oldDf_temp.col(key)
.equalTo(newDf_temp.col(key)),"left_anti")
.select(oldDf_temp.col(key));
Long deletedCount = deletedDf.count();
logger.info("deleted File Count == "+deletedCount);
Dataset<Row> changedDf = newDf_temp.exceptAll(oldDf_temp); // This gives rows (New +changed Records)
Dataset<Row> changedDfTemp = changedDf.join(insertedDf, changedDf.col(key)
.equalTo(insertedDf.col(key)),"left_anti"); // This gives only changed record
Long changedCount = changedDfTemp.count();
logger.info("Changed File Count == "+changedCount);
但是,如果我的CSV包含300多个列,则它会失败并显示异常
批处理分辨率达到了最大迭代次数(100)–火花错误
The Above code fails for one row in CSV with 300+columns, so I am sure this is not file Size problem.
sparkConf.set(“ spark.sql.optimizer.maxIterations”,“ 500”);
但是我的问题是为什么我必须设置这个?
我在做错什么吗? 或对于具有大列的CSV来说,这种行为是预期的。
我可以通过任何方式对其进行优化以处理大列CSV的
。答案 0 :(得分:2)
您遇到的问题与spark如何接受您告诉它的指令并将其转换为实际要执行的事情有关。它首先需要通过运行Analyzer来了解您的指令,然后尝试通过运行其优化器来改进它们。该设置似乎适用于两者。
特别是您的代码在分析器中的某个步骤中被炸毁。分析器负责弄清您指的是什么东西,实际上是指什么。例如,将函数名称映射到实现,或在重命名和不同的转换之间映射列名称。它会多次执行此操作,每次通过解决其他问题,然后再次检查是否可以解决移动问题。
我认为针对您的情况正在发生的事情是,每个遍可能解析一列,但100次遍还不足以解析所有列。通过增加它,您为它提供了足够的通行证,使其能够完全通过您的计划。这绝对是潜在性能问题的一个危险信号,但是如果您的代码正常运行,那么您可以仅增加该值而不必担心。
如果它不起作用,那么您可能需要尝试做一些事情以减少计划中使用的列数。也许将所有列组合成一个编码的字符串列作为键。在进行联接之前,您可能会受益于对数据进行检查点设置,从而可以缩短计划。
编辑:
此外,我将重构您的上述代码,以便您只需一个连接即可完成所有操作。这应该快很多,并且可能会解决您的其他问题。
每次连接都会导致随机播放(数据在计算节点之间发送),这会增加您的工作时间。您不必一次计算添加,删除和更改,而是可以一次完成所有操作。类似于以下代码。它在scala伪代码中,因为我比Java API更熟悉。
import org.apache.spark.sql.functions._
var oldDf = ..
var newDf = ..
val changeCols = newDf.columns.filter(_ != "id").map(col)
// Make the columns you want to compare into a single struct column for easier comparison
newDf = newDF.select($"id", struct(changeCols:_*) as "compare_new")
oldDf = oldDF.select($"id", struct(changeCols:_*) as "compare_old")
// Outer join on ID
val combined = oldDF.join(newDf, Seq("id"), "outer")
// Figure out status of each based upon presence of old/new
// IF old side is missing, must be an ADD
// IF new side is missing, must be a DELETE
// IF both sides present but different, it's a CHANGE
// ELSE it's NOCHANGE
val status = when($"compare_new".isNull, lit("add")).
when($"compare_old".isNull, lit("delete")).
when($"$compare_new" != $"compare_old", lit("change")).
otherwise(lit("nochange"))
val labeled = combined.select($"id", status)
在这一点上,我们将每个ID标记为ADD / DELETE / CHANGE / NOCHANGE,因此我们只能对groupBy / count进行分组。此agg几乎可以完全在地图端完成,因此比连接要快得多。
labeled.groupBy("status").count.show