写点什么

Elasticsearch Mapping 类型修改

  • 2024-08-09
    北京
  • 本文字数:3660 字

    阅读完需:约 12 分钟

背景


通常数据库进行分库分表后,目前比较常规的作法,是通过将数据异构到 Elasticsearch 来提供分页列表查询服务;在创建 Elasticsearch 索引时,基本都是会参考目前的业务需求、关系数据库中的类型以及对数据的相关规划来定义相关字段 mapping 的类型.在 Elasticsearch 的 mapping 中的列(或则叫属性),有几个比较重要的参数(更多参数参考官方文档)


列类型:type
指定了该列的数据类型,常用的有text, keyword, date, long, double,boolean以及 object和nested,不同的类型也有对应的不同查询方式,创建之后是不能修改的;
是否可索引:index
该index选项控制字段值是否被索引。它接受true or false,并且默认为true. 未索引的字段不可查询,当然也不能做为排序字段。
复制代码


但是在实际的开发过程中,又会有需求对现有的 mapping 的 type 进行修改(类似对 MySQL 数据表的字段进行 DDL 操作)的诉求。比如商品上的价格 price 字段,按原来的业务分析,只需要提供数据返回即可,在创建索引时类型定义了 keyword 了,并且 index 设置成了 false,这时我们需要根据价格的范围查询或则进行排序操作,就希望对 mapping 进行调整,将类型修改成数字类型,索引也需要加上;今天针对 Elasticsearch 的 Mapping 类型进行修改,讨论几个可行的方案方案 1:运用 reindex


遇到问题第一时间,我们应该是查询官方文档是否有相关的操作说明,在官方文档中,确实还能找到对已有 mapping 更新的相关 api put-mapping,通过这个文档,很快可以找到文档中对修改已有 mapping 的列的方式(参考官方文档),同时也提到的通过 reindex 的方式来修改已有类型的方式;


除了支持的mapping parameters外,您不能更改现有字段的映射或字段类型。更改现有字段可能会使已编制索引的数据无效。如果您需要更改字段的映射,请使用正确的映射创建一个新索引并将您的数据重新索引reindex到该索引中。
复制代码


如原来索引的 mapping 如下


PUT /users{"mappings" : {"properties": {"user_id": {"type": "long"}}}}


//加一了两条数据 POST /users/_doc?refresh=wait_for{"user_id" : 12345}


POST /users/_doc?refresh=wait_for{"user_id" : 12346}


这时想修改 user_id 的类型为 keyword,我们直接是修改不了的。


//尝试直接修改 type,行不通,会报错 PUT /users/_mapping{"properties": {"user_id": {"type": "keyword"}}}


//报错信息{"error": {"root_cause": [{"type": "illegal_argument_exception","reason": "mapper [user_id] of different type, current_type [long], merged_type [keyword]"}],"type": "illegal_argument_exception","reason": "mapper [user_id] of different type, current_type [long], merged_type [keyword]"},"status": 400}


按官方文档说的 reindex 重新索引可按以下步骤操作操作步骤第一步:创建新的索引 new_users 将 user_id 的类型定义成 keyword


PUT /new_users{"mappings" : {"properties": {"user_id": {"type": "keyword"}}}}


第二步:将原 user 索引标记为只读


控制我们的应用系统,数据停写不再向老索引中写数据,并且最好对老索引进行只读操作设置,保证在 reindex 的过程中,不要生产新数据,导致新老索数据不一致;


//设置索引为读写的 PUT /users/_settings{"settings": {"index.blocks.write": true}}


第三步:将原 user 索引中的数据迁移到 new_users 中


POST /_reindex{"source": {"index": "users"},"dest": {"index": "new_users"}}


reindex 还有很多的参数可以配置,包括从远程的一个集群迁移数据都是可以的,详细可参考:Reindex API


如果新的索引的 mapping 的定义与原索引的定义有差异的,会按新索引定义的 dynamic 规则进行数据的迁移,具体的,可以参考: dynamic


该 dynamic 设置控制是否可以动态添加新字段。它接受三种设置:值 说明 true 新检测到的字段被添加到映射中。(默认); 新增的数据类型的规则,可以参考:dynamic-mappingfalse 忽略新检测到的字段。这些字段不会被编入索引,因此将无法搜索,但仍会出现在_source 返回的命中字段中。这些字段不会添加到映射中,必须明确添加新字段。strict 如果检测到新字段,则会抛出异常并拒绝文档。必须将新字段显式添加到映射中。


同时将原 user 索引标记为可读写


//设置索引为可读写 PUT /users/_settings{"settings": {"index.blocks.write": false}}


第四步:切换到使用新的 mapping


可以将应用系统中的配置改成新索引
也可以通过索引的别名的方式为新索引增加原来老索引的别名来操作,为索引增加别名参考文档:Add index alias API,在增加别名前,需要删除原来的老索引;
复制代码


//为索引增加别名 基本格式 PUT /<index>/_alias/<alias>POST /<index>/_alias/<alias>


//为 new_users 索引增加别名 usersPUT /new_users/_alias/users


//没有删除老索引前,是增加不了别名的,需要先删除老别名{"error": {"root_cause": [{"type": "invalid_alias_name_exception","reason": "Invalid alias name [users], an index exists with the same name as the alias","index_uuid": "8Rbq_32BTHC4CoO_CqWdXA","index": "users"}],"type": "invalid_alias_name_exception","reason": "Invalid alias name [users], an index exists with the same name as the alias","index_uuid": "8Rbq_32BTHC4CoO_CqWdXA","index": "users"},"status": 400}


方案优劣分析【优点】操作简单,官方方案


该方案,不需要对原索引做操作,在线即可进行,并且操作步骤也简单;也是官方文档提供的方案。【缺点】数据量大迁移耗时长


当数据最大时,这个数据迁移会比较耗时结论


当数据量小时,并且希望 mapping 比较规整好看,该方案是比较推荐的。当数据量大时,可能该方案在数据迁移过程中会比较耗时,需要评估是否可行;方案 2:运用 multi-fields


为不同的目的以不同的方式索引同一个字段通常很有用。这就是multi-fields的目的。例如,一个string 字段可以映射为text用于全文搜索的字段,也可以映射keyword为用于排序或聚合的字段;在这个方案中,应用的是mapping参数fields来对同一个列,定义多种数据类型;详细[【官方文档】multi-fields] (https://www.elastic.co/guide/en/elasticsearch/reference/7.5/multi-fields.html)
复制代码


操作步骤第一步:为列增加 fields 属性


还是以上面的 users 这个索引为例,我们还是想将 user_id 的类型定义成 keyword;


PUT /users/_mapping{"properties":{"user_id":{"type":"long","fields":{"raw":{"type":"keyword"}}}}}


操作完成后,在 users 的 user_id 列下,就会多出一个 raw 的子属性;在我们正常写数据 user_id 时,会自动生成这两个索引,一个是 long 类型的 user_id,以及 keyword 类型的 user_id.raw(注意这里有个点,跟子对象访问方式一样);在 put mapping 时,type 参数必需给,并且需要跟原来的类型一致,fields 中新定义的子属性可以多个;【可选】第二步:历史数据更新


针对历史数据需要处理,可以借助_update_by_query 来更新数据,只需要将原来的索引再写一次,即可将新加的字段写入数据。


POST /users/_update_by_query


{"query":{"exists":{"field":"user_id"}},"script":{"source":"ctx._source.user_id=ctx._source.user_id ","lang":"painless"}}


// query 部分为需要更新数据过滤条件,可根据业务规则写// script 更数据的逻辑,这个基本可以不改


方案优劣分析【优点】不影响原索引,同一列可以定义多种类型


通过这方式不会影响原来的索引数据,可以不用修改现在的应用程序的读写方式,对应用程序一切按原来逻辑执行,对应用方无感知,非常优化。只需要有使用新类型的场景使用即可,可以说影响是最小的;同时只是做了一个定义,执行速度是非常快的,对 Elasticsearch 服务基本不会有太大影响;并且对于同一个列可以定义多个类型,比如商品名称,在多国多语言环境下可以根据不同语言定义多个列,对应使用不同的分词器;【缺点】老数据不会自动创建子索引,多出额外的存储


老数据不会自动创建索引,因为需要多出新的索引来,会增加额外的存储;结论


1、需要对多一列创建多个索引类型时,是一个非常推荐的方案;2、对于新索引,只有新业务使用,对老数据没有诉求的,也非常推荐该方案;方案 3:运用 copy_to


copy_to 是将多个字段的值,合并到一个字段中,便于搜索。但是也可以实现一个字段存在多个类型的需求。详细参考【官方文档】copy_to 操作步骤


还是用上面的 users 这个索引为例,为 user_id 创建一个 copy 列: user_id_raw 类型定义成 keyword


PUT /users/_mapping{"properties":{"user_id_raw":{"type":"keyword","copy_to":"user_id"}}}


这个方案与方案 2:multi-fields 基本是一样的,只是创建列的方式不同,优缺点都一样;参考资料


[1] 【官方文档】Mapping parameters
[2] 【官方文档】Mapping Field datatypes
[3] [【官方文档】multi-fields] (https://www.elastic.co/guide/en/elasticsearch/reference/7.5/multi-fields.html)
[4] Elasticsearch Rename Index
[5] elasticSearch7.x—mapping中的fields属性||copy_to配置(同一个字段两种类型)
[6] 《Elasticsearch:权威指南》Mapping -- Mapping parameters -- fields(multi-fields)
复制代码


发布于: 刚刚阅读数: 4
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
Elasticsearch Mapping类型修改_京东科技开发者_InfoQ写作社区