存储附加索引 (SAI) 概念
存储附加索引 (SAI) 是 Cassandra 数据库的高度可扩展、全局分布式索引。
SAI 相对于 Apache Cassandra 的现有索引的主要优势是
-
为 AI 应用程序启用向量搜索
-
在同一表上的多个索引之间共享公共索引数据
-
缓解写入时间可扩展性问题
-
显着减少磁盘使用量
-
出色的数值范围性能
-
索引的零复制流式传输
事实上,SAI 为 Apache Cassandra 提供了最全面的索引功能。SAI 为几乎所有 CQL 数据类型的任何 CQL 表列添加了列级索引。
SAI 支持基于以下内容进行过滤的查询:
-
向量嵌入
-
数值和文本类型的 AND/OR 逻辑
-
数值和文本类型的 IN 逻辑(使用值数组)
-
数值范围
-
非变长数值类型
-
文本类型相等
-
CONTAINs 逻辑(用于集合)
-
标记化数据
-
行感知查询路径
-
区分大小写(可选)
-
Unicode 规范化(可选)
优势
在数据库表中的任何列上定义一个或多个 SAI 索引后,您将能够运行指定索引列的高性能查询。与关系数据库和复杂的索引方案相比,SAI 通过加速您开发应用程序的路径,使您更高效。
SAI 与 Cassandra 的存储引擎深度集成。SAI 功能会对写入的内存中 Memtable 和磁盘上的 SSTable 进行索引,并在读取时解决这些索引之间的差异。因此,SAI 的设计在核心数据库之上几乎没有操作复杂性。从快照创建到模式管理,再到数据过期,SAI 与核心数据库已经提供的功能和机制紧密集成。
SAI 还与零复制流式传输 (ZCS) 完全兼容。因此,当您在集群中引导或停用节点时,索引会与 SSTable 一起完全流式传输,而不会在接收节点端进行序列化或重建。
SAI 的核心是一个过滤引擎,它简化了数据建模和客户端应用程序,否则这些应用程序将严重依赖于维护多个特定于查询的表。
性能
SAI 的性能优于 Apache Cassandra 的任何其他索引方法。
SAI 提供的功能比二级索引 (2i) 多,但磁盘空间使用量却少得多,从而降低了磁盘、基础设施和操作的总拥有成本 (TCO)。对于读取路径性能,SAI 的吞吐量和延迟至少与其他索引方法一样好。
SAI 写入路径和读取路径
SAI 与底层数据库的存储引擎深度集成。SAI 不会抽象地对表进行索引。相反,SAI 会对写入的**Memtable** 和排序字符串表 (**SSTable**) 进行索引,并在读取时解决这些索引之间的差异。每个 Memtable 都是一个特定于特定数据库表的内存中数据结构。Memtable 类似于写入后缓存。每个 SSTable 都是一个不可变的数据文件,数据库会定期将 Memtable 写入该文件。SSTable 按顺序存储在磁盘上,并为每个数据库表维护。
本主题讨论 SAI 读取和写入路径的详细信息,并检查 SAI 索引生命周期。
SAI 写入路径
可以在将任何数据写入 CQL 表之前或之后创建 SAI 索引。作为复习,写入 CQL 表的数据将首先写入 Memtable,然后在数据从 Memtable 刷新后写入 SSTable。创建 SAI 索引后,SAI 会收到对当前 Memtable 的所有变异的通知。与任何其他数据一样,SAI 会更新插入和更新的索引,Apache Cassandra 对这些操作的处理方式完全相同。SAI 还支持分区删除、范围墓碑和行删除。如果查询中执行了删除操作,SAI 会在后过滤步骤中处理索引更改。因此,SAI 在对频繁删除的列进行索引时不会产生任何特殊惩罚。
如果插入或更新包含索引列的有效内容,则该内容将添加到**Memtable 索引**中,并且更新行的主键将与索引值相关联。SAI 会计算新条目增量堆使用量的估计值。此估计值会计入底层 Memtable 的堆使用量。此功能还意味着,随着对表进行索引的列数量增加,Memtable 刷新率也会增加,而刷新的 SSTable 的大小会减小。所有活动 Memtable 索引的总写入次数和估计堆使用量会作为指标公开。请参阅SAI 指标。
Memtable 刷新
当达到刷新阈值时,SAI 会将 Memtable 索引内容直接刷新到磁盘,而不是创建额外的内存中表示形式。这是可能的,因为 Memtable 索引按术语/值排序,然后按主键排序。当刷新发生时,SAI 会为每个索引列写入一个新的 SSTable 索引,因为 SSTable 正在写入。
刷新操作是一个两阶段过程。在第一阶段,行将写入新的 SSTable。对于每行,都会生成一个行 ID,并创建三个索引组件。这些组件是
-
行 ID 到其对应令牌值的磁盘上映射——SAI 支持 Murmur3Partitioner
-
SSTable 分区偏移量
-
主键到其行 ID 的临时映射,该映射将在后续阶段使用
第一和第二组件文件的内容是压缩数组,其序数对应于行 ID。
在第二阶段,在所有行都写入新的 SSTable 并且共享的 SSTable 级索引组件已完成之后,SAI 将开始对每个索引列进行索引工作。具体来说,在第二阶段,SAI 会遍历 Memtable 索引以生成术语及其令牌排序行 ID 的对。此迭代器使用在第一阶段构建的临时映射结构将主键转换为行 ID。然后,术语迭代器(带帖文)将根据每个索引元素是针对字符串列还是数值列传递给单独的写入组件。
在字符串情况下,SAI 索引写入器会遍历每个术语,首先将帖子写入磁盘,然后将这些帖子的偏移量记录在帖子文件中,作为磁盘上字节排序字典树中新条目(针对术语本身)的有效负载。在数值情况下,SAI 将数值索引的写入分为两个步骤
术语将传递给平衡的 kd 树写入器,该写入器会将 kd 树写入磁盘。当树的叶块写入时,它们的帖子会暂时存储在内存中。然后,这些临时帖子将用于在磁盘上构建帖子,在叶节点处以及索引节点的各个级别处。
当列索引刷新完成时,一个特殊的空标记文件将被标记以指示成功。此过程在启动和增量重建操作中用于区分以下情况:
-
SSTable 索引文件对于某个列丢失。
-
没有可索引的数据——例如,当 SSTable 仅包含墓碑时。(墓碑是行中的一个标记,指示某个列已被删除。在压缩期间,标记的列将被删除。)
然后,SAI 会增加该列已刷新 Memtable 索引的计数器,并向直方图添加每秒刷新的单元格数量。
触发压缩时
回想一下,Apache Cassandra 使用压缩来合并 SSTable。压缩会收集每个唯一行的所有版本,并使用来自 SSTable 的每行所有列的最新版本(按时间戳)组装一个完整的行。合并过程是高效的,因为行在每个 SSTable 中按分区键排序,并且合并过程不使用随机 I/O。每行的最新版本将写入新的 SSTable。旧版本以及任何准备删除的行将保留在旧的 SSTable 中,并在完成任何挂起的读取后删除。
对于 SAI,当触发压缩时,每个索引组都会创建一个 SSTable 刷新观察器来协调从其各自的 SSTable 写入器实例中的新压缩数据中写入所有附加列索引的过程。与 Memtable 刷新(其中索引术语已排序)不同,在压缩期间迭代合并数据时,SAI 会缓冲索引值及其行 ID,这些值和 ID 按令牌顺序添加。
为了避免诸如耗尽可用堆资源等问题,SAI 使用累积段缓冲区,该缓冲区通过专有计算同步刷新到磁盘。然后每个段记录一个段行 ID **偏移量**,并且只存储段行 ID(SSTable 行 ID 减去段行 ID 偏移量)。SAI 将段同步刷新到同一个文件中,以避免重写所有段的成本,并降低分区限制查询和分页范围查询的成本,因为它减少了搜索空间。
从每个列索引的帖子到 SSTable 偏移量,再到 SSTable 分区的磁盘布局
实际的段刷新过程与 Memtable 刷新非常相似。但是,缓冲的术语在与它们的帖子一起写入它们各自的特定类型磁盘结构之前会被排序。在对给定索引进行压缩结束时,一个特殊的空标记文件被标记为成功,并且段的数量记录在 SAI 指标中。请参阅 全局索引指标。
当整个压缩任务完成时,SAI 会收到一个 SSTable 列表更改通知,其中包含事务期间添加和删除的 SSTable。SSTable 上下文管理器和索引视图管理器负责原子地将旧的 SSTable 索引替换为新的索引。此时,新的 SSTable 索引可用于查询。
SAI 读取路径
本节解释了索引查询如何由 SAI 协调器处理并由副本执行。与传统二级索引不同,传统二级索引每个查询最多使用一个列索引,SAI 实现了一个 查询计划
,它可以使在单个查询中使用所有可用的列索引成为可能。
SAI 读取操作的总体流程如下
索引选择和协调器处理
当遇到查询时,SAI 协调器执行的第一个操作是识别最具选择性的索引,以利用一个或多个索引。最具选择性的索引是指通过返回估计结果行计算的最低值,最积极地缩小过滤空间和最终结果数量的索引。如果存在多个 SAI 索引(即每个 SAI 索引基于不同的列,但查询涉及多个列),则选择哪个 SAI 索引首先并不重要。
一旦选择了读取操作的最佳索引,该索引就会嵌入到读取命令中,该命令进入分布式范围读取装置。分布式范围读取按令牌顺序查询 Apache Cassandra 集群,进行一轮或多轮查询。SAI 协调器根据本地数据和查询限制估计 **并发因子**(每个范围的行数),以确定要联系的范围数量。对于每一轮,并发因子决定将并行查询多少个副本。
在第一轮开始之前,SAI 通过专有计算估计初始并发因子,此处显示为步骤 1。
一旦建立了初始并发因子,范围读取就开始了。
在步骤 2 中,SAI 协调器根据并发因子并行向所需范围发送请求。在步骤 3 中,SAI 协调器等待来自请求副本的响应。在步骤 4 中,SAI 协调器收集结果并根据返回的行和查询限制重新计算并发因子。
在每一轮结束时,如果未达到限制,则会调整并发因子以考虑已读取结果的形状。如果第一轮没有返回结果,则并发因子会立即增加到剩余令牌范围的最小计算值和并发因子的最大计算值。如果返回结果,则会更新并发因子。SAI 重复步骤 2、3 和 4,直到满足查询限制。
为了避免查询具有失败索引的副本,每个节点通过八卦将自己的本地索引状态传播到对等节点。在协调器处,读取请求将过滤包含请求中使用的不可查询索引的副本。在大多数情况下,第二轮副本查询应该返回所有必要的结果。如果副本周围的结果分布极不平衡,则可能需要更多轮次。
更深入地了解:副本查询规划和视图获取
一旦副本从 SAI 协调器接收到令牌范围读取请求,本地索引查询就开始了。SAI 通过查询计划实现了一个索引搜索器接口,它可以使在单个查询中访问所有可用的 SAI 列索引成为可能。
查询计划通过读取命令对其传递的表达式进行分析。SAI 确定哪些索引应该用于满足给定列上的查询子句。一旦列表达式与索引配对,查询控制器就会获取每个列索引的活动 SSTable 索引的视图。为了避免压缩删除正在进行的查询使用的索引文件,在读取任何索引文件之前,查询控制器会尝试获取对与查询的令牌范围相交的索引文件的 SSTable 的引用,并在读取请求完成后释放它们。
此时,将创建一个令牌流来从每个列索引流式传输匹配项。这些流以及确定它们如何合并的布尔逻辑被封装在一个操作中,该操作被返回到查询计划组件。
SAI 令牌流框架的作用
SAI 查询引擎围绕一个令牌流框架,该框架定义了 SAI 如何异步迭代、跳过和合并来自单个 SSTable 索引和整个列索引的匹配分区的流。SAI 使用令牌来描述 Cassandra 环令牌中分区匹配的容器。
迭代是最简单的三个操作之一。具体来说,帖子的迭代涉及通过块缓存对行 ID 进行顺序磁盘访问,这些行 ID 用于查找环令牌和分区键偏移量信息。
令牌跳过用于在从上一个分页边界继续时或在令牌交集中找到更大的令牌时跳过不匹配的令牌。
匹配流式传输和后过滤示例
考虑一个具有单个列索引(例如 age = 44
)的示例,生成的流是所有 Memtable 索引和所有 SSTable 索引的并集。
-
SAI 通过其各个令牌范围分区实例,按令牌顺序“延迟”地(即一次一个)迭代每个 Memtable 索引。此功能减少了否则从对环末尾数据的无用搜索中产生的开销。
-
磁盘索引:SAI 返回所有匹配的 SSTable 索引的并集。在一个 SSTable 索引中,由于压缩期间的内存限制,可能存在多个段。与 Memtable 索引类似,SAI 按令牌排序顺序延迟搜索段。
当查询中存在多个索引表达式(例如 WHERE age=44 AND state='CA'
)并使用 AND
查询运算符连接时,索引表达式的结果将被交集,这将返回与所有索引表达式匹配的分区键。
在索引搜索之后,SAI 会公开一个分区键流。对于每个分区键,SAI 执行一个分区读取操作,该操作返回给定分区中的行。当行被物化时,SAI 使用过滤器树来应用另一轮过滤。SAI 执行此后续过滤步骤以解决以下问题
-
分区粒度:SAI 会跟踪分区偏移量。在宽分区模式的情况下,并非分区中的所有行都将与索引表达式匹配。
-
墓碑:SAI 不会索引墓碑。索引行可能已被新添加的墓碑遮蔽。
-
非索引表达式:操作可能包含没有索引结构的非索引表达式。
下一步是什么?
请参阅博客文章 更好的 Cassandra 索引,更好的数据模型:介绍存储附加索引。