Elasticsearch:以 “Painless” 方式保护你的映射

news/2025/3/16 0:48:09/

Elasticsearch 是一个很棒的工具,可以从各种来源收集日志和指标。 它为我们提供了许多默认处理,以便提供最佳用户体验。 但是,在某些情况下,默认处理可能不是最佳的(尤其是在生产环境中); 因此,今天我们将深入探讨避免 “映射污染” 的方法。

什么是“映射”,为什么会有污染?

与任何数据存储解决方案一样,必须有一个模式(schema)等效设置来说明如何处理数据字段(例如存储和处理/分析); 此设置在 Elasticsearch 下称为 “映射(mapping)”。

与大多数不同,如果未指定,Elasticsearch 会为模式设置提供默认处理。 举个例子,当我们创建一个文档并引入一个全新的数据索引时,Elasticsearch 会尝试为我们猜测正确的字段映射。 是的~这是一个猜测! 因此,在大多数情况下,我们会毫无问题地摄取该文档,并自动完成。 干杯~但是等等……

  • 猜测的映射可能未优化(例如,任何整数字段都将映射到占用 64 位内存的数据类型 “long”,但是如果你的整数字段范围仅从 0~5 ......可能是 “byte” 或 “short” 就足够了,只需要 8 到 16 位内存)
  • 有些字段对我们来说毫无意义,因此我们应该在摄取之前排除它们以免引入映射污染

如何避免映射污染 —— Painless 脚本处理器方法

我们可以使用脚本处理器创建一个摄取管道,以在最终摄取发生之前删除目标字段。

用例:删除名称长度超过 15 个字符的字段。

也许我们的数据与随机生成的 UUID(超过 15 个字符长)下的一些元数据一起出现,并且不知何故我们永远不需要这些元数据。 因此,为避免映射污染,我们需要在一开始就排除此类字段。 以下是检查文档中可能字段长度的示例:

# pipeline to script and loop around all fields in a 
# context
POST _ingest/pipeline/_simulate
{"pipeline": {"processors": [{"script": {"source": """boolean flag = false;java.util.Set keys = ctx.keySet();for (String key : keys) {if (key.length()>15) {flag = true;}}ctx["has_long_fields"] = flag;"""}}]},"docs": [{"_source": {"very_very_long_field": "balabalabalabalabala"}},{"_source": {"age": 12}}]
}

上面的命令的响应为:

{"docs": [{"doc": {"_index": "_index","_id": "_id","_version": "-3","_source": {"very_very_long_field": "balabalabalabalabala","has_long_fields": true},"_ingest": {"timestamp": "2023-03-01T06:40:53.22560943Z"}}},{"doc": {"_index": "_index","_id": "_id","_version": "-3","_source": {"has_long_fields": false,"age": 12},"_ingest": {"timestamp": "2023-03-01T06:40:53.225907596Z"}}}]
}

我们可以清楚地看到 2 个测试文档的结果:第一个包含一个字段 very_very_long_field,结果为 true,而第二个只包含一个 age 字段,结果为 false。

这里的技巧是 ctx.keySet() 方法。 此方法返回一个 Set 接口,其中包含文档下可用的所有 “field-names”。 获得 Set 后,我​​们可以开始迭代它并应用我们的匹配逻辑。

一个棘手的事情是......这个集合还包含元数据字段,如 _index 和 _id,因此当我们应用一些字段匹配逻辑时,也要注意这些字段。

下一个示例将说明如何从我们的文档上下文中删除相应的字段:

# remove long fields... 
POST _ingest/pipeline/_simulate
{"pipeline": {"processors": [{"script": {"source": """boolean flag = false;java.util.Set keys = ctx.keySet();java.util.List fields = new java.util.ArrayList();for (String key : keys) {if (!key.startsWith("_") && key.length() > 10) {fields.add(key); }}// look through and delete those long field(s)if (fields.size() > 0) {for (String field: fields) {ctx.remove(field);}flag = true;}ctx["has_removed_long_fields"] = flag;"""          }}]},"docs":[{"_source": {"very_very_long_field": "balabalabalabalabala","another_long_field": "wakkakakakaka","age": 13,"name": "Felis"}},{"_source": {"desc": "dkjfdkjfkdjfkdfjk","address": "wakkakakakaka","age": 13,"name": "Felis"}}]
}

上面命令的返回值为:

{"docs": [{"doc": {"_index": "_index","_id": "_id","_version": "-3","_source": {"name": "Felis","age": 13,"has_removed_long_fields": true},"_ingest": {"timestamp": "2023-03-01T06:45:46.164088718Z"}}},{"doc": {"_index": "_index","_id": "_id","_version": "-3","_source": {"name": "Felis","address": "wakkakakakaka","age": 13,"desc": "dkjfdkjfkdjfkdfjk","has_removed_long_fields": false},"_ingest": {"timestamp": "2023-03-01T06:45:46.164113593Z"}}}]
}

魔法是 ctx.remove(“fieldname”)。 很简单不是吗? 另请注意,我们对字段匹配逻辑 !key.startsWith(“_”) && key.length()>10 应用了更精确的规则,因此不会考虑所有元字段(例如 _index)。

还引入了一个 ArrayList 来存储目标字段名称。 你可能会问为什么我们不在循环期间直接从文档上下文中删除该字段? 原因是如果我们尝试这样做,则会爆发一个异常,描述对文档上下文的并发修改。 因此,我们需要延迟删除过程,并且此 ArrayList 会跟踪这些字段名称。

最后,还有另一种情况,我们的文档可能涉及多个级别/层次结构。 以下示例说明如何确定字段是 “leaf” 字段还是 “branch” 字段:

# remove the inner field
POST _ingest/pipeline/_simulate
{"pipeline": {"processors": [{"script": {"source": """java.util.Set keys = ctx.keySet();java.util.ArrayList fields = new java.util.ArrayList();for (String key : keys) {// access the value; check the typeif (key.startsWith("_")) {continue;}Object value = ctx[key];if (value != null) {// it is a MAP (equivalent to the "object" structure of a json field)if (value instanceof java.util.Map) {// inner fields loopjava.util.Map innerObj = (java.util.Map) value;for (String innerKey: innerObj.keySet()) {if (innerKey.length() > 10) {//Debug.explain("a long field >> "+innerKey);fields.add(key+"."+innerKey);}}} else {if (key.length() > 10) {fields.add(key);}}}}if (fields.size()>0) {for (String field:fields) {// is it an inner field?int idx = field.indexOf(".");if (idx != -1) {ctx[field.substring(0, idx)].remove(field.substring(idx+1));} else {ctx.remove(field);  }}}"""}}]},"docs": [{"_source": {"age": 13,"very_very_very_long_field": "to be removed","outer": {"name": "Felix","very_very_long_field": "dkjfdkjfkdjfkdfjk"}}}]
}

上面的响应为:

{"docs": [{"doc": {"_index": "_index","_id": "_id","_version": "-3","_source": {"outer": {"name": "Felix"},"age": 13},"_ingest": {"timestamp": "2023-03-01T06:54:51.610568095Z"}}}]
}

一个很长的代码……为了检查该字段是 “leaf” —— 普通字段还是 “branch” —— 另一层字段(例如对象); 我们需要检查字段值的类型 java.util.Map 的值实例。

 instanceof 方法有助于验证提供的值是否与特定的 Java 类类型匹配。

接下来,我们需要再次迭代 Set of inner-object fields 以应用我们的匹配规则。 使用 ArrayList 的相同技术将应用于跟踪目标字段名称,以便在稍后阶段删除。

最后,通过 ctx.remove(“fieldname”) 删除字段。 但是这次,我们还需要检查这个字段是 leaf 还是 branch 字段。 对于 branch 字段,它将以 outer-object-name.inner-field-name 的格式出现。 我们需要先提取 outer-object-name 并在删除 inner-field-name 之前访问其上下文 -> ctx[field.substring(0, idx)].remove(field.substring (idx+1))

举个例子:outer.very_very_long_field

  • idx(“.”分隔符所在的 index) = 5
  • field.substring(0, idx) = "outer"
  • field.substring(idx+1) = "very_very_long_field"
  • 因此…… ctx[field.substring(0, idx)].remove(field.substring(idx+1)) = ctx[“outer”].remove(“very_very_long_field”)

干得好 ~ 这是避免贴图污染的 “Painless” 脚本方法。 

如何避免映射污染 —— 索引的动态设置方法

有时,我们可能不介意引入一个映射污染; 然而~我们不希望那些无意义的字段是可搜索或可聚合的。 只是我们让那些无意义的字段充当虚拟对象,你可以看到它们(在 _source 字段下可用)但永远无法对它们应用任何操作。 如果是这样的话……我们可以更改索引的动态设置。

PUT test_dynamic 
{"mappings": {"dynamic": "false","properties": {"name": {"type": "text"},"address": {"dynamic": "true","properties": {"street": {"type": "keyword"}}},"work": {"dynamic": "strict","properties": {"department": {"type": "keyword"},"post": {"type": "keyword"}}}}}  
}

我们通过如下的方法来摄入一些数据:

# all good, everything matches the mapping
POST test_dynamic/_doc
{"name": "peter parker","address": {"street": "20 Ingram Street","state": "NYC"},"work": {"department": "daily bugle"}
}# added a non-searchable "age" and a searchable field "address.post_code"
POST test_dynamic/_doc
{"age": 45,"name": "Edward Elijah","address": {"post_code": "344013"}
}

很显然,在上面,age 不在之前的映射中定义。由于我们在映射中设置 dynamic 为 false,age 这个字段将不能被用于搜索:

dynamicdoc indexed?fields searchablefields indexed?mapping updated?
trueYesYesYesYes
runtimeYesYesNo        No
falseYesNoNoNo
strictNo
GET test_dynamic/_search
{"query": {"match": {"age": 45}}
}

上面搜索的结果为空。有关动态映射的文章,请详细阅读文章 “Elasticsearch:Dynamic mapping”。

我们接着进行如下的搜索:

GET test_dynamic/_search
{"query": {"match": {"address.post_code": "344013"}}
}

上面的搜索显示的是一个文档:

    "hits": [{"_index": "test_dynamic","_id": "Z2X8m4YBRPmzDW_iGJOb","_score": 0.2876821,"_source": {"age": 45,"name": "Edward Elijah","address": {"post_code": "344013"}}}]

我们执行如下的命令:

# exception as work.salary is forbidden
POST test_dynamic/_doc
{"age": 45,"name": "Prince Tomas","address": {"post_code": "344013"},"work": {"salary": 10000000}
}

上面命令返回的结果为:

{"error": {"root_cause": [{"type": "strict_dynamic_mapping_exception","reason": "mapping set to strict, dynamic introduction of [salary] within [work] is not allowed"}],"type": "strict_dynamic_mapping_exception","reason": "mapping set to strict, dynamic introduction of [salary] within [work] is not allowed"},"status": 400
}

这是因为 work 字段的属性为 strict。我们不可以为这个字段添加任何新的属性。

如何避免映射污染 —— 通过预处理方法删除字段

这里讨论的最后一种方法是在传递给 Elasticsearch 之前删除无意义的字段......以及如何? 嗯……自己写程序,对文档进行预处理~:)))))

这确实是一种有效的方法,但可能并不适合所有人; 因为需要一些编程知识。 有时,如果我们在传递给 Elasticsearch 之前对文档进行预处理,它可能会更加灵活,因为我们可以完全控制文档的修改(由于编程语言的功能)。


http://www.ppmy.cn/news/29052.html

相关文章

MySQL基础(一)SQL分类、导入、SELECT语句,运算符

目录 MySQL安装以及相关工具 SQL分类 导入数据 最基本的SELECT语句 SELECT FROM 列的别名 去除重复行 着重号 查询常数 描述表结构 过滤数据(重要) 运算符 算数运算符 比较运算符 符号运算符 非符号运算符 逻辑运算符 位运算符 MySQL安…

SSM SpringBoot vue 在线教学质量评价系统

SSM SpringBoot vue 在线教学质量评价系统 SSM 在线教学质量评价系统 功能介绍 首页 图片轮播展示 登录 学生注册 教师注册 督导注册 教师展示 教师详情 学生评价 课程信息 课程详情 提交选修该课 学生选课 学生留言 个人中心 后台管理 管理员或学生或教师或督导登录 个人中…

集成RocketChat至现有的.Net项目中,为ChatGPT铺路

文章目录前言项目搭建后端前端代理账号鉴权方式介绍登录校验模块前端鉴权方式后端鉴权方式登录委托使用登录委托处理聊天消息前端鉴权方式后端校验方式项目地址前言 今天我们来聊一聊一个Paas的方案,如何集成到一个既有的项目中。 以其中一个需求为例子&#xff1a…

主机状态(查看资源占用情况、查看网络占用情况)

1. 查看资源占用情况 【1】可以通过top命令查看cpu、内存的使用情况,类似windows的任务管理器 默认5s刷新一次 语法:top 可 Ctrl c 退出 2.磁盘信息监控 【1】使用df命令,查看磁盘信息占用情况 语法:df [ -h ] 以更加人性化…

山地车和公路车怎么选

公路车: 只能适应平坦的路面,骑行阻力小,速度快比较适合新手 山地车: 能适应所有路面,更注重操控性和舒适性 怎么选? 1、先决定用途 旅游:旅行车、山地车、 通勤:公路车 2、预…

图注意网络GAT理解及Pytorch代码实现【PyGAT代码详细注释】

文章目录GAT代码实现【PyGAT】GraphAttentionLayer【一个图注意力层实现】用上面实现的单层网络测试加入Multi-head机制的GAT对数据集Cora的处理csr_matrix()处理稀疏矩阵encode_onehot()对label编号build graph邻接矩阵构造GAT的推广GAT 题:Graph Attention Netwo…

Ep_操作系统面试题-什么是协程

协程 是一种 比线程更加轻量级的存 在,一个线程可以拥有多个协程。是一个特殊的 函数 ,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。协程 不是被操作系统内核所管理 , 而完全是由程序所控制(也就是在…

[MatLab]矩阵运算和程序结构

一、矩阵 1.定义 矩阵以[ ]包含,以空格表示数据分隔,以;表示换行。 A [1 2 3 4 5 6] B 1:2:9 %1-9中的数,中间是步长(不能缺省) C repmat(B,3,2) %将B横向重复2次,纵向重复2次 D ones(2,4) …