Mongodb MongoSpark仅在海量数据集上出现重复密钥错误
使用MongoSpark,在两个大小不同的数据集上运行相同的代码,导致其中一个抛出Mongodb MongoSpark仅在海量数据集上出现重复密钥错误,mongodb,apache-spark,Mongodb,Apache Spark,使用MongoSpark,在两个大小不同的数据集上运行相同的代码,导致其中一个抛出E11000重复键错误 在我们继续之前,以下是代码: object ScrapeHubCompanyImporter { def importData(path: String, companyMongoUrl: String): Unit = { val spark = SparkSession.builder() .master("local[*]") .config("sp
E11000重复键错误
在我们继续之前,以下是代码:
object ScrapeHubCompanyImporter {
def importData(path: String, companyMongoUrl: String): Unit = {
val spark = SparkSession.builder()
.master("local[*]")
.config("spark.mongodb.input.uri", companyMongoUrl)
.config("spark.mongodb.output.uri", companyMongoUrl)
.config("spark.mongodb.input.partitionerOptions.partitionKey", "profileUrl")
.getOrCreate()
import spark.implicits._
val websiteToDomainTransformer = udf((website: String) => {
val tldExtract = SplitHost.fromURL(website)
if (tldExtract.domain == "") {
null
} else {
tldExtract.domain + "." + tldExtract.tld
}
})
val jsonDF =
spark
.read
.json(path)
.filter { row =>
row.getAs[String]("canonical_url") != null
}
.dropDuplicates(Seq("canonical_url"))
.select(
toHttpsUdf($"canonical_url").as("profileUrl"),
$"city",
$"country",
$"founded",
$"hq".as("headquartes"),
$"industry",
$"company_id".as("companyId"),
$"name",
$"postal",
$"size",
$"specialties",
$"state",
$"street_1",
$"street_2",
$"type",
$"website"
)
.filter { row => row.getAs[String]("website") != null }
.withColumn("domain", websiteToDomainTransformer($"website"))
.filter(row => row.getAs[String]("domain") != null)
.as[ScrapeHubCompanyDataRep]
val jsonColsSet = jsonDF.columns.toSet
val mongoData = MongoSpark
.load[LinkedinCompanyRep](spark)
.withColumn("companyUrl", toHttpsUdf($"companyUrl"))
.as[CompanyRep]
val mongoColsSet = mongoData.columns.toSet
val union = jsonDF.joinWith(
mongoData,
jsonDF("companyUrl") === mongoData("companyUrl"),
joinType = "left")
.map { t =>
val scrapeHub = t._1
val liCompanyRep = if (t._2 != null ) {
t._2
} else {
CompanyRep(domain = scrapeHub.domain)
}
CompanyRep(
_id = pickValue(liCompanyRep._id, None),
city = pickValue(scrapeHub.city, liCompanyRep.city),
country = pickValue(scrapeHub.country, liCompanyRep.country),
postal = pickValue(scrapeHub.postal, liCompanyRep.postal),
domain = scrapeHub.domain,
founded = pickValue(scrapeHub.founded, liCompanyRep.founded),
headquartes = pickValue(scrapeHub.headquartes, liCompanyRep.headquartes),
headquarters = liCompanyRep.headquarters,
industry = pickValue(scrapeHub.industry, liCompanyRep.industry),
linkedinId = pickValue(scrapeHub.companyId, liCompanyRep.companyId),
companyUrl = Option(scrapeHub.companyUrl),
name = pickValue(scrapeHub.name, liCompanyRep.name),
size = pickValue(scrapeHub.size, liCompanyRep.size),
specialties = pickValue(scrapeHub.specialties, liCompanyRep.specialties),
street_1 = pickValue(scrapeHub.street_1, liCompanyRep.street_1),
street_2 = pickValue(scrapeHub.street_2, liCompanyRep.street_2),
state = pickValue(scrapeHub.state, liCompanyRep.state),
`type` = pickValue(scrapeHub.`type`, liCompanyRep.`type`),
website = pickValue(scrapeHub.website, liCompanyRep.website),
updatedDate = None,
scraped = Some(true)
)
}
val idToMongoId = udf { st: String =>
if (st != null) {
ObjectId(st)
} else {
null
}
}
val saveReady =
union
.map { rep =>
rep.copy(
updatedDate = Some(new Timestamp(System.currentTimeMillis)),
scraped = Some(true),
headquarters = generateCompanyHeadquarters(rep)
)
}
.dropDuplicates(Seq("companyUrl"))
MongoSpark.save(
saveReady.withColumn("_id", idToMongoId($"_id")),
WriteConfig(Map(
"uri" -> companyMongoUrl
)))
}
def generateCompanyHeadquarters(companyRep: CompanyRep): Option[CompanyHeadquarters] = {
val hq = CompanyHeadquarters(
country = companyRep.country,
geographicArea = companyRep.state,
city = companyRep.city,
postalCode = companyRep.postal,
line1 = companyRep.street_1,
line2 = companyRep.street_2
)
CompanyHeadquarters
.unapply(hq)
.get
.productIterator.toSeq.exists {
case a: Option[_] => a.isDefined
case _ => false
} match {
case true => Some(hq)
case false => None
}
}
def pickValue(left: Option[String], right: Option[String]): Option[String] = {
def _noneIfNull(opt: Option[String]): Option[String] = {
if (opt != null) {
opt
} else {
None
}
}
val lOpt = _noneIfNull(left)
val rOpt = _noneIfNull(right)
lOpt match {
case Some(l) => Option(l)
case None => rOpt match {
case Some(r) => Option(r)
case None => None
}
}
}
}
此问题与companyUrl
有关,它是集合中唯一的键之一,另一个是\u id
键。问题是Spark会尝试在700gb数据集上保存大量的重复数据,但是如果我在本地运行一个非常小的数据集,我永远无法复制这个问题。我试图了解发生了什么,如何确保将companyUrl
上的所有现有公司分组,并确保数据集中的重复项确实被全局删除
编辑
以下是出现的一些情况:
companyUrl
字段附近
编辑3
在合并阶段,我把这个问题缩小了。查看已标记为具有重复的
公司URL
的记录,其中一些记录不在目标集合中,但不知何故,仍在将重复记录写入集合中。在其他情况下,新记录的\u id
字段与具有相同公司URL
Spark版本的旧记录不匹配?什么版本的火花连接器?@Ross Spark_2.11 2.0.0和Mongo-Spark-Connector_2.11 2.0.0什么类型的saveReady
idToMongoId
可以生成空值-为什么不创建一个ObjectId
?Null可以是有效的Bson值,因此请确保这不是重复的原因。最后,您已经通过“companyUrl”删除了重复项。为什么不也删除\u id
呢?@Ross我添加了一个编辑,重复键位于我试图重复的companyUrl
周围,我想知道这是否是因为Mongo使用forEachPartition
进行保存的缘故,它可以再次重复重复SaveReady
是一个DS[CompanyRep]
@Ross我添加了一个更深入的编辑,关于我认为正在发生的事情。什么版本的Spark?什么版本的火花连接器?@Ross Spark_2.11 2.0.0和Mongo-Spark-Connector_2.11 2.0.0什么类型的saveReady
idToMongoId
可以生成空值-为什么不创建一个ObjectId
?Null可以是有效的Bson值,因此请确保这不是重复的原因。最后,您已经通过“companyUrl”删除了重复项。为什么不也删除\u id
呢?@Ross我添加了一个编辑,重复键位于我试图重复的companyUrl
周围,我想知道这是否是因为Mongo使用forEachPartition
进行保存的缘故,它可以再次重复重复SaveReady
是一个DS[CompanyRep]
@Ross我添加了一个更深入的编辑,关于我认为正在发生的事情。