ElasticSearch系列18:Mapping 设计指南

2023-11-05

 点击上方“方才编程”,即可关注我!

本文导读

ElasticSearch 的 mapping 该如何设计,才能保证检索的高效?想要回答这个问题,就需要全面系统地掌握 mapping 各种参数的含义以及其适用的场景。(ps:本文基于ElasticSearch 7.7.1)

本文通过分类讲解各个参数的含义,结合使用示例,让你从此不再迷路。

最后,还有方才兄精心设计的 mapping 实例,让你全面掌握 mapping 设计。

本文导航

什么是 Mapping?

Mapping 类似于数据库中的表结构定义 schema,它有以下几个作用:

  • 1、定义索引中字段的名称;

  • 2、定义字段的数据类型,比如 text、keyword、date;

  • 3、倒排索引的相关配置,比如设置某个字段为不被索引;

下面我们就一起来学习下mapping配置各个参数的含义,以及如何结合实际场景进行合理的配置。

基础配置

1、properties

mappings、object字段和nested字段包含的子字段就叫做 properties,示例:

PUT my_index
{
  "mappings": {
    "properties": { 
      "manager": {
        "properties": { 
          "age":  { "type": "integer" },
          "name": { "type": "text"  }
        }
      },
      "employees": {
        "type": "nested",
        "properties": { 
          "age":  { "type": "integer" },
          "name": { "type": "text"  }
        }
      }
    }
  }
}

分词相关的配置

1、fields

对同一个字段建立不同的索引方式,即multi-field。示例:

PUT my_index
{
  "mappings": {
    "properties": {
      "name": { 
       # 针对 name 字段,使用 standard 分词器建立索引
        "type": "text",
        "fields": {
       # 针对 name.sub_name 字段,使用 english 分词器建立索引
          "sub_name": { 
            "type":     "text",
            "analyzer": "english"
          }
        }
      }
    }
  }
}

2、analyzer

设置text类型字段index时的分词器。如上例中的【"analyzer": "english"】,就表示对 name.sub_name 字段,使用 english 分词器建立索引。

关于analyzer,方才兄在这里补充一个知识点,ElasticSearch如何确定 index 的 analyzer:

1)读取字段的“analyzer”配置

PUT my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "whitespace"
      }
    }
  }
}
# 指定字段 title 建立倒排索引时的 analyzer 为 whitespace

2)上述步骤没有,再读取index的setting:analysis.analyzer.default

PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default": {
          "type": "english"
        }
      }
    }
  }
}

3)都没有,使用默认的 standard 标准分词器

3、search_analyzer

设置 search 时使用的分词器。ElasticSearch 如何确定 search 时的 analyzer:

1)search API 指定 analyzer

GET my_index/_search
{
  "query": {
    "match": {
      "message": {
        "query": "Quick foxes",
        "analyzer": "stop"
      }
    }
  }
}

2)读取 index 的 mapping 字段配置 search_analyzer

PUT my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "whitespace",
        "search_analyzer": "simple"
      }
    }
  }
}

3)读取 index 的 setting 的 analysis.analyzer.default_search

PUT my_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default": {
          "type": "simple"
        },
        "default_search": {
          "type": "whitespace"
        }
      }
    }
  }
}

4)field 的 analyzer

5)都没有,使用默认的 standard analyzer

4、normalizer

针对 keyword 字段的normalizer属性,与analyzer类似,不同之处在于它保证 analyzer 生成单个 token。

与 analyzer相比,缺少了 tokenizer【关于分词的原理与流程,推荐阅读:ElasticSearch实战系列02:中文+拼音混合检索,并高亮显示】,示例:

PUT index
{
  "settings": {
    "analysis": {
      "normalizer": {
        "my_normalizer": {
          "type": "custom",
          "char_filter": [],
          "filter": ["lowercase", "asciifolding"]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "foo": {
        "type": "keyword",
        "normalizer": "my_normalizer"
      }
    }
  }
}
# 存入文本【BÀR a】,该配置得到的倒排索引是【bar a】
PUT index/_doc/1
{
  "foo": "BÀR a"
}
# 可以检索到结果
GET index/_search
{
  "query": {
    "term": {
      "foo": "BAR"
    }
  }
}

与相关性算分相关的配置

1、boost

增强字段的权重,示例【关于es的相关度,推荐阅读:ES系列13:彻底掌握相关度:从TF-IDF、BM25到对相关度的控制】:

PUT my_index
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "boost": 2 
      },
      "content": {
        "type": "text"
      }
    }
  }
}

与数据处理相关的配置

1、dynamic

控制是否可以动态添加新字段。可选参数:

  • 1)true 动态添加新的字段--缺省;

  • 2)false 忽略新的字段,【不会被索引】不会添加字段映射,但是会存在于_source中;

  • 3)strict 如果遇到新字段抛出异常【推荐配置参数】。

PUT /my_index
{
  "mappings": {
    "my_type": {
      "dynamic": "strict",
      "properties": {
        "title": {
          "type": "string"
        },
        "stash": {
          "type": "object",
          "dynamic": "strict"
        }
      }
    }
  }
}

2、format

为 date 类型的字段指定格式,示例:

ES的date类型允许我们规定格式,可以使用的格式有3种:
yyyy-MM-dd HH:mm:ss
yyyy-MM-dd
epoch_millis(毫秒值)

# 规定格式如下:|| 表示或者

PUT my_index
{
  "mappings": {
    "_doc": {
      "properties": {
        "date": {
          "type":   "date",
          "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        }
      }
    }
  }
}
注意:一旦我们规定了格式,如果新增数据不符合这个格式,ES将会报错mapper_parsing_exception。

可参考阅读:ElasticSearch系列03:ES的数据类型

与性能相关配置

1、enabled

设置成 false,仅做存储,不⽀持搜索和聚合分析 (数据保存在 _source 中)。

该enabled设置仅可应用于顶级映射定义和object字段,示例:

PUT my_index
{
  "mappings": {
    "properties": {
      "user_id": {
        "type":  "keyword"
      },
      "last_updated": {
        "type": "date"
      },
      "session_data": { 
        "type": "object",
        "enabled": false
      }
    }
  }
}
# 任何任意数据都可以传递到该session_data字段,因为它将被完全忽略。
PUT my_index/_doc/session_1
{
  "user_id": "kimchy",
  "session_data": { 
    "arbitrary_object": {
      "some_array": [ "foo", "bar", { "baz": 2 } ]
    }
  },
  "last_updated": "2015-12-06T18:20:22"
}

PUT my_index/_doc/session_2
{
  "user_id": "jpountz",
  "session_data": "none", 
  "last_updated": "2015-12-06T18:22:13"
}

2、doc_values

Doc Values 默认对所有字段启用,除了 text。禁用 Doc Values,可以节省磁盘空间,但不能被用于聚合、排序以及脚本操作,仍然可以查询。示例:

PUT my_index
{
  "mappings": {
    "properties": {
      "status_code": {
        "type": "keyword"
      },
      "session_id": {
        "type": "keyword",
        "doc_values": false
      }
    }
  }
}
PUT /my_index/_doc/1
{
  "session_id":"关注我"
}
# 可以检索到
GET /my_index/_search
{
  "query": {
    "term": {
      "session_id": {
        "value": "关注我"
      }
    }
  }
}
# 直接报错 illegal_argument_exception
GET /my_index/_search
{
 "aggs": {
   "test_agg": {
     "terms": {
       "field": "session_id",
       "size": 10
     }
   }
 }
}

如果你确定永远也不会对某些字段进行聚合、排序或是使用脚本操作,就可以通过禁用特定字段的 Doc Values,这样可以节省磁盘空间。

3、index

设置为 false,不构建倒排索引,不能被查询,但还是⽀持 aggregation,排序,脚本,并出现在 _source 【与doc_values 的效果相反】。示例:

PUT my_index
{
  "mappings": {
    "properties": {
      "test_field": {
        "type": "keyword",
         "index": false
      }
    }
  }
}
PUT /my_index/_doc/1
{
  "test_field":"关注我"
}
# 直接报错
GET /my_index/_search
{
  "query": {
    "term": {
      "test_field": {
        "value": "关注我"
      }
    }
  }
}
# 可以聚合
GET /my_index/_search
{
 "aggs": {
   "test_agg": {
     "terms": {
       "field": "test_field",
       "size": 10
     }
   }
 }
}

因为倒排索引在index时会序列化到磁盘,所以该配置设置为false,也节约磁盘【ps:关于此配置是否节约内存,有待深入研究,欢迎有了解的小伙伴在留言区分享】。

4、fielddata

对 text 字段,该参数默认是禁止的,所以直接对 text 字段进行聚合、排序或在脚本中使用时,ElasticSearch 会报错。此时,你可以开启该配置,以完成聚合需求:

PUT my_index
{
  "mappings": {
    "properties": {
      "test_field": {
        "type": "text",
        "fielddata": true
      },
      "test_field2":{
        "type": "text"
      }
    }
  }
}
PUT /my_index/_doc/1
{
  "test_field":"点个在看",
  "test_field2":"可好"
}

# 可以聚合
GET /my_index/_search
{
  "size": 0, 
 "aggs": {
   "test_agg": {
     "terms": {
       "field": "test_field",
       "size": 10
     }
   }
 }
}
# 聚合报错
GET /my_index/_search
{
  "size": 0, 
 "aggs": {
   "test_agg": {
     "terms": {
       "field": "test_field2",
       "size": 10
     }
   }
 }
}

但是,一旦将 fielddata 加载到 JVM 的 Heap 中,在该 segment 的生命周期内会一直保留在堆中,所以使用 fielddata 会占用大量的堆内存,影响性能。而对于非 text 类型字段的聚合,大多数使用的都是 doc_value,根据ElasticSearch官网对其的描述:

Doc Values 是在索引时与倒排索引同时生成。也就是说 Doc Values 和 倒排索引 一样,基于 Segement 生成并且是不可变的。同时 Doc Values 和 倒排索引 一样序列化到磁盘,这样对性能和扩展性有很大帮助。Doc Values 通过序列化把数据结构持久化到磁盘,我们可以充分利用操作系统的内存,而不是 JVM 的 Heap 。 当 working set 远小于系统的可用内存,系统会自动将 Doc Values 驻留在内存中,使得其读写十分快速;不过,当其远大于可用内存时,系统会根据需要从磁盘读取 Doc Values,然后选择性放到分页缓存中。很显然,这样性能会比在内存中差很多,但是它的大小就不再局限于服务器的内存了。

综上,个人建议,如有对 text 类型字段进行聚合、排序等需求,建议通过 fields 配置多字段:新增 keyword类型,同时将 index 参数设置为false,示例如下:

PUT my_index
{
  "mappings": {
    "properties": {
      "my_field": { 
        "type": "text",
        "fields": {
         # 该字段只用于聚合、排序等,所以关闭了index
          "keyword": { 
            "type": "keyword",
            "index":false
          }
        }
      }
    }
  }
}
PUT /my_index/_doc/1
{
  "my_field":"点个在看,可好"
}

# 可以聚合
GET /my_index/_search
{
  "size": 0, 
 "aggs": {
   "test_agg": {
     "terms": {
       "field": "my_field.keyword",
       "size": 10
     }
   }
 }
}

5、store 与 _source 对比

对比分析:

从每一个 stored field 中获取值都需要一次磁盘 io,如果想获取多个 field 的值,就需要多次磁盘 io ,但是,如果从 _source 中获取多个 field 的值,则只需要一次磁盘 io ,因为 _source 只是一个字段,而且_source是被压缩过的。所以在大多数情况下,从 _source 中获取是快速而高效的。

哪些情形下需要显式的指定 store 属性呢?

如果你的文档长度很长,存储 _source 或者从 _source 中获取 field 的代价很大,你可以显式的将某些 field 的 store 属性设置为 yes。此时只查询这一个字段的值的,效率高。

总结

ps:之前有读者反馈说,有的知识点缺少总结,今天,它来了。

一图胜千言

结合示例,更明白

PUT /blogs_index/
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 1
    },
    "analysis": {
      "analyzer": {
        "pinyin_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "my_pinyin"
          ]
        }
      },
      "filter": {
        "my_pinyin": {
          "type": "pinyin",
          "keep_first_letter": true,
          "keep_separate_first_letter": true,
          "keep_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "lowercase": true
        }
      }
    }
  },
  "mappings": {
    # 禁止新增字段
    "dynamic": "strict",
    "properties": {
      "id": {
        "type": "integer"
      },
      "author": {
        "type": "text",
        # 对作者使用拼音分词
        "analyzer": "pinyin_analyzer",
        "fields": {
          # 建立多字段,用于聚合
          "keyword": {
            "type": "keyword",
            "index":false
          }
        }
      },
      # 博客的分类,支持 term 查询
      "blog_sort": {
        "type": "keyword",
        # 需要聚合,且数据量较大,但唯一值较少
        "eager_global_ordinals": true,
        # 提升该字段的权重
        "boost": 3
      },
      "title": {
        "type": "text",
        "analyzer": "ik_max_word",
        # 检索的分词没必要细粒度,提升效率
        "search_analyzer": "ik_smart",
        # 对 标题 不需要聚合、排序
        "doc_values": false,
        # 提升该字段的权重
        "boost": 5
      },
      "content": {
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart",
        # 博客内容为大字段,单独存储,用于查询返回
        "store": true,
        # 不需要聚合、排序
        "doc_values": false
      },
      "update_time":{
        "type": "date",
        # 规定格式,提高可读性
        "format": ["yyyy-MM-dd HH:mm:ss"],
        # 该字段仅用于显示,不用检索、聚合、排序【非object类型,不能使用 enabled 参数】
        "index":false,
        "doc_values": false
      },
      "create_time":{
        "type": "date",
        "format": ["yyyy-MM-dd HH:mm:ss"]
      }
    }
  }
}

完整版《Mapping参数详解》,公号后台回复【ES】即可获取。

待续


       

ElasticSearch系列文章合集

留言区


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ElasticSearch系列18:Mapping 设计指南 的相关文章

随机推荐

  • 利用Vulnhub复现漏洞 - Jenkins-CI 远程代码执行漏洞(CVE-2017-1000353)

    Jenkins CI 远程代码执行漏洞 CVE 2017 1000353 Vulnhub官方复现教程 漏洞原理 复现过程 启动环境 漏洞复现 生成序列化字符串 发送数据包 执行命令 检验 Vulnhub官方复现教程 https vulhub
  • 【数据库】PostgreSQL增加密码复杂度校验

    前言 最近修改问题单 被分配了一个增加密码复杂度校验的单子 PG库也不是很懂 查了资料 PG有自带的密码复杂度校验插件 只需要使用这个插件就可以了 然后根据这几天的折腾 总结一下 怎么添加密码复杂度校验插件 PostgreSQL可以使用pa
  • 云计算与大数据第11章 大数据隐私保护习题带答案

    第11章 大数据隐私保护习题 11 1 选择题 1 以下 D 通常不是导致数据隐私泄露问题的来源 A 数据被肆意收集 B 数据集成融合 C 大数据分析 D DDOS攻击 2 以下 C 不是数据隐私保护的主要目标 A 机密性 B 完整性 C
  • 第38步 深度学习图像识别:VGG19建模(Tensorflow)

    基于WIN10的64位系统演示 一 写在前面 1 预训练模型和迁移学习 预训练模型就像是一个精心制作的省力工具 它是在大量的数据上进行训练 然后将学习到的模型参数保存下来 然后 我们可以直接使用这些参数 而不需要从头开始训练模型 这样可以节
  • PyTorch基础练习-task7(用PyTorch完成手写数字识别)

    PyTorch基础练习 task7 task7 import torch import numpy as np from torch autograd import Variable import torch nn as nn import
  • Vue全局注册组件的几种方式

    Vue全局注册组件的几种方式 1 extend vue js 代码 var com Vue extend template h1 这是第一种方式 h1 Vue component MyComponent com 此时的组件名为 MyComp
  • MySQL数据备份和恢复

    MySQL数据备份和恢复 数据备份 mysqldump是MySQL数据库备份工具 可以备份MySQL数据库中的数据和结构 生成 sql文件 方便数据的迁移和恢复 使用mysqldump工具前一定要配置环境变量 打开开始菜单 搜索 环境变量
  • 谷歌gn编译文件的使用简介

    Gn是什么 它是Google用来维护chromium项目的编译工具 现在相关的开源项目都基于gn来进行编译管理 目前一些大型系统的都会使用gn 例如谷歌 鸿蒙 Gn就是一个构建脚本生成器 是之前gyp的升级版本 并且gn是基于c 编写 效率
  • Python论文绘图利器seaborn.lineplot

    Python论文绘图利器seaborn lineplot 提示 前言 Python论文绘图利器seaborn lineplot 提示 写完文章后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 Python论文绘图利器seabor
  • Java中的equals()方法

    equals 在哪里 首先我们知道Java中Object类是所有类的父类 它里面定义了equals 方法 public boolean equals Object obj return this obj 可以看到是使用 来进行比较的 那么
  • 20 个常用的 CSS 技巧

    Sandy 推荐 高级工程师 游戏开发 下面这几个CSS技巧你可能不知道 1 彩色照片变黑白 2 所有元素垂直居中 3 禁用鼠标 4 模糊文字 小编学完能量满满的 觉得对CSS又充满了爱 你也来看看 原文链接 http caibaojian
  • sql 关联了2张表的 update 语句(转)

    转自 SQL Update 使用一个表的数据更新另一张表 update 关联两个表 基本上 select 能支持的关联和子查询操作 都能在 update 语句中使用 在 where 条件中使用子查询 update a set a age 1
  • Spark WARN cluster.ClusterScheduler: Initial job has not accepted any resources;check your cluster

    当我在Spark集群模式执行以下命令时 root debian master home hadoop spark 0 8 0 incubating bin hadoop1 run example org apache spark examp
  • DBA成长随笔---Oracle 11g,性能优化之等待事件

    目录 等待的定位方式 等待事件分类 观察等待事件的视图 常见等待事件 等待事件主要可以分为两类 即空闲 IDLE 等待事件和非空闲 NON IDLE 等待事件 空闲等待事件 是指Oracle正等待某种工作 比如用sqlplus登录之后 但没
  • 远程桌面连接出现了内部错误怎么解决?

    远程桌面连接是一种非常方便的工具 可以让用户从远程访问其他计算机的桌面界面 但是 有时候在连接远程桌面时会出现内部错误 导致无法连接或者连接后无法正常使用 在本文中 我们将会讨论远程桌面连接出现内部错误的原因和解决方法 1 确认网络连接 在
  • 2023牛客暑期多校第三场部分题解

    索引 A B D E G H I J A 直接输出两个数的差即可 再判一下无解的情况 B 其实思路还挺顺的 首先拿的牌肯定是一段增一段减一段增一段减 的序列 并且 gt n gt n gt n 的开头和 n
  • React中使用antd DatePicker限制日期选择

    场景 React中使用antd DatePicker限制日期选择 有下面一些场景 1 今天之前的日期不可选择 不包括今天 disabledDate current gt let current current format YYYY MM
  • 多标签学习之白话版

    简单的机器学习 就是把人类的学习方式教给机器 斯 cdot 沃索迪 1 任务的提出 单标签学习 假设你不知道河豚长什么样子 给你 1000 张照片 并标注哪些有河豚 再给你 100 张新的照片 你能判断哪些照片里面有河豚吗 本例中 从 10
  • charles介绍及代理设置

    一 介绍 你别说 这个真挺好看 有的叫花瓶 有的叫青花瓷 二 说明 安装charles后 如果不是正版 需要破解 破解码去百度查一个即可 否则试用期过后 使用半小时后就自动关闭了 需要重新打开呢 破解码输入路径 help register
  • ElasticSearch系列18:Mapping 设计指南

    点击上方 方才编程 即可关注我 本文导读 ElasticSearch 的 mapping 该如何设计 才能保证检索的高效 想要回答这个问题 就需要全面系统地掌握 mapping 各种参数的含义以及其适用的场景 ps 本文基于ElasticS