Mapping 是用来定义一个文档以及其所包含的字段如何存储和索引,我们可以在映射中事先定义好字段的数据类型、分词器、是否索引等属性,作用类似于数据库中 schema 的定义。Elasticsearch 支持动态索引,也支持在创建索引时指定 Mapping。
动态映射
Elasticsearch 最重要的功能之一就是让你尽可能快地开始探索数据,要为文档建立索引,不需要先创建索引、定义 Mapping 和字段,在写入文档时,如果索引不存在,会自动创建索引,并且会根据字段的类型自动识别并创建索引的 Mapping,这种机制被称为动态映射。
可以通过修改 dynamic 参数在文档和对象级别禁用此动态映射行为:
dynamic 为 true 时(默认),一旦有新增字段的文档写入,Mapping 也同时被更新。
dynamic 为 false 时,一旦有新增字段的文档写入,Mapping 不会被更新,因此新增字段的数据就无法被索引,但信息会出现在 _source 中。
dynamic 设为 strict 时,一旦有新增字段的文档写入,文档写入失败。
在实际项目中,如果遇到的业务在导入数据之前不确定有哪些字段,也不清楚字段的类型是什么,这种场景使用动态映射就非常合适。当 Elasticsearch 在文档中碰到一个以前没见过的字段时,它会根据字段的取值自动推测字段类型并自动把该字段添加到映射中,类型推断规则见下表:
| 值 | 自动推测的字段类型 |
|---|---|
| null | 没有字段被添加 |
| true、false | boolean 类型 |
| 浮点类型数字 | float 类型 |
| 整数 | long 类型 |
| JSON 对象 | object 类型 |
| JSON 数组 | 由数组中第一个非空值决定 |
| string | 有可能是 date 类型(通过了日期检测)、double 或 long 类型(通过了数字检测)、带有 keyword 子字段的 text 类型 |
以上是动态类型检测可以推断出的字段类型,其他数据类型必须显示映射。
1. 日期检测
默认情况下,日期检测是开启的,当 Elasticsearch 碰到一个新的字符串类型的字段时,它会检查这个字符串的内容是否与 dynamic_date_formats 中指定的任何日期模式匹配,如果匹配则该字段会被识别为 date 类型。但这种自动检测机制有时也会导致一些问题,比如索引一份这样的文档到 Elasticsearch 中:
{"message": "2015/09/02"}
如果 message 字段第一次被发现,那么根据规则它会被作为 date 字段添加。但如果下一份文档是这样的:
{"message": "Logged out"}
这时,该字段显然不是日期类型,但已经太迟了。该字段的类型已经是日期类型的字段了,因此这会导致一个异常被抛出。可以在 Mapping 或指定字段定义上将 date_detection 设为 false 来关闭日期检测,命令如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"date_detection": false}}'
设置完成后,一个字符串字段总是会被当作 text 类型。如果要新增一个 date 类型的字段则需要手动添加。
2. 数字检测
虽然 JSON 支持本地浮点和整数数据类型,但一些应用程序或语言有时可能将数字呈现为字符串。通常正确的解决方案是显式地映射这些字段,但是可以启用数字检测(默认禁用)来自动完成这一任务:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"numeric_detection": true}}'curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"my_float": "1.0","my_integer": "1"}'
- my_float 字段会被自动推断为 float 类型
- my_integer 字段会被自动推断为 long 类型
3. 动态映射模版
通过动态映射模板,可以在 mapping 之上拥有对新字段的完整控制,甚至可以根据字段的名称来设置映射。每个模板都有一个名字,用来描述这个模板做了什么。同时它有一个映射用来指定具体的映射信息,还有至少一个参数(比如 match)来规定对于什么字段需要使用该模板。
动态映射模板的匹配有先后,只有第一个匹配的模板会被使用。下面的例子中增加了一个名为 longsasstrings 的映射模板,如果字段名称以 long 开头,则将字符串转为 long 类型,映射如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"dynamic_templates": [{"longs_as_strings": {"match_mapping_type": "string","match": "long_*","unmatch": "*_text","mapping": {"type": "long"}}}]}}'
写入一条测试文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"long_num": "5","long_text": "foo"}'
long_num 字段会被解析为 long 类型,long_text 仍为默认的字符串类型,match_mapping_type 允许只对特定类型的字段使用模板,match 参数中指定可以匹配的字段名的规则,unmatch 参数指定不匹配的字段名规则。
静态映射
静态映射是在创建索引时手工指定索引映射,和 SQL 在建表语句中指定字段属性类似。相比动态映射,通过静态映射可以添加更详细、更精准的配置信息。
1. 字段类型
Elasticsearch 提供了多种不同的字段类型,具体如下:
1.1 alias
alias 类型为索引中的字段定义了一个别名,别名可用于 search 请求中的目标字段,但要注意,在使用 alias 类型时存在一些限制条件:
- 目标必须是一个具体字段,而不是一个对象或另一个字段别名
- 目标字段必须在创建别名时已经存在
- 如果定义了 nested 对象,字段别名必须与其目标具有相同的 nested 作用域
- 在写入时不支持写入字段别名
alias 的使用示例如下:
curl -X PUT "localhost:9200/trips?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"distance": {"type": "long"},"route_length_miles": {"type": "alias","path": "distance"}}}}'curl -X GET "localhost:9200/_search?pretty" -H 'Content-Type: application/json' -d'{"query": {"range" : {"route_length_miles" : {"gte" : 39}}}}'
1.2 text
如果一个字段是要被全文搜索的,比如:邮件内容、产品描述、新闻内容等,应该使用 text 类型。设置 text 类型后,字段内容会被分析,在生成倒排索引之前,字段内容会被分词器(analyzer)分成一个一个词项。通过分析过程,Elasticsearch 可以通过单个词项来搜索出包含该单词的文档。通常 text 类型的字段不用于排序,很少用于聚合。
把 fuII_name 字段设为 text 类型的 Mapping 如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"full_name": {"type": "text"}}}}'
1.3 keyword
keyword 类型适用于索引结构化的字段,比如:email地址、主机名、状态码等,通常用于精确匹配、过滤、排序和聚合。类型为 keyword 的字段只能通过精确值搜索到,这一点区别于 text 类型。
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"tags": {"type": "keyword"}}}}'
对于一些数字枚举类型的值,这些枚举值是数字,但是通常只用于精确匹配。在这种情况下,可以将这种数字值类型设置为 keyword 而不是数字类型,这样能获得更好的性能。因为对于 keyword 类型的 term query,ES 使用的是倒排索引,但 numeric 类型为了能有效的支持范围查询,它的存储结构并不是倒排索引,而是使用了一种名为 block KD tree 的存储结构(类似 B+树)。
参考链接:https://www.elastic.co/cn/blog/searching-numb3rs-in-5.0
1.4 number
Elasticsearch 支持的数字类型有:byte、short、integer、long、float、double、half_float 和 scaled_float,具体取值范围如下:
- byte:有符号 8 bit 整数,取值为 -128 ~ 127
- short:有符号 16 bit 整数,取值为 -32768 ~ 32767
- integer:有符号 32 bit 整数,取值为 -2**31 ~ 231**-1
- long:有符号 64 bit 整数,取值为 -2**63 ~ 263**-1
- float:32 bit 单精度 IEEE 754 浮点类型
- double:64 bit 双精度 IEEE 754 浮点类型
- half_float:16 bit 半精度 IEEE 754 浮点类型
- scaled_float:缩放类型的浮点数,通过 scaling_factor 指定缩放因子可将浮点数以整数形式存储
对于 double、float 和 half_float 来说,-0.0 和 +0.0 是不同的值,因此使用 term 查询查找 -0.0 不会匹配到 +0.0,同样在 range 查询中,如果上边界是 -0.0 则不会匹配 +0.0,如果下边界是 +0.0 则不会匹配 -0.0。
如何选择合适的类型:
对于整数类型的字段,在满足需求的情况下,要尽可能选择范围小的数据类型。比如,某个字段的取值最大值不会超过 100,那么选择 byte 类型即可。字段的长度越短,索引和搜索的效率越高。
当处理浮点数时,优先考虑使用 scaled_float 类型将浮点数存储为整数通常更高效。scaled_float 是通过缩放因子把浮点数变成 long 类型,比如价格只需要精确到分,price 字段的取值为 57.34,设置缩放因子为 100,那么 Elasticsearch 底层存储起来的就是 5734 这种整数类型(所有的 API 还是会把 price 的取值当作浮点数)。这对于节省磁盘空间非常有帮助,因为整数比浮点数更容易压缩。
数字类型配置映射的例子如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"number_of_bytes": {"type": "integer"},"time_in_seconds": {"type": "float"},"price": {"type": "scaled_float","scaling_factor": 100}}}}'
1.5 date
JSON 中没有日期类型,所以在 Elasticsearch 中的日期可以是以下几种形式:
- 格式化日期的字符串,如:”2015-01-01” 或 “2015/01/01 12:10:30” 等
- 代表 milliseconds-since-the-epoch 的长整型数(epoch 指的是一个特定的时间:1970-01-01 00:00:00 UTC)。
- 代表 seconds-since-the-epoch 的整型数。
Elasticsearch 内部在存储日期类型时是会包含时区信息的,如果我们没有在 JSON 代表日期的字符串中显式指定时区,则默认会把日期转换为 UTC(世界标准时间)并将其存储为表示 milliseconds-since-the-epoch 的长整型数,并且对 date 类型字段的查询会被转为对这种长整型数的范围查询。
但对日期进行查询时,由于我们所在的是东八区,所以查询出来的日期会延迟 8 小时,因为 Elasticsearch 内部是按 UTC 时区存储的,最佳实践方案是:我们在提交日期数据的时候,直接提交带有时区信息的日期字符串,比如:2016-07-15T12:58:17.136+0800。
日期将始终以字符串的形式出现,即使它们最初在 JSON 文档中以长整型数的形式提供。日期的格式可以进行自定义,如果没有通过 format 进行自定义,则默认格式如下:
strict_date_optional_time||epoch_millis
日期类型配置映射的例子如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"date": {"type": "date"}}}}'curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{ "date": "2015-01-01" }'curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'{ "date": "2015-01-01T12:10:30Z" }'curl -X PUT "localhost:9200/my_index/_doc/3?pretty" -H 'Content-Type: application/json' -d'{ "date": 1420070400001 }'
默认情况下,以上 3 个文档的日期格式都可以被解析,内部存储的是毫秒计时的长整型数。
在使用 format 指定日期格式时,索引中定义的日期格式与提交数据的日期格式要一致,否则会报错。我们可以通过 || 作为分隔符来指定多个格式。Elasticsearch 将依次尝试每种格式直到找到匹配的格式。如下,第一种格式将 milliseconds-since-the-epoch 的长整型值转换回字符串值。
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"date": {"type": "date","format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"}}}}'
1.5 boolean
boolean 类型的字段接受 JSON 中的 true 和 false,但也可以接受被解释为 true 或 false 的字符串:
- 假值表示:false、”false” 和 “”(空字符串)
- 真值表示:true 和 “true”
布尔类型配置映射的例子如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"is_published": {"type": "boolean"}}}}'curl -X POST "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"is_published": "true"}'curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'{"query": {"term": {"is_published": true}}}'
1.6 array
在 Elasticsearch 中,数组没有专门的字段数据类型。默认情况下,任何字段都可以包含 0 个或多个值,但数组中的所有值必须是相同的数据类型。例如:
- 字符数组:[“one”, “two”]
- 整型数组:[1, 2]
- 嵌套数组:[1, [2, 3]] 等价于 [1, 2, 3]
- 对象数组:[{“name”:”allen”, “age”:20}, {“name”:”John”, “age”:18}]
当使用对象数组时,我们无法单独查询对象数组中的每个对象。如果我们需要能够单独查询数组项,那么应该使用 nested 数据类型而不是 object 数据类型。
当动态添加字段时,数组的第一个值的类型决定了整个数组的类型,所有后续值必须具有相同的数据类型,混合数组类型是不支持的,比如:[1, “abc”]。数组可以包含 null 值,这个 null 值要么被我们配置的 null_value 所替换,要么被跳过。而空数组 [] 会被当作 missing field 对待。
在文档中使用 array 类型不需要提前做任何配置,默认开箱即用,使用示例如下:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"message": "some arrays in this document...","tags": [ "elasticsearch", "wow" ],"lists": [{"name": "prog_list","description": "programming list"},{"name": "cool_list","description": "cool stuff list"}]}'curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'{"message": "no arrays in this document...","tags": "elasticsearch","lists": {"name": "prog_list","description": "programming list"}}'
- tags 字段作为 text 字段类型被动态添加
- lists 字段作为 object 字段类型被动态添加
- 第二个文档虽然不包含数组,但是也可以索引到相同的字段中
1.7 object
JSON 文档本质上是分层的:文档可能包含内部对象,而内部对象本身又可能包含内部对象。因此我们不需要显示地将字段类型设置为 object,因为这是默认值。
如下示例:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"region": "US","manager": {"age": 30,"name": {"first": "John","last": "Smith"}}}'
上面的文档整体是一个 JSON 对象,JSON 中包含一个 manager 对象,manager 对象又包含名为 name 的内部对象。当写入到 Elasticsearch 之后,文档会被索引成简单的扁平 key-value 对,格式如下:
{"region": "US","manager.age": 30,"manager.name.first": "John","manager.name.last": "Smith"}
上面文档结构的显示映射如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"region": {"type": "keyword"},"manager": {"properties": {"age": { "type": "integer" },"name": {"properties": {"first": { "type": "text" },"last": { "type": "text" }}}}}}}}'
1.8 nested
nested 类型是 object 类型中的一个特例,可以让对象数组独立索引和查询。Lucene 没有内部对象的概念,所以 Elasticsearch 将对象层次扁平化,转化成字段名和值构成的简单列表。例如,下面的文档:
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"user" : [{"first" : "John","last" : "Smith"},{"first" : "Alice","last" : "White"}]}'
user 字段会被动态添加为 object 类型,最后会被转换为以下平整的格式:
{"user.first" : [ "alice", "john" ],"user.last" : [ "smith", "white" ]}
而 user.first 和 user.last 扁平化以后变为了多值字段,alice 和 white 的关联关系丢失了。执行以下搜索请求会搜索到上述文档:
curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'{"query": {"bool": {"must": [{ "match": { "user.first": "Alice" }},{ "match": { "user.last": "Smith" }}]}}}'
但事实上是不应该匹配的,如果需要为对象数组建立索引并维护数组中每个对象的独立性,则应该使用 nested 对象类型而不是 object 类型。在内部,nested 对象将数组中的每个对象索引为一个单独的隐藏文档,这意味着每个 nested 对象都可以使用 nested query 独立于其他对象进行查询。
我们将字段类型显示设置成 nested 类型,重复上面的文档写入,最后使用 nested query 进行查询:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"user": {"type": "nested"}}}}'curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'{"query": {"nested": {"path": "user","query": {"bool": {"must": [{ "match": { "user.first": "Alice" }},{ "match": { "user.last": "Smith" }}]}}}}}'
最终,这个查询不会被匹配,因为 Alice 和 Smith 不在同一个嵌套对象中。
nested 使用限制:
由于每个嵌套对象在底层被索引为一个单独的文档,如果我们对包含 100 个用户对象的单个文档进行索引,那么将创建 101 个 Lucene 文档,一个用于父文档,一个用于每个嵌套对象。因此,Elasticsearch 会适当地控制 nested 字段的数量以防止性能问题。
index.mapping.nested_fields.limit
nested 类型应该只在需要相互独立地查询对象数组时才使用,为了防止在 Mapping 定义中随意设置,该参数限制了每个索引下可定义的 nested 字段的数量,默认值为 50。
index.mapping.nested_objects.limit
该参数限制了单个文档在所有 nested 类型中可能包含的 nested 对象的数量,以防止当文档包含太多 nested 对象时出现内存不足错误,默认为 10000。
1.9 geo_point
geo_point 类型的字段用于存储地理位置信息的经纬度,可用于以下几种场景:
- 查找一定范围内的地理位置。
- 通过地理位置或相对中心点的距离来聚合文档。
- 把距离因素整合到文档的评分中。
- 通过距离对文档排序。
geo_point 字段可以接收以下 4 种类型的地理位置数据:
# 经纬度坐标键值对curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"location": {"lat": 41.12, "lon": -71.34}}'# 字符串格式的地理坐标参数curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'{"location": "41.12,-71.34"}'# 地理坐标的哈希值curl -X PUT "localhost:9200/my_index/_doc/3?pretty" -H 'Content-Type: application/json' -d'{"location": "drm3btev3e86"}'# 数组形式的地理坐标,注意数组的经纬度顺序是反的curl -X PUT "localhost:9200/my_index/_doc/4?pretty" -H 'Content-Type: application/json' -d'{"location": [ -71.34, 41.12 ]}'# 一个地理边界框查询,用于查找位于该框内的所有地理点curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'{"query": {"geo_bounding_box": {"location": {"top_left": {"lat": 42, "lon": -72},"bottom_right": {"lat": 40, "lon": -74}}}}}'
1.10 geo_shape
geo_point 类型可以存储一个坐标点,而 geo_shape 类型则可以存储一块区域,比如矩形、三角形或其他多边形。我们可以使用 geo_shape Query 来查询包含该字段类型的文档。Elasticsearch 使用 GeoJSON 格式来表示地理形状。GeoJSON 是一种对各种地理数据结构进行编码的格式,支持点、线、面、多点、多线、多面等几何类型。具体类型说明见下表:
| Elasticsearch 地理形状类型 | 说明 |
|---|---|
| point | 一个单独的经纬度坐标点 |
| linestring | 任意的线条,由两到多个点组成 |
| polygon | 由 N+1 个点组成的封闭 N 边行 |
| multipoint | 一组不连续但有可能想关联的点 |
| multilinestring | 多条不关联的线 |
| multipolygon | 多个不关联的多边形 |
| geometrycollection | 几何对象的集合 |
| envelope | 由左上角坐标或右下角坐标确定的封闭矩形 |
| circle | 由圆心和半径确定的圆,默认单位为米 |
写入一条由经纬度组成的点:
curl -X POST "localhost:9200/example/_doc?pretty" -H 'Content-Type: application/json' -d'{"location" : {"type" : "point","coordinates" : [-77.03653, 38.897676]}}'
写入一条由多个点组成的线:
curl -X POST "localhost:9200/example/_doc?pretty" -H 'Content-Type: application/json' -d'{"location" : {"type" : "linestring","coordinates" : [[-77.03653, 38.897676], [-77.009051, 38.889939]]}}'
写入一条首尾封闭的多边形:
curl -X POST "localhost:9200/example/_doc?pretty" -H 'Content-Type: application/json' -d'{"location" : {"type" : "polygon","coordinates" : [[ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]]}}'
1.11 ip
ip 类型的字段用于存储 IPv4 或 IPv6 的地址,使用示例如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"ip_addr": {"type": "ip"}}}}'curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{"ip_addr": "192.168.1.1"}'curl -X GET "localhost:9200/my_index/_search?pretty" -H 'Content-Type: application/json' -d'{"query": {"term": {"ip_addr": "192.168.0.0/16"}}}'
1.12 range
range 类型的使用场景包括网页中的时间选择表单、年龄范围选择表单等,range 类型支持的类型和取值范围见下表所示:
| range 类型 | 范围 |
|---|---|
| integer_range | -231 ~ 231-1 |
| long_range | -263 ~ 263-1 |
| float_range | 单精度 32 bit IEEE 754 |
| double_range | 双精度 64 bit IEEE 754 |
| date_range | 64 位整数,毫秒计时 |
| ip_range | IPv4 或 IPv6 的地址范围 |
使用示例如下:
curl -X PUT "localhost:9200/range_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"expected_attendees": {"type": "integer_range"},"time_frame": {"type": "date_range","format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"}}}}'curl -X PUT "localhost:9200/range_index/_doc/1?refresh&pretty" -H 'Content-Type: application/json' -d'{"expected_attendees" : {"gte" : 10,"lte" : 20},"time_frame" : {"gte" : "2015-10-31 12:00:00","lte" : "2015-11-01"}}'# 使用该搜索请求会查询出上面的文档curl -X GET "localhost:9200/range_index/_search?pretty" -H 'Content-Type: application/json' -d'{"query" : {"term" : {"expected_attendees" : {"value": 12}}}}'curl -X GET "localhost:9200/range_index/_search?pretty" -H 'Content-Type: application/json' -d'{"query" : {"range" : {"time_frame" : {"gte" : "2015-10-31","lte" : "2015-11-01","relation" : "within"}}}}'
1.13 token_count
token_count 用于统计字符串分词后的词项个数,本质上是一个整数型宇段。举个例子,映射中指定 name 字段为 text 类型,增加 name.length 字段用于统计分词后词项的长度,类型为 token_count,分词器使用标准分词器,命令如下:
curl -X PUT "localhost:9200/my_index?pretty" -H 'Content-Type: application/json' -d'{"mappings": {"properties": {"name": {"type": "text","fields": {"length": {"type": "token_count","analyzer": "standard"}}}}}}'
写入两条测试文档,解析后第一条文档的 name.length 的值为 2,第二条文档的 name.length 的值为 3。
curl -X PUT "localhost:9200/my_index/_doc/1?pretty" -H 'Content-Type: application/json' -d'{ "name": "John Smith" }'curl -X PUT "localhost:9200/my_index/_doc/2?pretty" -H 'Content-Type: application/json' -d'{ "name": "Rachel Alice Williams" }'
