【官方文档翻译】映射 - 动态映射 - Elasticsearch

239 阅读13分钟

动态映射(Dynamic mapping)

Elasticsearch的一个最重要的特性就是它为你铺好路让你能尽可能快的开始探索你的数据。要索引一个文档,你不必先创建一个索引,再定义一个映射类型,再定义你的字段-你只需要“索引”一个文档,那么索引、类型和字段将自动显示:

PUT data/_doc/1 
{ "count": 5 }

创建了 data 索引, _doc 映射类型和一个名为 count 且数据类型是 long 的字段。

对新字段的自动检测和添加被称为动态映射。为适合你的目的动态映射的规则可以定制:

  • 动态字段映射
    管理着动态字段检测的规则。
  • 动态模板
    为动态添加的字段配置自定义的映射规则。

1. 动态字段映射

当Elasticsearch检测到一个文档里的一个新字段,它动态地将字段添加为默认的映射类型。 dynamic参数控制着这个行为。

你可以通过设置 dynamic 参数为 trueruntime来明确的指示 Elasticsearch 基于新进来的文档动态的创建字段。当动态字段映射被开启,Elasticsearch使用下面表格的规则来决定如何为每一个字段映射一个数据类型。

🏳️ 下面表格里的字段数据类型只是 Elasticsearch 能动态检测的字段数据类型,所有其它的数据类型你必须明确地映射。

Elasticsearch数据类型

JSON数据类型"daynamic":"true""dynamic":"runtime"
null不会添加字段不会添加字段
true or falsebooleanboolean
doublefloatdouble
longlonglong
objectobject不会添加字段
array依赖于数组的第一个非null值依赖于数组的第一个非null值
string(通过了日期检测)datedate
string(通过了数字检测)float or longdouble or long
string(不能通过 date 检测或者 numeric 检测)带有.keyword子字段的textkeyword

你可以同时在文档和 object 层级禁用掉动态映射。设置 dynamic参数为 false 会忽略掉新的字段,设为 strict 如果 Es 遇到了一个未知的字段那么会拒绝掉这个文档。

在已存在的字段上使用 更新映射API更新 dynamic 设置。

你可以为日期检测数字检测自定义动态字段映射规则。要定义你能应用到额外的动态字段上的自定义映射规则,使用 动态模板

1.1 日期检测

如果 date_detection 被开启了(默认),那么新的字符串字段会被检查看看它们的内容是否匹配任何通过 dynamic_date_formats 指定的日期模式。如果找到了一个匹配,那么一个新的 date 字段会按照相应的格式添加。
默认的 dynamic_date_formats 的值是:
["strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]
例如:

PUT my-index-000001/_doc/1
{
  "create_date": "2015/09/02"
}

GET my-index-000001/_mapping

这个 create_date 字段被添加为一个 date 字段,格式为: "yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"

1.2 禁用日期检测

可以通过设置 date_detectionfalse 禁用掉日期检测:

PUT my-index-000001
{
  "mappings": {
    "date_detection": false
  }
}

PUT my-index-000001/_doc/1 
{
  "create_date": "2015/09/02"
}

这个 create_date 字段会被添加为一个 text 字段。

1.3 自定义检测的日期格式

或者,可以自定义 dynamic_date_formats 支持你自己的 日期格式

PUT my-index-000001
{
  "mappings": {
    "dynamic_date_formats": ["MM/dd/yyyy"]
  }
}

PUT my-index-000001/_doc/1
{
  "create_date": "09/25/2015"
}

1.4 数字检测

虽然JSON支持原生的浮点型和整型数据类型,但是一些应用或者语言有时会将数字渲染为字符串。通常正确的解决方案是明确地映射这些字段,但是可以启用数字检测(默认关闭)自动地做这些:

PUT my-index-000001
{
  "mappings": {
    "numeric_detection": true
  }
}

PUT my-index-000001/_doc/1
{
  "my_float":   "1.0", 
  "my_integer": "1" 
}

这个 my_float 字段被添加为一个 float 字段。
这个 my_integer 字段被添加为一个 long 字段。

2. 动态模板

动态模板允许你更好的控制在默认动态字段映射规则之外 Elasticsearch 如何映射你的数据。通过设置 dynamic 参数为 trueruntime来开启动态映射。然后你可以使用动态模板来定义定制化的规则,这些规则可以应用到基于匹配条件的动态添加的字段:

  • match_mapping_type 操作 Elasticsearch 检测的数据类型
  • matchunmatch 使用一个模式去匹配字段名
  • path_matchpath_unmatch 在字段的全点路径上操作
  • 如果一个动态模板没有定义 match_mapping_type, matchpath_match,它将不会匹配任何字段。你仍然可以在一个批量请求dynamic_templates 部分通过名称来引用模板。

使用映射说明里的 {name}{dynamic_type} 模板变量作为占位符。

动态字段映射只有在一个字段包含具体值的时候才会添加。当一个字段包含 null 或者空数组的时候 ES 不会添加动态字段映射。如果在一个动态模板里使用了 null_value 选项,只有在一个包含了一个具体值的字段的第一个这样的文档被索引的时候才会应用。

通过一个像命名对象的数组指定动态模板:

 "dynamic_templates": [
    {
      "my_template_name": {  // @1
        ... match conditions ...  // @2️
        "mapping": { ... }  // @3
      }
    },
    ...
  ]

@1 模板名称,可以是任意字符串值。
@2 匹配条件,可以包含任何:match_mapping_type, math, match_pattern, unmatch, path_match, path_unmatch
@3 匹配字段应该使用的映射。

2.1 验证动态模板

如果提供的映射包含了一个无效的映射片段,将返回一个验证错误。验证发生在索引时,当动态模板应用的时候,并且,大多数情况下是动态模板被更新的时候。在以下情况下提供一个无效的映射片段会导致动态模板的更新或者验证失败:

  • 如果 match_mapping_type 没有被指定,但是在至少有一个预定义的映射类型方面是有效的,那么映射片段就被认为是有效的。然而,在索引时如果一个匹配模板的字段作为一个不同的类型被索引,将返回一个验证错误。例如:配置一个没有 math_mapping_type 的动态模板作为一个字符串类型被认为是有效的,但是如果一个匹配到动态模板的字段作为 long 型被索引,在索引时将返回一个验证错误。推荐为预期的JSON类型配置 match_mapping_type 或者在映射片段里配置期望的 type
  • 如果在映射片段里使用了 {name} 占位符,当更新动态模板时验证会被跳过。这是因为在那时字段名还是未知的。相反,在索引时模板被应用的时候验证会发生。

模板按顺序处理 - 第一个匹配的模板会胜出。当通过映射更新API放入新的动态模板时,所有已存在的模板都会被覆盖。这允许动态模板初始化加入后被重新排序或删除。

2.2 在动态模板里映射运行时字段

如果你想要 ES 动态地映射一个特定类型的新字段为运行时字段,在索引映射里设置 "dynamic":"runtime"。这些字段不会被索引,在查询时从 _source 里加载。
或者,你可以使用默认的动态映射规则然后创建动态模板去映射指定的字段为运行时字段。在索引映射里设置 "dynamic":"true" ,然后创建一个动态模板去映射特定类型的新字段为运行时字段。
假定你有一些任何一个字段都以 ip_开头的数据。基于动态映射规则,ES 将任何通过 numeric 检测的字符串映射为 floatlong。然而,你可以创建一个动态模板将新的字符串映射为 ip 类型的运行时字段。
下面的请求定义了一个名为 strings_as_ip 的动态模板。当 ES 检测到匹配 ip* 模式的新的 string 字段时,它映射那些字段为 ip 类型的运行时字段。因为 ip 字段不是动态映射的,所以你可以用 "dynamic":"true" 或者 "dynamic":"runtime"来使用这个模板。

PUT my-index-000001/
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_ip": {
          "match_mapping_type": "string",
          "match": "ip*",
          "runtime": {
            "type": "ip"
          }
        }
      }
    ]
  }
}

这个例子 如何使用动态模板映射 string 字段为索引字段或者运行时字段。

match_mapping_type

match_mapping_type是JSON解析器检测到的数据类型。因为JSON不会区分 longinteger 或者 doublefloat,任何解析的浮点数都会被认为是 double JSON数据类型,同时任何解析的 integer 数字被认为是 long

由于动态映射,ES 总是选择宽一点的数据类型。一个例外是 float,比double需要更少的存储空间并且对于大多数的应用来说精度也足够了。运行时字段不支持 float,这就是为什么 "dynamic":"runtime" 使用 double 的原因。 Es自动检测下面的数据类型:

JSON数据类型"daynamic":"true""dynamic":"runtime"
null不会添加字段不会添加字段
true or falsebooleanboolean
doublefloatdouble
longlonglong
objectobject不会添加字段
array依赖于数组的第一个非null值依赖于数组的第一个非null值
string(通过了日期检测)datedate
string(通过了数字检测)float or longdouble or long
string(不能通过 date 检测或者 numeric 检测)带有.keyword子字段的textkeyword

使用通配符(*)匹配所有的数据类型。

例如,如果我们想要映射所有的 integer 字段为 integer 而不是 long,并且所有的 string 字段为 textkeyword,我们可以使用下面的模板:

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "integers": {
          "match_mapping_type": "long",
          "mapping": {
            "type": "integer"
          }
        }
      },
      {
        "strings": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text",
            "fields": {
              "raw": {
                "type":  "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    ]
  }
}

PUT my-index-000001/_doc/1
{
  "my_integer": 5, // @1
  "my_string": "Some string"  // @2 
}

@1: 这个 my_integer 字段被映射为一个 integer
@2: 这个 my_string 字段被映射为一个 text,带有一个多字段keyword

matchunmatch

match 参数使用一个模式去匹配字段名,然而 unmatch 使用一个模式去排除匹配到 match 的字段。
match_pattern 参数调整 match 参数的行为以支持在字段名上做匹配的全部的Java正则表达式而不是简单的通配符。比如:

"match_pattern": "regex",
"match": "^profit_\d+$"

下面的例子匹配了所有名字以 long_ 开头的(除了哪些以 _text结尾的)string字段,并将它们映射为 long 字段:

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "longs_as_strings": {
          "match_mapping_type": "string",
          "match":   "long_*",
          "unmatch": "*_text",
          "mapping": {
            "type": "long"
          }
        }
      }
    ]
  }
}

PUT my-index-000001/_doc/1
{
  "long_num": "5", // @1
  "long_text": "foo" // @2
}

@1: 这个 long_num 字段被映射为 long
@2: 这个 long_text 字段使用默认的 string 映射。

path_matchpath_unmatch

path_matchpath_unmatch 参数和 matchunmatch 有相同的工作方式,但是是在字段的全点路径上操作,而不仅仅是最后的名称,例如 some_object.*.some_field
下面的例子拷贝 name 对象里的任何字段的值到顶层的 full_name 字段,除了 middle 字段:

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "full_name": {
          "path_match":   "name.*",
          "path_unmatch": "*.middle",
          "mapping": {
            "type":       "text",
            "copy_to":    "full_name"
          }
        }
      }
    ]
  }
}

PUT my-index-000001/_doc/1
{
  "name": {
    "first":  "John",
    "middle": "Winston",
    "last":   "Lennon"
  }
}

注意 path_matchpath_unmatch 参数匹配对象的路径除了叶子字段。举一个例子:索引下面的文档将导致一个错误因为 path_match 设置也匹配到了对象字段 name.title,但它不能被映射为 text:

PUT my-index-000001/_doc/2
{
  "name": {
    "first":  "Paul",
    "last":   "McCartney",
    "title": {
      "value": "Sir",
      "category": "order of chivalry"
    }
  }
}

2.3 模板变量

mapping 里的 {name}{dynamic_type} 占位符会被替换为字段名和检测到的动态类型。下面的例子设置所有的字符串字段使用一个与字段名同名的 analyzer,并且为所有的非字符串字段禁用掉 doc_values

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "named_analyzers": {
          "match_mapping_type": "string",
          "match": "*",
          "mapping": {
            "type": "text",
            "analyzer": "{name}"
          }
        }
      },
      {
        "no_doc_values": {
          "match_mapping_type":"*",
          "mapping": {
            "type": "{dynamic_type}",
            "doc_values": false
          }
        }
      }
    ]
  }
}

PUT my-index-000001/_doc/1
{
  "english": "Some English text", // @1
  "count":   5  // @2
}

@1: 此 english 字段被映射为一个带有 english 分析器的 string 字段。
@2: 此 count 字段被映射为一个禁用了 doc_valueslong 字段。

2.4 动态模板例子

这里是一些实质上有用的动态模板的例子:

结构化搜索

当你设置 "dynamic":"true",ES 将映射字符串字段为带有一个 keyword 子字段的 text 字段。如果你只想索引结构化的内容并且对全文本搜索不感兴趣,你可以让 ES 只映射你的字段为 keyword 字段。然而,你必须使用与被索引的字段相同的精确值来搜索这些字段。

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

text 映射型字符串

与上面的例子相反,如果你只关注在字符串字段上的全文搜索并且不计划运行聚合、排序或者精确搜索,你可以告诉 ES 指令只映射字符串为 text

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_text": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text"
          }
        }
      }
    ]
  }
}

或者,你可以创建一个动态模板来映射你的字符串字段为映射的运行时部分的 keyword 字段。当 ES 检测到一个 string 类型的新字段时,那些字段将被创建为 keyword 类型的运行时字段。
虽然你的 sting 字段不会被索引,但是他们的值被存储进 _source 可以被用在搜索请求,聚合,过滤和排序。
例如,下面的请求创建了一个动态模板映射 string 字段为 keyrod 类型的运行时字段。虽然 runtime 定义为空,基于 ES 对添加到映射里的字段类型所使用的动态映射规则,新的 string 字段将被映射为 keyword 运行时字段。任何不能通过日期检测和数字检测的 string 字段自动被映射为 keyword

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "runtime": {}
        }
      }
    ]
  }
}

你索引一个简单的文档:

PUT my-index-000001/_doc/1
{
  "english": "Some English text",
  "count":   5
}

当你查看映射时,你将看到那个 english 字段是一个 keyword类型的运行时字段:

GET my-index-000001/_mapping
{
  "my-index-000001" : {
    "mappings" : {
      "dynamic_templates" : [
        {
          "strings_as_keywords" : {
            "match_mapping_type" : "string",
            "runtime" : { }
          }
        }
      ],
      "runtime" : {
        "english" : {
          "type" : "keyword"
        }
      },
      "properties" : {
        "count" : {
          "type" : "long"
        }
      }
    }
  }
}

禁用定量(norms)

定量(norms)是索引时的分数因子。如果你不关心分数,你的实例的情况是你从来不会按分数来排序文档,那么在索引时你可以禁用掉这些分数因子的存储来节省一些空间。

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text",
            "norms": false,
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    ]
  }
}

模板里出现的 keyword 子字段和动态映射的默认规则保持了一致性。当然如果你不需要他们由于你不需要在这个字段上执行精确的搜索或聚合,你可以像上一部分那样把他们移除。

时间序列

当用Es做时间序列的分析时,通常会有很多的数字型的字段,你经常在它们上面进行聚合但不会进行过滤,你可以在这些字段上禁用掉索引以节省磁盘空间可能还会收获一些索引速度的提升。

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "unindexed_longs": {
          "match_mapping_type": "long",
          "mapping": {
            "type": "long",
            "index": false
          }
        }
      },
      {
        "unindexed_doubles": {
          "match_mapping_type": "double",
          "mapping": {
            "type": "float",  // @1
            "index": false
          }
        }
      }
    ]
  }
}

@1: 和默认的动态映射规则一样,doubles 被映射为 floats,通常精度足够了,但只需要一半的磁盘空间。