Elasticsearch 是一个基于 的开源分布式搜索和分析引擎,使用 Apache Lucene 构建,可提供快速的实时搜索功能。它是一种默认面向文档、可扩展且无模式的 NoSQL 数据存储。 Elasticsearch 旨在大规模处理大型数据集。作为搜索引擎,它提供快速索引和搜索功能,可以跨多个节点水平扩展。 JSON 无耻插件: 是一个云端实时索引数据库。它会自动构建索引,这些索引不仅针对搜索进行了优化,还针对聚合和联接进行了优化,使您的应用程序能够快速轻松地查询数据,无论数据来自何处以及采用什么格式。但这篇文章将重点介绍一些解决方法,如果您确实想在 Elasticsearch 中执行 SQL 样式的联接。 Rockset 为什么数据关系很重要? 我们生活在一个高度互联的世界,处理数据关系非常重要。关系数据库擅长处理关系,但随着业务需求的不断变化,这些数据库的固定模式导致了可扩展性和性能问题。 NoSQL 数据存储的使用变得越来越流行,因为它们能够解决与传统数据处理方法相关的一些挑战。 企业不断处理复杂的数据结构,需要聚合、联接和过滤功能来分析数据。随着非结构化数据的爆炸式增长,越来越多的用例需要连接来自不同来源的数据以进行数据分析。 虽然连接主要是一个 SQL 概念,但它们在 NoSQL 世界中也同样重要。 Elasticsearch 不支持 SQL 样式的连接作为一等公民。本文将讨论如何使用各种技术(例如非规范化、应用程序端联接、嵌套文档和父子关系)在 Elasticsearch 中定义关系。它还将探讨与每种方法相关的用例和挑战。 如何处理 Elasticsearch 中的关系 由于 Elasticsearch 不是关系数据库,因此联接不像 SQL 数据库那样作为本机功能存在。它更关注搜索效率而不是存储效率。存储的数据实际上被展平或非规范化,以驱动快速搜索用例。 在 Elasticsearch 中定义关系的方法有多种。根据您的用例,您可以在 Elasticsearch 中选择以下技术之一来对数据进行建模: 一对一关系:对象映射 一对多关系:嵌套文档和父子模型 多对多关系:非规范化和应用程序端连接 一对一的对象映射很简单,这里不再讨论。本博客的其余部分将更详细地介绍其他两种场景。 在 Elasticsearch 中管理您的数据模型 在 Elasticsearch 中管理数据有四种常见方法: 非规范化 应用程序端连接 嵌套对象 亲子关系 非规范化 非规范化在 Elasticsearch 中提供了最佳的查询搜索性能,因为不需要在查询时连接数据集。每个文档都是独立的并包含所有所需的数据,从而消除了昂贵的连接操作的需要。 通过非规范化,数据在索引时存储在扁平结构中。尽管这会增加文档大小并导致每个文档中存储重复数据。磁盘空间并不是一种昂贵的商品,因此无需担心。 非规范化用例 在使用分布式系统时,必须通过网络连接数据集可能会导致显着的延迟。您可以通过对数据进行非规范化来避免这些昂贵的连接操作。多对多关系可以通过数据扁平化来处理。 数据非规范化的挑战 将数据复制到扁平文档中需要额外的存储空间。 对于本质上是关系型的数据集来说,以扁平化结构管理数据会产生额外的开销。 从编程的角度来看,非规范化需要额外的工程开销。您将需要编写额外的代码来展平存储在多个关系表中的数据并将其映射到 Elasticsearch 中的单个对象。 如果数据经常更改,则对数据进行非规范化并不是一个好主意。在这种情况下,当数据的任何子集发生更改时,非规范化将需要更新所有文档,因此应该避免。 对于扁平数据集,索引操作需要更长的时间,因为要对更多数据建立索引。如果您的数据频繁更改,则表明您的索引率较高,这可能会导致集群性能问题。 应用程序端连接 当需要维护文档之间的关系时,可以使用应用程序端联接。数据存储在单独的索引中,并且可以在查询期间从应用程序端执行连接操作。然而,这确实需要在搜索时从应用程序运行额外的查询来加入文档。 应用程序端连接的用例 应用程序端连接确保数据保持规范化。修改在一处完成,无需不断更新您的文档。通过这种方法可以最大限度地减少数据冗余。当文档较少且数据更改不太频繁时,此方法效果很好。 应用程序端连接的挑战 应用程序需要执行多个查询来在搜索时连接文档。如果数据集有许多消费者,您将需要多次执行同一组查询,这可能会导致性能问题。因此,这种方法并没有发挥 Elasticsearch 的真正威力。 这种方法导致实现层面的复杂性。它需要在应用程序级别编写额外的代码来实现联接操作以建立文档之间的关系。 嵌套对象 如果需要维护数组中每个对象的关系,可以使用嵌套方法。嵌套文档在内部存储为单独的 Lucene 文档,并且可以在查询时连接。它们是索引时连接,其中多个 Lucene 文档存储在单个块中。从应用程序的角度来看,该块看起来就像一个单一的 Elasticsearch 文档。因此,查询相对较快,因为所有数据都驻留在同一个对象中。嵌套文档处理一对多关系。 嵌套文档的用例 当文档包含对象数组时,首选创建嵌套文档。下面的图 1 显示了 Elasticsearch 中的嵌套类型如何允许对象数组作为单独的 Lucene 文档进行内部索引。 Lucene 没有内部对象的概念,因此了解 Elasticsearch 如何在内部将原始文档转换为扁平的多值字段是很有趣的。 使用嵌套查询的优点之一是它不会进行跨对象匹配,因此可以避免意外的匹配结果。它能够识别对象边界,从而使搜索更加准确。 图 1:使用嵌套方法在 Elasticsearch 中作为单独 Lucene 文档进行内部索引的对象数组 嵌套对象的挑战 必须完全重新索引根对象及其嵌套对象才能添加/更新/删除嵌套对象。换句话说,子记录更新将导致整个文档重新索引。 无法直接访问嵌套文档。它们只能通过其相关的根文档访问。 搜索请求返回整个文档,而不是仅返回与搜索查询匹配的嵌套文档。 如果您的数据集经常更改,使用嵌套文档将导致大量更新。 亲子关系 父子关系利用 ,以便将具有关系的对象完全分离到单独的文档(父文档和子文档)中。这使您能够将文档以关系结构存储在可单独更新的单独 Elasticsearch 文档中。 连接数据类型 当文档需要经常更新时,亲子关系是有益的。因此,这种方法非常适合数据频繁变化的场景。基本上,您将基本文档分离为包含父文档和子文档的多个文档。这允许父文档和子文档彼此独立地索引/更新/删除。 在父文档和子文档中搜索 为了优化Elasticsearch在索引和搜索过程中的性能,一般建议是确保文档大小不大。您可以利用父子模型将文档分解为单独的文档。 然而,实施这一点存在一些挑战。父文档和子文档需要路由到同一个分片,以便在查询期间将它们连接到内存中并且高效。父 ID 需要用作子文档的路由值。 字段为 Elasticsearch 提供父文档的 ID 和类型,从而在内部将子文档路由到与父文档相同的分片。 _parent Elasticsearch 允许您从复杂的 JSON 对象中进行搜索。然而,这需要彻底了解数据结构才能有效地查询。父子模型利用多个过滤器来简化搜索功能: 查询 has_child 返回具有与查询匹配的子文档的父文档。 查询 has_parent 接受父文档并返回关联父文档已匹配的子文档。 查询 inner_hits 从 查询中获取相关子项信息。 has_child 图 2 显示了如何使用父子模型来演示一对多关系。可以添加/删除/更新子文档而不影响父文档。这同样适用于父文档,无需重新索引子文档即可更新父文档。 图 2:一对多关系的父子模型 亲子关系的挑战 由于连接操作,查询更加昂贵并且占用内存。 父子构造会产生开销,因为它们是单独的文档,必须在查询时连接。 需要确保父分片及其所有子分片存在于同一个分片上。 存储具有父子关系的文档涉及实现复杂性。 结论 选择正确的 Elasticsearch 设计对于应用程序性能和可维护性至关重要。在 Elasticsearch 中设计数据模型时,请务必注意本文讨论的四种建模方法的各种优缺点。 数据建模 在本文中,我们探讨了嵌套对象和父子关系如何在 Elasticsearch 中实现类似 SQL 的联接操作。您还可以在应用程序中实现自定义逻辑来处理与应用程序端联接的关系。对于需要在 Elasticsearch 中连接多个数据集的用例,您可以将这两个数据集提取并加载到 Elasticsearch 索引中以实现高性能查询。 Elasticsearch 开箱即用,没有 SQL 数据库中的联接。尽管有一些潜在的解决方法可以在文档中建立关系,但重要的是要了解每种方法所带来的挑战。 将本机 SQL 连接与 Rockset 结合使用 当需要组合多个数据集进行实时分析时,提供本机 SQL 连接的数据库可以更好地处理这种用例。与 Elasticsearch 一样,Rockset 用作数据库、事件流和数据湖数据的索引层,允许从这些来源进行无模式摄取。与 Elasticsearch 不同,Rockset 提供 能力(包括联接),让您可以更灵活地使用数据。 使用全功能 SQL 进行查询的 也发布 。 在这里