不必Reindex,利用runtime_fields优雅地解决字段类型错误问题

简介: 不必Reindex,利用runtime_fields优雅地解决字段类型错误问题

前言

作为程序员入职一家新公司,当你看到前任程序员写的代码的时候,你是不是经常有这样的感觉:我屮艸芔茻!这代码真他喵的烂!

bf99b138bc1543a1ad9da7e7a56a930b.png

问题

对于程序员来说,代码水平良莠不齐比较常见的事情,之所以代码质量不高,一方面原因是自身不太关注于代码的整体管理,另一方面原因是经验不足。实际上在处理公司业务的时候,这种前人挖坑,后人填坑的事情时有发生。


对,我们今天暂时不讨论代码规范的问题,而是说一说,再处理搜索业务的时候,如果你已经接手了一些无法描述的代码,咱们怎么补救。


首先对于任何问题而言,预防问题发生永远胜于问题发生之后再对其进行补救,这是一定的。不过既然问题已经发生,我们就只能以最小代价或者针对自身情况选择处理方式,比如,我们可以选择是先治标还是先治本,最主要区别在于见效速度。下面我们分别来说一下这两种解决方案的区别和适用场景:


常见场景

场景一

字段类型错误:这种情况一般发生在项目初期对业务的预估错误或者技术负责人对ES本身不够了解。


很多人都知道,为了优化性能,可以适当缩减字段属性。比如确定不需要聚合或者排序的字段,可对其关闭正排索引,不需要对其进行检索的字段可关闭倒排索引,不需要评分的字段可以关闭评分功能以此可节约字段存储空间,提高效率。


另外一种情况是在创建索引的时候,对ES的字段本身不够了解,比如之前我们提到的“yyyy-MM-dd HH:mm:ss”这种格式并非默认支持的时间类型,如果一开始技术员并不知道,而将数据直接写入,有可能的结果就是,时间总段存储成为了“text”类型,而给后期埋下隐患。


场景二

上述问题是字段类型错误,还有一种情况是针对于数据本身。比如:当我们某个索引报错了不同来源或渠道的数据,每个渠道可能采集数据负责的程序员并不相同,最常见的情况比如,我们采集用户行为日志,需要涉及“APP”、“Web”、“PC”、“小程序”等来源,可能负责开发的程序员分别来自不同的部门,如果在最开始没有最好约定,可能就会出现字段或者单位不统一或者一些列其他问题。总之就是协调发生问题而导致数据不统一。这也给后期做数据分析或者统计造成很大影响。


案例

我们来看下面一个例子,创建以下映射并写入一条数据:

PUT twitter
{
  "mappings": {
    "properties": {
      "uid": {
        "enabled": false
      }
    }
  }
}
POST twitter/_doc
{
  "uid": "1"
}

以上代码为我们在项目初期,负责人对将来业务的预估是不会出现通过uid 进行检索的情况,因此错误的把此字段的enabled 设置为了false, 此时数据将不能通过此字段进行检索。注意是不能通过此字段检索而不是不能将此字段检索出来,只是没有创建索引而非删除了元数据。具体体现为:下面代码第一行执行有数据,第二行无数据。


以下代码执行有结果

# 有数据
GET twitter/_search


以下代码执行无结果

# 无数据
GET twitter/_search
{
  "query": {
    "term": {
      "uid": {
        "value": 1
      }
    }
  }
}


痛点及现状

后期经过业务的发展和迭代,逐渐发现了此字段无法检索的问题,而很多公司的选择都是Reindex,的确,重建索引可以解决很多问题,但是这样未来过于“劳师动众!”,因为基本上ES的应用场景都是基于海量数据的索引,重建索引可能耗费大量时间,无异于杀鸡取卵,得不偿失。


有没有更好的解决方案 ?

那必须有啊!关注我就对了。

同类问题还包括如之前提到的,“yyyy-MM-dd HH:mm:ss”而导致的类型不匹配问题,在处理此一类问题时,皆可采用runtime_fields来解决。


案例:

我们沿用之前“yyyy-MM-dd HH:mm:ss 是时间类型?别再错下去了!”提到的不是时间类型导致的错误的案例,如果没看过之前的文章,戳:传送门

假如生产环境我们有如下索引,存储了一些地震的经纬度以及发生时间和描述等信息:


需求

写一个查询满足以下要求


  • 1:按星期分桶统计地震数据
  • 2:输出星期一至星期日中平均地震等级 没有数据的不显示
  • 3:返回平均地震等级最大的一个 是星期几
  • 4:进阶问题 每个星期的平均地震等级
  • 5:进阶问题 平均地震等级最大的是哪个星期


POST task1/_bulk?refresh=true
{"index":{}}
{"time":"2011-06-16 12:12:21","magnitude" : 1.4, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":"2011-06-16 12:12:21","magnitude" : 1.3, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":"2011-06-17 12:12:21","magnitude" : 1.5, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":"2011-04-18 12:12:21","magnitude" : 1.6, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":"2011-06-19 12:12:21","magnitude" : 1.9, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":"2011-06-20 12:12:21","magnitude" : 2.0, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":1308544245123,"magnitude" : 2.1, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":1308717045123,"magnitude" : 2.8, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":"2011-06-20 12:12:21","magnitude" : 2.9, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}
{"index":{}}
{"time":"2011-06-20 12:12:21","magnitude" : 3.3, "lon" : -116.0902, "lat" : 33.2253, "depth" : 9.98, "area" : " 10km NNE of Ocotillo Wells"}


这是我为Elastic认证考试出的一道模拟题,在此我们简化题目,只看前两个问题


但是,因为不可描述的原因,time字段有的数据存储成为了yyyy-MM-dd HH:mm:ss,有的存储成为了时间戳,最终time字段的类型并没有像预想的那样为date类型他们可能来自于同一个公司的不同部门,所以导致了这种问题。


面临的问题

此时,面对上述需求,存在两个问题

  • time字段为text而非date类型,而导致我们无法使用日期时间类型的所有函数。
  • 数据存储的格式不统一,有时间戳,有"yyyy-MM-dd HH:mm:ss",无法统一计算。


解决方案:runtime_fields

运行时字段是在执行查询时动态对字段类型和索引重新定义的字段。runtime_fields具备以下特点:

  • 在不重新索引数据的情况下向现有文档添加字段
  • 在不了解数据结构的情况下开始处理数据
  • 在查询时覆盖从索引字段返回的值
  • 为特定用途定义字段而不修改底层架构


由于运行时字段未编入索引,因此添加运行时字段不会增加索引大小。直接在索引映射中定义运行时字段,可以节省存储成本并提高预处理速度。


如果将运行时字段设为索引字段,则无需修改任何引用运行时字段的查询。而且可以引用字段是运行时字段的一些索引,以及字段是索引字段的其他索引。可以灵活地选择要索引哪些字段以及保留哪些字段作为运行时字段。


就其核心而言,运行时字段最重要的好处是能够在提取文档后将字段添加到文档中。此功能简化了映射决策,所以不需要预先决定如何解析数据,并且可以使用运行时字段随时修改映射。使用运行时字段索引更小且更快的预处理信息,这结合使用更少的资源并降低运营成本。


上述问题,解决方案代码如下:

GET task1/_search
{
  "size": 0,
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": "emit(doc['time'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL,Locale.ROOT))"
    },
    "time": {
      "type": "date",
      "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
    }
  },
  "aggs": {
    //1:按照周统计地震信息,也就是每周有几天地震了
    "week_agg": {
      "date_histogram": {
        "field": "time",
        "calendar_interval": "week"
      },
      "aggs": {
        "week_avg_magnitude": {
          "avg": {
            "field": "magnitude"
          }
        }
      }
    },
    //一周中的每一天的震级
    "day_of_week_magnitude":{
      "terms": {
        "field": "day_of_week"
      },
      "aggs": {
        //2: 一周中每一天的平均地震等级
        "day_of_week_avg_magnitude": {
          "avg": {
            "field": "magnitude"
          }
        }
      }
    }
  }
}


此段代码中,其核心代码如下

"runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": "emit(doc['time'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL,Locale.ROOT))"
    },
    "time": {
      "type": "date",
      "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
    }
  }


其在runtime_mappings 中定义了两个“新字段”,即day_of_weektime,其中day_of_week利用运行时字段中执行脚本进行动态计算,从而得出每天分别是一周内的星期几。这种用法可用于各种其他复杂的运算。


time字段则是对原有字段进行重新映射,改变其原有字段的类型和其他属性,如format,使其原本不支持的时间类型变为支持。

相关文章
|
数据库
Unknown column ‘字段名‘ in ‘field list‘解决方法
Unknown column ‘字段名’ in 'field list’英语翻译过来就是未知列的字段名在字段表中,也就是说数据库表中的字段名在实体类中找不到。 解决方案如下: 查看实体类的属性名和数据库表中的名字是否一致(注意数据库表字段中是否有空格或者下划线)
4037 0
|
6月前
|
SQL 分布式计算 DataWorks
MaxCompute操作报错合集之表增加字段,提示创建表失败:DDL execute error, OdpsException: ODPS-0130071:[1,60] Semantic analysis exception - column,该怎么办
MaxCompute是阿里云提供的大规模离线数据处理服务,用于大数据分析、挖掘和报表生成等场景。在使用MaxCompute进行数据处理时,可能会遇到各种操作报错。以下是一些常见的MaxCompute操作报错及其可能的原因与解决措施的合集。
141 2
|
存储 数据库
ODOO中的Related字段及Computed字段原理
难理解的参数: Store = True 原因:ODOO 默认情况下不存储计算字段。(因为,可以减少数据库的开销)。但你知道的, 当您希望能够执行某个函数,需要从数据库中调用此值时,这会导致问题。要修复此问题,odoo 在计算字段上提供 store=true 标志。
3593 0
Elastic:查询时字段runtime fields不显示,如何处理?
很多时候runtime field是结合一起使用的,这时没有任何问题,因为这类需要一般将聚合结果显示出来就行了。但是当我们需要将runtime fields也查询出来时发现查询结果中是不会显示它们的,下面我们通过具体的案例来解决这个问题
119 0
|
Web App开发 存储 数据可视化
【Elastic Engineering】Elasticsearch:使用 Runtime fields 对索引字段进行覆盖处理以修复错误 - 7.11 发布
Elasticsearch:使用 Runtime fields 对索引字段进行覆盖处理以修复错误 - 7.11 发布
178 0
【Elastic Engineering】Elasticsearch:使用 Runtime fields 对索引字段进行覆盖处理以修复错误 - 7.11 发布
Elasticsearch 多字段查询 best_fields、most_fields、cross_fields,傻傻分不清楚?
题记 Multi-match query 的目的多字段匹配,但 Multi-match query 中的 best_fields, most_fields, cross_fields 分不清楚,都什么含义? 下面我们一一举例解读。
653 0
Elasticsearch 多字段查询 best_fields、most_fields、cross_fields,傻傻分不清楚?
|
NoSQL 关系型数据库 MySQL
Create Table Like Mapping: 基于类型推断的建表方法
![create-table-like-mapping.png](https://ata2-img.cn-hangzhou.oss-pub.aliyun-inc.com/1e60369b1873a7a1c3b358a3194cd3db.png) [Data Lake Analytics](https://www.aliyun.com/product/datalakeanalytics) 作为云上
1154 0
|
关系型数据库 PostgreSQL
PostgreSQL 快速返回表上某列的唯一值(枚举值) - pg_stats.most_common_vals
标签 PostgreSQL , 统计信息 , 唯一值 , 枚举值 背景 PostgreSQL的列统计信息中包含一项高频词,同时包含一项唯一值个数。 pg_stats.n_distinct pg_stats.most_common_vals 同时PostgreSQL允许用户自定义统计信息柱状图BUCKET的个数。
1657 0
|
SQL 关系型数据库 数据库
PostgreSQL 同名 index operator search_path优先级引入的一个问题 - 为啥突然不走索引了? - intarray示例
标签 PostgreSQL , intarray , ops , operator , OPERATOR , 操作符路径 , search_path , 优先级 背景 操作符是数据库最常用的要素之一,一个SQL语句中总是会出现它的影子。
1264 0