重复数据在数据分析和搜索中会造成错误。在我们的实际使用中,我们应该避免重复导入的数据。重复数据有各种原因会造成。比如我们重复导入同样的数据。当我们写入文档时使用自动生成的 ID,那么同样的文档被导入两次,这样会造成同样的两个一样的文档会保存于 Elasticsearch 中尽管它们的 ID 会有不同。在我之前的文章 “Beats:如何避免重复的导入数据”,我详细描述了如果使用 Beats 导入数据时,避免重复数据。
避免在 Elasticsearch 索引中重复始终是一件好事。 但是,通过消除重复项,你可以获得其他好处:节省磁盘空间,提高搜索准确性,提高硬件资源管理效率。 也许最重要的是,你减少了搜索的获取时间。令人惊讶的是,有关该主题的文档很少,因此我们提供了本教程,为你提供识别和管理索引中重复项的适当技术。
?
这里有四个简单的文档,其中一个是另一个的重复数据。 我们建立一个叫做 employeeid 的索引。
POST employeeid/_bulk
{ "index" : { "_id" : "1" } }
{ "name" : "John", "organisation": "Apple", "employeeID": "23141A"}
{ "index" : { "_id" : "2" } }
{ "name" : "Sam", "organisation": "Tesla", "employeeID": "TE9829"}
{ "index" : { "_id" : "3" } }
{ "name" : "Sarah", "organisation": "Microsoft", "employeeID": "M54667"}
{ "index" : { "_id" : "4" } }
{ "name" : "John", "organisation": "Apple", "employeeID": "23141A"}
从上面的命令中,我们可以看得出来 ID 为 1 和 4 的两个文档完全是一样的,尽管它们的 ID? 是不同的。
?
在考虑如何在 Elasticsearch 中执行重复检查之前,让我们花点时间考虑一下不同类型的索引方案。
一种情况是在索引编制之前我们可以访问源文档。 在这种情况下,检查数据并查找一个或多个包含唯一值的字段相对容易。 也就是说,该字段的每个不同值仅出现在一个文档中。 在这种情况下,我们可以将该特定字段设置为 Elasticsearch 索引的文档 ID。 由于任何重复的源文档也将具有相同的文档 ID,因此 Elasticsearch 将确保这些重复文档不会成为索引的一部分。
你可以参考我之前的文章 “Beats:如何避免重复的导入数据”。
?
另一种情况是一个或多个文档具有相同的标识符但内容不同。 当用户编辑文档并想使用相同的文档 ID 重新索引该文档时,通常会发生这种情况。 问题在于,当用户尝试重新索引时,Elasticsearch 不允许这样做,因为它的文档 ID 必须是唯一的。
解决方法是使用 Upsert API。 Upsert 检查特定文档的存在,如果存在,Upsert 将使用 Upsert 的内容更新该文档。 如果文档不存在,Upsert 将创建具有相同内容的文档。 无论哪种方式,用户都将在相同的文档 ID 下获得内容更新。
在第三种情况下,在创建索引之前无法访问数据集。 在这些情况下,我们将需要搜索索引并检查重复项。 这就是我们在以下各节中演示的内容。
在写入数据时,我们可以使用 Upsert 来进行。如果该文档尚不存在,则将 Upsert 元素的内容作为新文档插入。 如果文档存在,则执行更新。比如:
POST test/_update/1
{
"script": {
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params": {
"count": 4
}
},
"upsert": {
"counter": 1
}
}
在上面,如果 ID 为 1 的文档已经存在没那么将执行脚本,并把 count 的值加上 4。否则创建一个新的文档,并把 count 字段的值设置为 1。又比如:
POST sessions/_update/dh3sgudg8gsrgl
{
"scripted_upsert": true,
"script": {
"id": "my_web_session_summariser",
"params": {
"pageViewEvent": {
"url": "foo.com/bar",
"response": 404,
"time": "2014-01-01 12:32"
}
}
},
"upsert": {}
}
在上面,我们设置 scrpted_upsert 为 true。无论 ID 为?dh3sgudg8gsrgl 已经存在与否,id 为 my_web_session_summariser 的脚本将被执行,并把相应的参数传入。
我们也可以直接使用 _update 对文档直接更新,比如:
POST test/_update/1
{
"doc": {
"name": "new_name"
},
"doc_as_upsert": true
}
在上面,如果 ID 为 1 的文档已经存在,那么它的字段 name 值将被更新,否则创建一个新的 ID 为 1 的文档,并设置它的字段 name 值为 new_name。
在使用 Upsert 命令时,我们需要注意的是:我们必须提供一个 ID,另外它有两个操作:检查是否存在,并更新或者写入。Upsert 的速度会比正常的自动生成 ID 的导入速度慢。
当我们使用 Logstash 进行导入时,我们也可以指定 Upsert。
?
在上面的每个示例文档中,我们看到三个字段:name,organisation 及 employeeID,并且如果我们假设 name 字段是唯一的,则可以将该字段指定为检查重复项的标识符。 如果多个文档的 name 字段具有相同的值,则该文档确实是重复的。
遵循此基本原理,我们可以执行简单的术语聚合,以获取 name 字段每个值的文档计数。 但是,这种简单的聚合只会返回该字段每个值下的文档计数。 这种方法在检查重复项时没有用,因为我们要检查文档中该字段的一个或多个值的重复项。 为此,我们还需要应用 top_hits 聚合 - 一个子聚合器,其中每个存储桶中均会聚合最匹配的文档。
这是我们建议针对上面给出的示例文档索引的查询:
GET employeeid/_search
{
"size": 0,
"aggs": {
"duplicateCount": {
"terms": {
"field": "name.keyword",
"min_doc_count": 2
},
"aggs": {
"duplicateDocuments": {
"top_hits": {}
}
}
}
}
}
在这里,我们定义参数? min_doc_count。通过将此参数设置为 2,只有 doc_count 为 2 或更大的聚合桶将出现在聚合中(如以下结果所示)。
上面的命令运行的结果是:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"duplicateCount" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "John",
"doc_count" : 2,
"duplicateDocuments" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "John",
"organisation" : "Apple",
"employeeID" : "23141A"
}
},
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"name" : "John",
"organisation" : "Apple",
"employeeID" : "23141A"
}
}
]
}
}
}
]
}
}
}
请务必注意,我们必须将 min_doc_count 的值设置为2。 否则,其他结果将出现在聚合中,并且我们将找不到可能存在的任何重复项。
?
我们上面所做的是一个非常基本的示例,该示例根据单个字段中的值来标识重复文档。这不是很有趣。还是有用的。在大多数情况下,检查重复项需要检查多个字段。我们不能可靠地假设员工文档之间存在重复项,而这些重复项仅在名称字段中包含多次出现的 “Bill” 值。在许多实际情况下,有必要检查许多不同字段之间的重复项。考虑到上面的示例数据集,我们需要检查所有字段中的重复项。
我们可以从上一节中扩展我们的方法,并执行多字段术语聚合和 top-hits 聚合。我们可以对索引文档中的所有三个字段进行术语聚合。我们将再次指定 min_doc_count 参数,以仅获取 doc_count 大于或等于 2 的存储桶。我们还应用 top_hits 聚合以获取正确的结果。为了容纳多个字段,我们使用脚本来帮助我们追加字段值以在聚合中显示:
GET employeeid/_search
{
"size": 0,
"aggs": {
"duplicateCount": {
"terms": {
"script": "doc['name.keyword'].value + doc['employeeID.keyword'].value + doc['organisation.keyword'].value",
"min_doc_count": 2
},
"aggs": {
"duplicateDocuments": {
"top_hits": {}
}
}
}
}
}
如下所示,运行此查询的结果将显示一个重复计数聚合。聚合 duplicateDocuments 包含在其中找到重复值的文档。 我们可以对这些文档进行交叉检查和验证。
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"duplicateCount" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "John23141AApple",
"doc_count" : 2,
"duplicateDocuments" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "John",
"organisation" : "Apple",
"employeeID" : "23141A"
}
},
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"name" : "John",
"organisation" : "Apple",
"employeeID" : "23141A"
}
}
]
}
}
}
]
}
}
}
从上面,我们可以看出来有一个重复的文档。
假如我们把导入的文档修改为:
POST employeeid/_bulk
{ "index" : { "_id" : "1" } }
{ "name" : "John", "organisation": "Apple", "employeeID": "23141A"}
{ "index" : { "_id" : "2" } }
{ "name" : "Sam", "organisation": "Tesla", "employeeID": "TE9829"}
{ "index" : { "_id" : "3" } }
{ "name" : "Sarah", "organisation": "Microsoft", "employeeID": "M54667"}
{ "index" : { "_id" : "4" } }
{ "name" : "John", "organisation": "Apple", "employeeID": "23141A"}
{ "index" : { "_id" : "5" } }
{ "name" : "Sarah", "organisation": "Microsoft", "employeeID": "M54667"}
在上面,ID 为 1 和 4 为重复文档。3 和 5 的文档为重复文档。重新运行上面的查询,我们可以看到:
"aggregations" : {
"duplicateCount" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "John23141AApple",
"doc_count" : 2,
"duplicateDocuments" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "John",
"organisation" : "Apple",
"employeeID" : "23141A"
}
},
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "4",
"_score" : 1.0,
"_source" : {
"name" : "John",
"organisation" : "Apple",
"employeeID" : "23141A"
}
}
]
}
}
},
{
"key" : "SarahM54667Microsoft",
"doc_count" : 2,
"duplicateDocuments" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"name" : "Sarah",
"organisation" : "Microsoft",
"employeeID" : "M54667"
}
},
{
"_index" : "employeeid",
"_type" : "_doc",
"_id" : "5",
"_score" : 1.0,
"_source" : {
"name" : "Sarah",
"organisation" : "Microsoft",
"employeeID" : "M54667"
}
}
]
}
}
}
]
}
}
如上所示,重复的文档都被显示出来了。
背景 该问题来自某客户,据描述,他们在部署 MySQL 主从复制时,有时候仅在主库...
struts json 类型异常返回到js弹框问题解决办法 当struts 框架配置了异常时 例如...
本文转载自微信公众号「程序员历小冰」,转载本文请联系程序员历小冰公众号。 疫...
php实现微信支付 微信支付文档地址: https://pay.weixin.qq.com/wiki/doc/api/i...
六、XML展望 任何一项新技术的产生都是有其需求背景的,XML的诞生是在HTML遇到不...
微软官方博客于 2 月初再次发布提示,将会在 3 月 9 日停止对经典版 Edge 浏览器...
下面是ajax代码和Controller层代码,期初以为是后台程序写错了。 $("#sourcefile...
前言 我们在使用ajax异步的提交多选框得到需要操作的对象的id,这时我们可以把每...
文章目录 关系数据库 关系数据库简介 关系数据结构及形式化定义 关系 关系模式 ...
在 2021 年,人们喜欢 Linux 的理由比以往任何时候都多。在这个系列中,我将分享...