ElasticSearch-Nested嵌套类型解密

栏目: 编程工具 · 发布时间: 5年前

内容简介:Lucene Field本身并不支持嵌套类型,最多也就支持多值类型。ElasticSearch进行了扩展,使得Field可以是object对象类型或者nested嵌套类型。object类型,本质上是把字段使用上的区别,可以参考官方

Lucene Field本身并不支持嵌套类型,最多也就支持多值类型。ElasticSearch进行了扩展,使得Field可以是object对象类型或者nested嵌套类型。

object类型,本质上是把字段 路径打平 ,最终在索引里还是一个正常的Field字段,object类型转化后,并不会保留 对象内的属性对应关系 ,这在查询中可能需要特别注意。然后nested嵌套类型却实现的对应关系。

使用上的区别,可以参考官方 Nested Data Type

本文,我们主要关注的是elasticsearch内部是如何实现的?

ElasticSearch的官方说法

Because nested documents are indexed as separate documents, they can only be accessed within the scope of the nested query, the nested / reverse_nested aggregations, or nested inner hits .

For instance, if a string field within a nested document has index_options set to offsets to allow use of the postings during the highlighting, these offsets will not be available during the main highlighting phase. Instead, highlighting needs to be performed via nested inner hits .

Indexing a document with 100 nested fields actually indexes 101 documents as each nested document is indexed as a separate document. To safeguard against ill-defined mappings the number of nested fields that can be defined per index has been limited to 50. See Settings to prevent mappings explosion edit .

从这里的介绍,我们所知有限。能得到的结论如下:

  • nested字段在索引里是作为Document 单独存储
  • 普通的query对nested字段查询无效,必须使用 nested Query
  • highlight高亮也需要使用专门的
  • 对于nested类型的field个数是有限制的。

Nested类型怎么分开存储的

org.elasticsearch.index.mapper.DocumentParser 类是专门用于解析要索引的Document的。

其中有这样一个方法来解析对象类型。

static void parseObjectOrNested(ParseContext context, ObjectMapper mapper)throws IOException {
    ...
        
    ObjectMapper.Nested nested = mapper.nested();
    if (nested.isNested()) {
        //如果是nested类型,这里单独处理了
        context = nestedContext(context, mapper);
    }

  ...
}

下面来看是怎么处理的。

private static ParseContext nestedContext(ParseContext context, ObjectMapper mapper){
    context = context.createNestedContext(mapper.fullPath());
    ParseContext.Document nestedDoc = context.doc();
    ParseContext.Document parentDoc = nestedDoc.getParent();

    // We need to add the uid or id to this nested Lucene document too,
    // If we do not do this then when a document gets deleted only the root Lucene document gets deleted and
    // not the nested Lucene documents! Besides the fact that we would have zombie Lucene documents, the ordering of
    // documents inside the Lucene index (document blocks) will be incorrect, as nested documents of different root
    // documents are then aligned with other root documents. This will lead tothe nested query, sorting, aggregations
    // and inner hits to fail or yield incorrect results.
    // 这里把根/父Document的id,同时赋予nested Document,保证删除父Document的时候,nested Document同时被删除
    IndexableField idField = parentDoc.getField(IdFieldMapper.NAME);
    if (idField != null) {
        // We just need to store the id as indexed field, so that IndexWriter#deleteDocuments(term) can then
        // delete it when the root document is deleted too.
        // 这里增加一个_id字段
        nestedDoc.add(new Field(IdFieldMapper.NAME, idField.binaryValue(), IdFieldMapper.Defaults.NESTED_FIELD_TYPE));
    } else {
        throw new IllegalStateException("The root document of a nested document should have an _id field");
    }

    // the type of the nested doc starts with __, so we can identify that its a nested one in filters
    // note, we don't prefix it with the type of the doc since it allows us to execute a nested query
    // across types (for example, with similar nested objects)
    // 这里增加一个_type字段,值为__开头,这里需要注意
    nestedDoc.add(new Field(TypeFieldMapper.NAME, mapper.nestedTypePathAsString(), TypeFieldMapper.Defaults.FIELD_TYPE));
    return context;
}

总之,经过DocumentParser的处理,Nested Document多了这样的2个字段

  • _id,值为父Document的id,用来关联
  • _type,值为‘__’开头的,标志特定nested 类型。

Nested类型普通Query如何隐藏

从上面存储知道,nested document是和普通Document一起存在索引中的,但对外是隐藏的,甚至是MatchAllQuery。

org.elasticsearch.common.lucene.search.Queries 类中:

/**
 * Creates a new non-nested docs query
 * @param indexVersionCreated the index version created since newer indices can identify a parent field more efficiently
 */
public static Query newNonNestedFilter(Version indexVersionCreated){
    if (indexVersionCreated.onOrAfter(Version.V_6_1_0)) {
        // ES 6.1.0之后。 只保留有_primary_term这个元字段的,所有的父Document都带有这个Field
        return new DocValuesFieldExistsQuery(SeqNoFieldMapper.PRIMARY_TERM_NAME);
    } else {
        // 老模式
        return new BooleanQuery.Builder()
            .add(new MatchAllDocsQuery(), Occur.FILTER)
            .add(newNestedFilter(), Occur.MUST_NOT)  //取非。过滤掉nested Document
            .build();
    }
}

public static Query newNestedFilter(){
    // _type以“__”为前缀
    return new PrefixQuery(new Term(TypeFieldMapper.NAME, new BytesRef("__")));
}

下面来看一下 _primary_term 是如何放进去的。这里涉及到 org.elasticsearch.index.mapper.SeqNoFieldMapper 这个类。他主要是用来为Document添加 _seq_no 元字段的,附带着把 _primary_term 给加了进去。

@Override
protected void parseCreateField(ParseContext context, List<IndexableField> fields)throws IOException {
    // see InternalEngine.innerIndex to see where the real version value is set
    // also see ParsedDocument.updateSeqID (called by innerIndex)
    SequenceIDFields seqID = SequenceIDFields.emptySeqID(); // 默认SeqID
    context.seqID(seqID);
    fields.add(seqID.seqNo);
    fields.add(seqID.seqNoDocValue);
    fields.add(seqID.primaryTerm); // 添加到fields里
}
public static SequenceIDFields emptySeqID(){
    return new SequenceIDFields(new LongPoint(NAME, SequenceNumbers.UNASSIGNED_SEQ_NO),
            new NumericDocValuesField(NAME, SequenceNumbers.UNASSIGNED_SEQ_NO),
            // 主要这里把_primary_term=0复制为了primaryTerm Field
            new NumericDocValuesField(PRIMARY_TERM_NAME, 0), new NumericDocValuesField(TOMBSTONE_NAME, 0));
}

下面,我们来看下,都什么地方使用到了 Queries.newNonNestedFilter .这里我在IDEA里做了一个截图。

ElasticSearch-Nested嵌套类型解密

从上可以看出,基本的正常查询都默认加了这个filter的,只有是nestedQuery才做特别的处理。

总结下如何隐藏的:

ElasticSearch版本 隐藏实现方式
小于6.1.0 过滤掉_type以“__”为前缀的nested document
大于等于6.1.0 只获取有 __primary_term Field的父Document

综述

无论是ElasticSearch,还是Solr, 其底层都是通过Lucene来做索引的。Lucene本身并不支持这种嵌套类型。ElasticSearch通过一个比较hack的方式支持了这样的功能,用起来更加的顺手,功能更强大。

同时,我们通过上面的分析,Nested也是有缺点的。

  • nested无形中增加了索引量,如果不了解具体实现,将无法很好的进行文档划分和预估。ES限制了Field个数和nested对象的size,避免无限制的扩大
  • nested Query 整体性能慢,但比parent/child Query稍快。应从业务上尽可能的避免使用NestedQuery, 对于性能要求高的场景,应该直接禁止使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

趋势红利

趋势红利

刘润 / 文化发展出版社(原印刷工业出版社) / 2016-6-1 / 45.00

【编辑推荐】 1、国内顶尖的互联网转型专家,海尔、百度等知名企业战略顾问刘润送给传统企业的转型、创新“导航仪”,这个时代企业家的必修课 站在近200年商业全景图角度,刘润发现三种企业类型(产品型、渠道型、营销型),针对不同企业类型定制转型战略(找到自己的未来红利),方便 传统企业对号入座:不走错路就是节省时间,适合自己的最有效率。 本书内容还源自芬尼克兹、红领集团、名创优品、必要......一起来看看 《趋势红利》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具