Elasticsearch中的数据建模并不像处理关系数据库时那样明显。与依赖数据规范化和 SQL 连接的传统关系数据库不同,Elasticsearch 需要采用其他方法来管理关系。
在 Elasticsearch 中管理关系有四种常见的解决方法:
应用程序端连接
数据非规范化
嵌套字段类型和嵌套查询
亲子关系
在本博客中,我们将讨论如何设计数据模型以使用嵌套字段类型和父子关系来处理关系。我们将介绍这两种技术的架构、性能影响和用例。
Elasticsearch 支持嵌套结构,其中对象可以包含其他对象。嵌套字段类型是主文档内的 JSON 对象,它们可以拥有自己独特的字段和类型。这些嵌套对象被视为单独的隐藏文档,只能使用嵌套查询进行访问。
嵌套字段类型非常适合数据完整性、紧密耦合和层次结构很重要的关系。这些关系包括一对一和一对多关系,其中有一个主要实体。例如,在单个文档中表示一个人及其多个地址和电话号码。
使用嵌套字段类型,Elasticsearch 会将整个文档以及父对象和嵌套对象存储在单个 Lucene 块和段中。由于关系包含在文档中,因此可以提高查询速度。
让我们看一个带有评论的博客文章示例。我们希望将评论嵌套在博客文章下方,以便可以轻松地在同一文档中一起查询它们。
{ "post_id": "1", "title": "Introduction to Elasticsearch Data Modeling", "content": "Exploring various data modeling options in Elasticsearch.", "comments": [ { "comment_id": "101", "text": "Great overview of data modeling!" }, { "comment_id": "102", "text": "Looking forward to more content." } ] }
嵌套对象关系的好处包括:
更新效率低下:对具有嵌套对象的文档的任何部分进行更新、插入和删除都需要重新索引整个文档,这可能会占用大量内存,尤其是在文档很大或更新频繁的情况下。
具有大型嵌套字段的查询性能:如果您的文档具有特别大的嵌套字段,这可能会对性能产生影响。这是因为搜索请求会检索整个文档。
多层嵌套可能会变得很复杂:在多层嵌套结构中运行查询仍然会变得很复杂。这是因为查询可能涉及嵌套查询中的嵌套查询,从而导致代码的可读性降低。
在父子映射中,文档被组织成父类和子类。每个子文档都与父文档有直接关联。这种关系是通过子文档中与父文档 ID 匹配的特定字段值建立的。父子模型采用分散的方式,父文档和子文档独立存在。
父子连接适用于实体之间的一对多或多对多关系。想象一下这样一个应用程序,您想要在公司和联系人之间建立关系,并想要搜索公司和联系人以及特定公司的联系人。
Elasticsearch 通过跟踪哪些父级与哪些子级相连,并让两个实体位于同一分片上,使父子连接变得高效。通过本地化连接操作,Elasticsearch 避免了广泛的分片间通信需求,而这可能是性能瓶颈。
让我们以博客文章和评论的父子关系为例。每篇博客文章(即父级)可以有多个评论(即子级)。要创建父子关系,让我们按如下方式索引数据:
PUT my-index-000001 { "mappings": { "properties": { "post_id": { "type": "keyword" }, "post_id": { "type": "join", "relations": { "post": "comment" } } } } }
父文档将是如下所示的帖子:
{ "post_id": "1", "title": "Introduction to Elasticsearch Data Modeling", "content": "Exploring various data modeling options in Elasticsearch." }
子文档将是一条评论,其中包含将其链接到其父文档的 post_id。
{ "comment_id": "101", "text": "Great overview of data modeling!", "post_id": "1" }
亲子建模的好处包括:
类似于关系数据模型:在父子关系中,父文档和子文档是分开的,并通过唯一的父 ID 链接。此设置更接近关系数据库模型,对于熟悉此类概念的人来说可能更直观。
更新效率:可以添加、修改或删除子文档,而不会影响父文档或其他子文档。这在处理需要频繁更新的大量子文档时尤其有用。请注意,将子文档与其他父文档关联是一个更复杂的过程,因为新的父文档可能位于另一个分片上。
更适合异构子文档:由于子文档是分开存储的,因此它们可能更节省内存和存储,特别是在存在许多子文档且其大小差异很大的情况下。
亲子关系的缺点包括:
查询成本高、速度慢:跨不同索引连接文档会增加查询执行期间的计算工作,从而再次影响性能。Elasticsearch 指出, 父子查询可能比查询嵌套对象慢 5-10 倍。
映射开销:父子关系会消耗更多内存和缓存资源。Elasticsearch 维护父子关系映射,该映射可能会变得很大并消耗大量内存,尤其是在文档量很大的情况下。
分片大小管理:由于父文档和子文档都位于同一分片上,因此存在整个集群中数据分布不均匀的潜在风险。某些分片可能会比其他分片大得多,尤其是当父文档带有许多子文档时。这可能导致管理和扩展 Elasticsearch 集群面临挑战。
重新索引和集群维护:如果您需要重新索引数据或更改分片策略,父子关系会使此过程复杂化。您需要确保在执行此类操作期间保持关系完整性。常规集群维护任务(例如分片重新平衡或节点升级)可能会变得更加复杂。必须特别小心,以确保在这些过程中父子关系不会中断。
Elastic是 Elasticsearch 背后的公司,它始终建议您在建立父子关系之前进行应用程序端连接、数据非规范化和/或嵌套对象。
下表回顾了嵌套字段类型和查询以及父子关系的特点,以便并排比较数据建模方法。
| 嵌套字段类型和嵌套查询 | 亲子关系 |
---|---|---|
定义 | 将一个对象嵌套在另一个对象中 | 将父文档和子文档链接在一起 |
关系 | 一对一、一对多 | 一对多、多对多 |
查询速度 | 由于数据存储在同一个块和段中,因此通常比父子关系更快 | 由于父文档和子文档在查询时连接在一起,因此通常比嵌套对象慢 5-10 倍。 |
查询灵活性 | 灵活性不如父子查询,因为它将查询范围限制在每个嵌套对象的范围内 | 提供更大的查询灵活性,因为可以一起或单独查询父文档或子文档 |
数据更新 | 更新嵌套对象需要重新索引整个文档 | 更新子文档更容易,因为它不需要重新索引所有文档 |
管理 | 由于所有内容都包含在单个文档中,因此管理更加简单 | 由于对父文档和子文档之间的关系进行单独索引和维护,管理起来更加复杂 |
用例 | 存储和查询具有多层次结构的复杂数据 | 父母较少、子女较多的关系,如产品和产品评论 |
虽然 Elasticsearch 提供了多种SQL 样式连接解决方法,包括嵌套查询和父子关系,但这些模型的扩展性已得到证实。在设计大规模应用程序时,考虑使用具有原生 SQL 连接功能的替代方法Rockset可能是明智之举。
Rockset 是一个搜索和分析数据库,专为对任何数据(包括深度嵌套的 JSON 数据)进行 SQL 搜索、聚合和连接而设计。当数据流入 Rockset 时,它会被编码到数据库的核心数据结构中,用于存储和索引数据以便快速检索。Rockset 使用其基于 SQL 的查询优化器以允许快速查询(包括连接)的方式对数据进行索引。因此,无需预先进行数据建模即可支持 SQL 连接。
Elasticsearch 面临的挑战之一是如何在数据更新时以有效的方式保留关系。原因之一是 Elasticsearch 建立在 Apache Lucene 之上,它将数据存储在不可变的段中,导致所有文档都需要重新索引。Rockset 使用 RocksDB(一种由 Meta 开源并专为数据突变而构建的键值存储),以便能够有效地支持字段级更新,而无需重新索引整个文档。
让我们将 Elasticsearch 中的父子关系方法与 Rockset 中的SQL 查询进行比较。
在上面的父子关系示例中,我们通过创建两种文档类型来对具有多条评论的帖子进行建模:
帖子或父文档类型
评论或子文档类型
我们使用唯一标识符(父 ID)来建立父文档和子文档之间的关系。在查询时,我们使用 Elasticsearch DSL 来检索特定帖子的评论。
在 Rockset 中,包含帖子的数据将存储在一个集合中,即关系世界中的一张表,而包含评论的数据将存储在单独的集合中。在查询时,我们将使用 SQL 查询将数据连接在一起。
以下是两种方法的比较:
POST /blog/posts/1 { "title": "Elasticsearch Modeling", "content": "A post about data modeling in Elasticsearch" } POST /blog/comments/2?parent=1 { "text": "Great post!" } POST /blog/comments/3?parent=1 { "text": "I learned a lot from this." }
要根据帖子的标题和所有评论检索帖子,您需要创建如下查询。
GET /posts/_search { "query": { "bool": { "must": [ { "match": { "title": "Exploring Elasticsearch Models" } } ] } }, "inner_hits": { "_source": ["text"], "name": "comments", "path": "comments" } }
要查询这些数据,您只需编写一个简单的 SQL 查询。
SELECT p.title, p.content, c.text FROM posts p JOIN comments c ON p.post_id = c.post_id WHERE p.post_id = 1;
如果您的应用程序需要连接多个数据集,那么 Rockset 比 Elasticsearch 更直接、更可扩展。它还简化了操作,因为您不需要重新建模数据、管理更新或重新索引操作。
本博客概述了 Elasticsearch 中的嵌套字段类型和嵌套查询以及父子关系,旨在帮助您确定最适合您的工作负载的数据建模方法。
嵌套字段类型和查询对于一对一或一对多关系非常有用,其中关系在单个文档中维护。这被认为是一种更简单、更具可扩展性的关系管理方法。
父子关系模型更适合一对多到多对多的关系,但复杂性增加,特别是当关系需要包含在特定的分片中时。
如果您的应用程序的主要要求之一是建模关系,那么考虑 Rockset 可能是有意义的。Rockset 简化了数据建模,并使用 SQL 连接提供了一种更具可扩展的关系管理方法。您可以立即开始免费试用,并获得 300 美元的积分,以比较和对比 Elasticsearch 和 Rockset 的性能。