我们知道 ES 的一个索引会对应着多个分片和副本,而每一个分片或副本就是一个完整的 Lucene 索引。

ES 索引 -> ES 分片 -> Lucene 索引

Elasticsearch 在索引数据时,会对输入的数据进行解析,得到一个个单独的字段。ES 本身还会添加一些额外的字段,最后把这些字段作为一个文档保存到 Lucene 中,ES 自身额外添加的 field:_uid, _source, _type, _version

从原始输入中解析出相应字段的源码 在此,在这里我们看到 nested 会把当前的 document 作为自己的 parent,nested 字段会使用其 parentDoc 的_uid 作为自己的_uid,这就意味着 nested 字段是和其 parent 文档拥有一样的_uid 的。

在上一步解析出了数据中的字段之后,ES 把所有解析得到的字段存放到ParsedDocument中,具体源码 在此

最终 ES 把所有的字段存储到 Lucene 中,一个文档中所有字段的 _uid 都是一样的,所有的字段在存储时都使用这个相同的 _uid。ES 根据版本决定这个数据是更新还是新增,版本比较以及数据写入到 Lucene 的源码 在此。ES 会给每个 nested 的数据创建一个独立的 Lucene document,所以一次写入一共有 nested + 1 个文档数量。如果一个字段不是 nested,就普通的解析一下变成单个字段然后存放在主 document 中进行保存。

例如如下的文本

{
    "city": "Nanjing",
    "comments": [
        {
            "name": "Mike",
            "comment": "Great"
        },
        {
            "name": "Mark",
            "comment": "Interesting"
        }
    ]
}

如果 comments 是一个普通的字段,那么 ES 通过解析操作我们可以得到如下的字段

city: Nanjing
comments.name: Mike
comments.comment: Great
comments.name: Mark
comments.comment: Interesting

这些字段存放在一个文档中,之后该文档被保存到 Lucene。

这样保存的一个问题在于 comments 的 name 和 comment 字段之间失去了关联,如下的一个文本将会和上面的文本生成完全一样的字段:

{
    "city": "Nanjing",
    "comments": [
        {
            "name": "Mark",
            "comment": "Great"
        },
        {
            "name": "Mike",
            "comment": "Interesting"
        }
    ]
}

这个文本生成的字段如下

city: Nanjing
comments.name: Mark
comments.comment: Great
comments.name: Mike
comments.comment: Interesting

这里生成的字段和上面的文本完全一致,所以如果我们想要查询 comments.name == Mike && comments.comment == Great 的结果,那么这两条数据都是符合要求的,但这可能不是我们想要的结果,我们也许只想要查询第一条数据而已。

但是如果 comments 是一个 nested 类型的字段,那么 ES 就会把这段文本解析为三个文档,分别为

// document1
city: Nanjing
// document2
comments.name: Mike
comments.comment: Great
// docuemtn3
comments.name: Mark
comments.comment: Interesting

这段文本被拆分为三个 document 保存到 Lucene 中,这样一来 comments 的 name 和 comment 就产生了关联,此时进行查询 comments.name == Mike && comments.comment == Great,那么就只有该条数据才是符合要求的。

参考:
Nested Objects
elasticsearch