ES101系列08 | 数据建模和索引重建

73 阅读5分钟

本篇文章主要讲解 ElasticSearch 中的数据建模内容,包括如何处理关联关系、索引重建和字段建模最佳实践等。

在 ElasticSearch 中处理关联关系

关系型数据库,一般会考虑 Normalize 数据;在 Elasticsearch,往往考虑 Denormalize 数据。

Denormalize 的好处:读的速度变快/无需表连接/无需行锁

Elasticsearch 并不擅长处理关联关系。我们一般采用以下四种方法处理关联:

  • 对象类型
  • 嵌套对象 (Nested Object)
  • 父子关联关系 (Parent/Child)
  • 应用端关联
特性对象类型嵌套对象父子关联应用端关联
本质默认处理 JSON 对象的方式特殊的对象类型同一索引内文档间的逻辑链接由应用程序维护关联
存储结构对象属性扁平化存储到父文档作为父文档内部的独立隐藏文档父子文档独立存储在同一分片关联数据存储在不同文档或外部系统
数据边界无边界,对象属性合并到父文档有边界,每个嵌套对象独立存储父子文档完全独立完全独立,无 ES 内部关联
查询特点可能匹配不同对象的字段组合确保条件匹配同一嵌套对象内部需用 has_childhas_parent 等查询需多次查询,先查主文档再查关联文档
更新特点更新需重索引整个父文档更新需重索引整个父文档可单独更新子文档,不影响父文档每个文档可单独更新
适用场景简单键值对对象,无需独立查询对象数组需独立查询和匹配子文档频繁更新或数量大关系简单、查询量小或数据分散
缺点无法精确匹配数组内对象组合更新成本高,嵌套数量有限制查询性能低,内存消耗大网络开销大,应用逻辑复杂

Nested Data Type

  • Nested 数据类型:允许对象数组中的对象被独立索引。
  • 使用 nested 和 properties 关键字,将所有数组索引到多个分隔的文档。
  • 在内部,Nested 文档会被保存在两个 Lucene 文档中,在查询时做 Join 处理。

示例

PUT my_movies
{
    "mappings": {
        "properties": {
            "actors": {
                "type": "nested",
                "properties": {
                    "first_name": {
                        "type": "keyword"
                    },
                    "last_name": {
                        "type": "keyword"
                    }
                }
            },
            "title": {
                "type": "text",
                "fields": {
                    "keyword": {
                        "type": "keyword",
                        "ignore_above": 256
                    }
                }
            }
        }
    }
}

若没有指定 "type": "nested" 时,数组数据会扁平化存储,搜索会出现不该出现的内容。

Parent / Child

对象和 Nested 对象每次更新都需要重新索引整个对象。

ES 提供了类似关系型数据库中 Join 的实现。可通过维护 Parent / Child 的关系,从而分离两个对象。

  • 父文档和子文档是两个独立的文档。
  • 更新父文档无需重新索引子文档。子文档被添加,更新或者删除也不会影响到父文档和其他的子文档。

示例

1、添加数据

PUT my_blogs
{
  "settings": {
    "number_of_shards": 2
  },
  "mappings": {
    "properties": {
      "blog_comments_relation": {
        "type": "join",
        "relations": {
          "blog": "comment"
        }
      },
      "content": {
        "type": "text"
      },
      "title": {
        "type": "keyword"
      }
    }
  }
}

// 索引父文档
PUT my_blogs/_doc/blog2
{
  "title":"Learning Hadoop",
  "content":"learning Hadoop",
    "blog_comments_relation":{
    "name":"blog"
  }
}


// 索引子文档
PUT my_blogs/_doc/comment1?routing=blog1
{
  "comment":"I am learning ELK",
  "username":"Jack",
  "blog_comments_relation":{
    "name":"comment",
    "parent":"blog1"
  }
}

// 索引子文档
PUT my_blogs/_doc/comment2?routing=blog2
{
  "comment":"I like Hadoop!!!!!",
  "username":"Jack",
  "blog_comments_relation":{
    "name":"comment",
    "parent":"blog2"
  }
}

// 索引子文档
PUT my_blogs/_doc/comment3?routing=blog2
{
  "comment":"Hello Hadoop",
  "username":"Bob",
  "blog_comments_relation":{
    "name":"comment",
    "parent":"blog2"
  }
}

2、父子关系查询

// 根据父文档ID查看
GET my_blogs/_doc/blog2

// 根据 Parent Id 查询
POST my_blogs/_search
{
  "query": {
    "parent_id": {
      "type": "comment",
      "id": "blog2"
    }
  }
}

// has_child 查询,返回父文档
POST my_blogs/_search
{
  "query": {
    "has_child": {
      "type": "comment",
      "query" : {
            "match": {
                "username" : "Jack"
            }
        }
    }
  }
}

// has_parent 查询,返回相关的子文档
POST my_blogs/_search
{
  "query": {
    "has_parent": {
      "parent_type": "blog",
      "query" : {
			"match": {
				"title" : "Learning Hadoop"
			}
		}
    }
  }
}

索引重建

需要索引重建的场景如下:

  • 索引的 Mappings 发生变更:字段类型更改,分词器及字典更新。
  • 索引的 Settings 发生变更:索引的主分片数发生改变。
  • 集群内,集群间需要做数据迁移。

索引重建的方式:

  • Update By Query:在现有索引上重建。
  • Reindex:在其他索引上重建索引。
特性Update By QueryReindex
核心操作原地更新现有索引文档复制数据到新索引
操作对象单个索引源索引和目标索引
数据迁移不支持支持
索引结构不可更改 Mapping/Settings/分片可更改 Mapping/Settings/分片
主要用途批量修改文档内容或删除文档重建索引结构/迁移数据
性能影响对原索引读写压力大源索引读压力,目标索引写压力
原子性非原子操作,失败需手动处理目标索引独立,失败可重试
适用场景字段值更新、文档删除更改字段类型、分片数或跨集群迁移等

字段建模

flowchart LR

    字段类型["字段类型"] --> 是否要搜索及分词["是否要搜索及分词"]

    是否要搜索及分词 --> a1["是否要聚合及排序"]

    a1 --> 是否要额外的存储

Mapping 字段的相关设置:

  • Enabled —— 设置成 false,仅做存储,不支持搜索和聚合分析(数据保存在 _source 中)。
  • Index —— 是否构建倒排索引。设置成 false,无法被搜索,但还是支持 aggregation,并出现在 _source 中。
  • Norms —— 如果字段用来过滤和聚合分析,可以关闭,节约存储。
  • Doc_values —— 是否启用 doc_values,用于排序和聚合分析。
  • Field_data —— 如果要对 text 类型启用排序和聚合分析,fielddata 需要设置成 true。
  • Store —— 默认不存储,数据默认存储在 _source。
  • Coerce —— 默认开启,是否开启数据类型的自动转换(例如,字符串转数字)。
  • Multifields 多字段特性。
  • Dynamic —— true/false/strict 控制 Mapping 的自动更新。

写在最后

这是该系列的第八篇,主要讲解 ElasticSearch 中的数据建模内容,包括如何处理关联关系、索引重建和字段建模最佳实践。可以自己去到 Kibana 的 Dev Tool 实战操作,未来会持续更新该系列,欢迎关注👏🏻。

同时欢迎关注公众号:LanTech指南。不定时分享职场思考、独立开发日志、大厂方法论和后端经验❤️

参考

  1. github.com/onebirdrock…
  2. www.elastic.co/elasticsear…