Kudu 笔记一

第一部分 概述

基于 HDFS 的存储技术,比如 Parquet ,具有高吞吐量连续读取数据的能力;而 HBase 和Cassandra等技术适用于低延迟的随机读写场景,那么有没有一种技术可以同时具备这两种优点呢? Kudu提供了一种“happy medium”的选择:
 

数据模型

KUDU 的数据模型与传统的关系型数据库类似,一个 KUDU 集群由多个 组成,每个表由多个 字段 组成,一个表必须指定一个由若干个(>=1 )字段组成的 主键 ,如下图:
Kudu 更像关系型数据库,而不是像 HBase Cassandra MongoDB 这些 NoSQL 数据库。不过Kudu 目前还不能像关系型数据一样支持二级索引。 Kudu使用确定的列类型,字段是强类型的 ,而不是类似于NoSQL “everything is byte” 。这可以带来三点好处:
  • 确定的列类型使Kudu可以进行类型特有的编码,节省空间。
  • 可以提供 SQL-like 元数据给其他上层查询工具,比如BI工具。
  • KUDU 的使用场景是 OLAP 分析,有一个数据类型对下游的分析工具也更加友好
Kudu 目前还不支持多行事务。 而在读操作方面,Kudu 只提供了 Scan 操作来获取数据。用户可以通过指定过滤条件来获取自己想要读取的数据,但目前只提供了两种类型的过滤条件:主键范围和列值与常数的比较。
 

一致性模型

Kudu 为用户提供了两种一致性模型。默认的一致性模型是 snapshot consistency 。这种一致性模型保证用户每次读取出来的都是一个可用的快照,但这种一致性模型只能保证单个client 可以看到最新的数据,但 不能保证多个client每次取出的都是最新的数据
另一种一致性模型 external consistency 可以在多个 client 之间保证每次取到的都是最新数据,但是Kudu没有提供默认的实现,需要用户做一些额外工作。
为了实现 external consistency Kudu提供了两种方式:
  • 在 clients 之间显示的传递时间戳。当写入一条数据之后,用户要求 client 去拿一个时间戳作为 token,然后通过一个 external channel 的方式传递给另一个 client。然后另一个 client 就可以通过这个 token 去读取数据,这样就一定能保证读取到最新的数据了。不过这个方法实在是有点复杂。

  • 提供类似 Spanner 的 commit-wait 机制。当写入一条数据之后,client 需要等待一段时间来确定写入成功。Kudu 并没有采用 Spanner TrueTime 的方案,而是使用了 HybridTime 的方案。HybridTime 依赖 NTP,这个可能导致 wait 的时间很长,但 Kudu 认为未来随着 read-time clock 的完善,这应该不是问题了。

第二部分 Kudu的架构

HDFS HBase 相似, Kudu 使用单个的 Master 节点,用来管理集群的元数据,并且使用任意数量的Tablet Server节点用来存储实际数据。可以部署多个 Master 节点来提高容错性。

Master

 
Kudu master 节点负责整个集群的元数据管理和服务协调。它承担着以下功能:
  • 作为catalog managermaster节点管理着集群中所有tabletabletschema及一些其他的元数据。
  • 作为cluster coordinatormaster节点追踪着所有server节点是否存活,并且当server节点挂掉后协调数据的重新分布。
  • 作为tablet directorymaster跟踪每个tablet的位置。

Catalog Manager

Kudu master 节点会持有一个 单tablet的table——catalog table ,但是用户是不能直接访问的。master将内部的 catalog 信息写入该 tablet ,并且将整个 catalog 的信息缓存到内存中。随着现在商用服 务器上的内存越来越大,并且元数据信息占用的空间其实并不大,所以master 不容易存在性能瓶颈。 catalog table保存了所有table的schema的版本以及table的状态 (创建、运行、删除等)。

Cluster Coordination

Kudu 集群中的每个 tablet server 都需要配置 master 的主机名列表。 当集群启动时,tablet server会向master注册,并发送所有tablet的信息 tablet server 第一次向 master 发送信息时会发送所有 tablet 的全量信息,后续每次发送则只会发送增量信息,仅包含新创建、删除或修改的tablet 的信息。 作为cluster coordination, master 只是集群状态的观察者。对于 tablet server tablet 的副本位置、 Raft 配置和schema 版本等信息的控制和修改由 tablet server 自身完成。 master只需要下发命令,tablet server执行 成功后会自动上报处理的结果。

Tablet Directory

因为 master 上缓存了集群的元数据, 所以client读写数据的时候,肯定是要通过master才能获取到tablet的位置等信息 。但是如果每次读写都要通过 master 节点的话,那 master 就会变成这个集群的性能瓶颈,所以client 会在本地缓存一份它需要访问的 tablet 的位置信息,这样就不用每次读写都从 master中获取。 因为tablet 的位置可能也会发生变化(比如某个 tablet server 节点 crash 掉了),所以 当tablet的位置发生变化的时候,client会收到相应的通知,然后再去master上获取一份新的元数据信息

Table

在数据存储方面, Kudu 选择完全由自己实现,而没有借助于已有的开源方案。 tablet 存储主要想要实现的目标为:
  • 快速的列扫描
  • 低延迟的随机读写
  • 一致性的性能

RowSets

Kudu 中, tablet 被细分为更小的单元,叫做 RowSets 。一些 RowSet 仅存在于内存中,被称为MemRowSets,而另一些则同时使用内存和硬盘,被称为 DiskRowSets 任何一行未被删除的数据都只能存在于一个RowSet中 。 无论任何时候,一个 tablet 仅有一个 MemRowSet 用来保存最新插入的数据,并且有一个后台线程会定期把内存中的数据flush 到硬盘上。 当一个 MemRowSet flush 到硬盘上以后,一个新的MemRowSet 会替代它。而 原有的MemRowSet会变成一到多个DiskRowSet flush 操作是完全同步进行的,在进行flush 时, client 同样可以进行读写操作。

MemRowSet
MemRowSets 是一个可以被并发访问并进行过锁优化的 B-tree ,主要是基于 MassTree 来设计的,但存在几点不同:
  • Kudu并不支持直接删除操作,由于使用了MVCC,所以在Kudu中删除操作其实是插入一条标志着删除的数据,这样就可以推迟删除操作
  • 类似删除操作,Kudu也不支持原地更新操作。
  • 将treeleaf链接起来,就像B+-tree。这一步关键的操作可以明显地提升scan操作的性能。
  • 没有实现字典树(trie树),而是只用了单个tree,因为Kudu并不适用于极高的随机读写的场景。
Kudu 中其他模块中的数据结构不同, MemRowSet中的数据使用行式存储 。因为数据都在内存中,所以性能也是可以接受的,而且Kudu 对在 MemRowSet 中的数据结构进行了一定的优化。
 
DiskRowSet
MemRowSet flush 到硬盘上,就变成了 DiskRowSet 。当 MemRowSet flush 到硬盘的时候,每32M就会形成一个新的 DiskRowSet ,这主要是为了保证每个 DiskRowSet 不会太大,便于后续的增量 compaction 操作。 Kudu 通过将数据分为 base data delta data ,来实现数据的更新操作。 Kudu会将数据按列存储,数据被切分成多个page ,并使用 B-tree 进行索引。除了用户写入的数据, Kudu 还会将主键索引存入一个列中,并且提供布隆过滤器来进行高效查找。
 
Compaction
为了提高查询性能, Kudu 会定期进行 compaction 操作,合并 delta data base data ,对标记了删除的数据进行删除,并且会合并一些DiskRowSet
 
分区
选择分区策略需要理解 数据模型 表的预期工作负载 :
  • 对于写量大的工作负载,重要的是要设计分区,使写分散在各个tablet上,以避免单个tablet超载。
  • 对于涉及许多短扫描的工作负载(其中联系远程服务器的开销占主导地位),如果扫描的所有数据都位于同一块tablet上,则可以提高性能。
没有默认分区
在创建表时, Kudu 不提供默认的分区策略。 建议预期具有繁重读写工作负载的新表至少拥有与tablet服务器相同的tablet
和许多分布式存储系统一样, Kudu table 是水平分区的。 BigTable 只提供了 range 分区, Cassandra 只提供hash 分区,而 Kudu 同时提供了这两种分区方式,使分区较为灵活。当用户创建一个 table 时,可以同时指定table 的的 partition schema partition schema会将primary key映射为partition key 。一个partition schema包括 0 到多个 hash-partitioning 规则和一个 range-partitioning 规则。通过灵活地组合各种partition 规则,用户可以创造适用于自己业务场景的分区方式。
 

第三部分 kudu表设计

Tablet是kudu表的水平分区 ,类似于 google Bigtable tablet ,或者 HBase region 。每个 tablet 存储 着一定连续range 的数据( key ),且 tablet 两两间的 range 不会重叠。一张表的所有 tablet 包含了这张表的所有key 空间。
Tablet RowSet 组成, RowSet 由一组 rows 组成( n 条数据、 n 行数据)。 RowSet 是不相交的,即不同的RowSet 间的 row 不会交叉,因此一条给定的数据,只会存在于一个 RowSet 中。虽然 Rowset 是不相交的,但是两两间的key 空间是可以相交的( key range )。

Handling Insertions

一个 RowSet 存储在内存中,它被称为 MemRowSet 一个tablet中只有一个MemRowSet 。 MemRowSet是一个 in-memory B-Tree 树,且按照表的主键排序。所有的 insert 直接写入进MemRowSet。受益于 MVCC Multi-Version Concurrency Control 多版本并发控制,下文中会讲述),一旦数据写入到MemRowSet ,后续的 reader 能立马查询到。
注意:不同于 BigTable Kudu 只有插入与 flush前的mutation才会被记录到MemRowSet。 mutation例如基于磁盘数据的update、delete ,下文会有介绍。
任何一条数据都以 entry 的形式精确的存在于一个 MemRowSet 中, entry由一个特殊的header和实际的row data内容组成 。由于 MemRowSet 只存于内存中,最终会被写满,然后 Flush 到磁盘里(一个或者多个DiskRowSet 中)。(下文会详细介绍)

MVCC overview

Kudu 为了提供一些有用的特性,使用多版本并发控制:
Snapshot scanner :快照查询,当创建了一个查询,系统会操作 tablet 指定时间的快照( point-in-time)。 在这个查询过程中的任何针对这个tablet的update都会被忽略 。另外,指定时间的快照 (point-in-time )可以被存储并在其他的查询中重复使用,例如,一个应用对一组连续的数据进行多次交互执行分析查询。
Time-travel scanners :历史快照查询,与上边的快照查询一样。用户可以指定历史的一个时间点创建一个查询,MVCC 可以保证历史快照的一致性。这个功能可以被用来在某个时间点上的一致性备份。
Change-history queries :历史变更查询,给定两个 MVCC 快照,用户可以查询这两个快照间任务数据。这个功能可以用来做增量备份,跨集群同步,或者离线审计分析。
Multi-row atomic updates within a tablet tablet 内多行数据的原子更新,在一个 tablet 里,一个操作 (mutation )可以修改多行数据,而且在一条数据里的原子操作里是可见的。(应该是针对 column 的原子操作)
为了提供 MVCC 功能,每个操作( mutation )会带有一个时间戳( timestamp )。 Timestamp 是由 TS-wide Clock实例提供的, tablet Mvcc Manager 能保证在这个 tablet timestamp 是唯一的不重复的。 Mvcc Manager决定了数据提交的 timestamp ,从而这个时间点后的查询都可以获取到刚刚提交的数据。 查询在被创建的时候,scanner提取了一个MvccManager时间状态的快照,所有对于这个scanner可见的数据都会跟这个MvccSnapshot比较,从而决定到底是哪个insertion、update或者detete操作后的数据可见
每个 tablet Timestamp 都是单调递增的。使用 HybridTime 技术来创建时间戳,它能保证节点之 间的时间戳一致。
为了支持快照和历史快照功能,多个版本的数据必须被存储。为了防止空间无限扩展,用户可以配置一个保留时间,并将这个时间之前的记录GC (这个功能可以防止每次查询都从最原始版本开始读取)。

MVCC Mutations in MemRowSet

为了在 MemRowSet 中支持 MVCC 功能,每行插入的数据都会带着时间戳。而且, row 会有一个指针,它指向紧随其后的mutations 列表,每个 mutation 都有带有 timestamp
 
在传统的关系型数据库术语里,这个有序的 mutations 列表可以被称作 “RODO log”
任何 reader 需要访问 MemRowSet row 中的 mutations ,才能得到正确的快照。逻辑如下:
如果这行数据插入时的 timestamp ,不在 scanner MVCC snapshot 里(即 scanner 快照指定的timestamp小于数据插入的时间戳,数据还没创建),忽略该行。如上如果不满足,将这行数据放入output 缓存里。
循环 list 里的 mutation
  1. 如果mutationtimestampMVCC snapshot里,在内存的缓存中执行这个更新。如果不在,则跳过此mutation
  2. 如果mutation是一个DELETE操作,则在buffer中标记为已经被删除了,并清空之前加载缓存里的数据。
注意, mutation 可以是如下的任何一种:
  • UPDATE:更新value,一行数据里的一列或者多列
  • DELETE: 删除一行数据
  • REINSERT:重新插入一行数据(这种情况只在之前有一个DELETE mutation且数据在MemRowSet里时发生。)
举个真实例子,表结构 (key STRING, val UINT32) ,经过如下操作:
INSERT INTO t VALUES (“row”, 1); [timestamp 1]
UPDATE t SET val = 2 WHERE key = “row”; [timestamp 2]
DELETE FROM t WHERE key = “row”; [timestamp 3]
INSERT INTO t VALUES (“row”, 3); [timestamp 4]
MemRowSet 中,会有如下结构:
注意,当更新过于频繁时,会有如下的影响:
readers需要追踪linked list指针,导致生成很多CPU cache任务。更新需要追加到linked list的末尾,导致每次更新的时间复杂度是O(n)
考虑到如上低效率的操作,我们给出如下假设:
Kudu 适用于相对低频率更新的场景,即假设数据不会过于频繁的更新。
整个数据中,只有一小部分存于 MemRowSet 中:一旦 MemRowSet 达到一定阈值,它会被 flush 到disk。因此即使 MemRowSet mutation 会导致性能低,也只是占用整体查询时间的一小部分。

MemRowSet Flushes

MemRowSet 满了,会触发 Flush 操作,它会持续将数据写入 disk。
 

数据flushdisk成了CFiles文件(参见src/kudu/cfifile/README)。数据里的每行都通过一个有序的rowid标识了,而且这个rowidDiskRowSet中是密集的、不可变的、唯一的。举个例子,如果一个给定的DiskRowSet包含有5行数据,那么它们会以key上升的顺序被分配为rowid0~4。不同的DiskRowSet,会有不同的行(rows),但却可能有相同rowid。
读取时,系统会使用一个索引结构,把用户可见的主键key和系统内部的rowid映射起来
注意:rowid不是精确的跟每行数据的data存在一起,而是在这个cfifile里根据数据有序的index的一个隐式识别。在一部分源码中,将rowid定义为 “row indexes” 或者 “ordinal indexes”

Historical MVCC in DiskRowSets

为了让 on-disk data 具备 MVCC 功能,每个 on-disk Rowset 不仅仅包含当前版本 row data ,还包含UNDO的记录,如此,可以获取这行数据的历史版本
 
当用户想读取 flush 后最新版本的数据时,只需要获取 base data 。因为 base data 是列式存储的,这种查询性能是非常高的。如果不是读取最新数据,而是time-travel 查询,就得回滚到指定历史时间的一个版本,此时就需要借助UNDO record 数据。
当一个查询拿到一条数据,它处理 MVCC 信息的流程是:
  1. 读取base data
  2. 循环每条UNDO record:如果相关的操作timestamp还未提交,则执行回滚操作。即查询指定的快照timestamp小于mutationtimestampmutation还未发生。
举个例子,回顾一下之前 MVCC Mutations in MemRowSet 章节例子的一系列操作:
当这条数据 flush 进磁盘,它将会被存成如下形式:
每条 UNDO record 是执行处理的反面。例如在 UNDO record 里,第一条 INSERT 事务会被转化成DELETE。 UNDO recod 旨在保留插入或者更新数据的时间戳:查询的 MVCC 快照指定的时间早于 Tx1时,Tx1 还未提交,此时将会执行 DELETE 操作,那么这时这条数据是不存在的。
最常见的场景是查询最新的数据。此时,需要优化查询策略,避免处理所有的UNDO records。 为了达到这个目标,我们引入文件级别的元数据,指向UNDO record 的数据范围。如果查询的 MVCC 快照符合的所有事务都已经提交了(查询最新的数据),这组deltas 就会短路(不处理 UNDO record ),这时查询将没有MVCC 开销。

Handling mutations against on-disk files

更新或者删除已经 flush disk 的数据,不会操作 MemRowSet 。它的处理过程是这样的 : 为了确定update/delete的 key 在哪个 RowSet 里,系统将巡视所有 RowSet 。这个处理首先使用一个区间 tree ,去定位一组可能含有这key RowSet 。然后,使用 boom fifilter 判断所有候选 RowSet 是否含有此 key 。如果某一些RowSet 同时通过了如上两个 check ,系统将在这些 RowSet 里寻找主键对应的 rowid
一旦确定了数据所在的 RowSet mutation 将拿到主键对应的 rowid ,然后 mutation 会被写入到一个称为 DeltaMemStore的内存结构 中。
一个 DiskRowSet 里就一个 DeltaMemStore DeltaMemStore 是一个并行 BTree BTree key 是使用rowid和 mutation timestamp 混合成的。查询时,符合条件的 mutation 被执行后得到快照 timestamp对应数据,执行方式与新数据插入后的mutation 类似( MemRowSet )。
当DeltaMemStore存入的数据很大后,同样也会执行flush到disk,落地为DeltaFile文件
DeltaFile 的信息类型与 DeltaMemStore 是一致的,只是被压实和序列化在密集型的磁盘里。为了 把数据从base data更新成最新的数据,查询时需要执行这些DeltaFile里的mutation事务 ,这些 DeltaFile 集合称作REDO 文件,而 fifile 里这些 mutation 称作 REDO record 。与存于 MemRowSet 里的 mutation 类似,当读取比base data 更新版本的数据时,它们需要被一次应用(执行)。
一条数据的 delta 信息可能包含在多个 DeltaFile 文件,这种情况下, DeltaFile 是有序的,后边的变更会优先于前边的变更。
注意 mutation 存储结构没必要包含整行的数据。如果在一行中,仅仅只有一列数据被更新,那么mutation结构只会包含这一列的更新信息。不读取或者重写无关的列,这样更新数据操作就快而有效率。

Summary of delta file processing

总结一下,每个 DiskRowSet 逻辑上分三部分:
  • Base dataMemRowSet flushDiskRowSet时的最新数据,数据是列式存储的。
  • UNDO records:历史数据,用来回滚到Base data之前一些历史版本数据。
  • REDO recordsBase data之后的一些更新数据,可以用来得到最新版本的数据。
  • UNDO record REDO record存储格式是一样的,都称为DeltaFile

Delta Compactions

DeltaFile 里的 mutation 堆积越来越多,读取 RowSet 数据效率就越来越低,最坏情况,读取最新版本数据需要遍历所有REDO record 并与 base data merge 。换一句话说,如果数据被更新了太多次,为了得到最新版本的数据,就需要执行这么多次的mutation
为了提高读取性能, Kudu 在后台将低效率的物理布局转化成更加高效的布局,且转化后具有同样的逻 辑内容。这种转化称为:delta compaction。它的目标如下:
  • 减少delta fifiles数量。RowSet flushdelta files文件越多,为了读取最新版本数据所要读取的独立的delta files就越多。这个工作不适于放在内存中(RAM),因为每次读取都会带有delta file的磁盘寻址,会遭受性能损失。
  • REDO records迁移成UNDO records。如上所述,一个RowSet包含了一个base data,且是按列存储的,往后一段是UNDO records,往前一段是REDO records。大部分查询都是为了获取最新版本的数据,因此我们需要最小化REDO records数量。
  • 回收old UNDO recordsUNDO recods只需要保存用户设定最早时间点后的数据,这个时间之前的UNDO record都可以从磁盘中移除。
注意: BigTable 的设计是 timestamp 绑定在 data 里,没有保留 change 信息( insert update delete ); 而kudu 的设计是 timestamp 绑定在 change 里,而不是 data 。如果历史的 UNDO record 被删除,那么将获取不到某行数据或者某列数据是什么时候插入或者更新的。如果用户需要这个功能,他们需要保存插入或者更新的timestamp 列,就跟传统关系型数据库一样。
 

Types of Delta Compaction

delta campaction minor major 两种。
Minor delta compactoin
Minor compaction 是多个 delta file compaction 不会包含base data,compact生成的也是delta file
Major delta compaction
Major compaction 是对 base data 和任意多个 delta file compact
Major compaction minor compaction 更耗性能,因为它需要读取和重写 base data ,并且 base data比delta data 大很多( 因为base data存了一行数据,而delta data是对某一些column的mutation ,需要注意的base data 是列式存储的, delta data 不是)。
Major compaction 可以对 DiskRowSet 里的任意多个或者一个 column 进行 compact 。如果只有一列数据进行了多次重要的更新,那么compact 可以只针对这一列进行读取和重写。在企业级应用中会经常遇到这种情况,例如更新订单的状态、更新用户的访问量。
两种类型的 compaction 都维护 RowSet 里的 rowid 。因为它们完全在后台执行,且不会带锁。 compact的结果文件会采用原子swapping 的方式被引入进 RowSet Swap操作结束后,compact前的那些老文件将会被删除

Merging compactions

随着越来越多的数据写入 tablet DiskRowSet 数量也会累积的越来越多。如此这般将会降低 kudu 性能:
1. 随机访问(通过主键获取或者更新一条数据),这种情况下,每个 RowSet 只要它的 key 范围包含了这个主键,将各自去定位主键的位置。Boom filter可以缓解一定数量的物理寻址,但是特大的 bloom filter 访问会影响到 CPU ,并且同样会增加内存消耗。
2. 查询一定 key 范围数据(例如查询主键在 A B 之间的数据),此时,每个 RowSet ,只要它的 key 范围与提供的范围重叠,将各自去寻址,不使用bloom filter。专门的索引结构可能会有帮助,但是同样会消耗内存。
3. 排序查询,如果用户要求查询的结果与主键有相同顺序,那么查询结果集必须经过一个 merge 过程。Merge 的消耗通常与输入的数据量成对数级增长,即随着数据量的增大, merge 将越耗性能。
如上所述,我们应该 merge RowSet 以减少 RowSet 的数量:
与如上提到的 Delta Compaction 不同,请注意, merging Compaction不会保持rowid一样 。这使得处理并发的mutation 错综复杂。这个过程在 compaction.txt 文件中有比较详细的描述。

Overall picture

Comparison to BigTable approach

BigTable 不同的设计方式点如下:
1. kudu 中,一个给定的 key 只会存在于一个 tablet RowSet 里。 在BigTable 里,一个 key 可以存在多个不同的 SSTable 里。 BigTable 的一整个 Tablet 类似于 kudu 的RowSet:读取一条数据需要 merge 所有 SSTable 里找到的数据(根据 key ),类似于 Kudu ,读取一条数据需要merge base data和所有 DeltaFile 数据。 Kudu的优势是,读取一条数据或者执行非排序查询,不需要 merge 。例如,聚合一定范围内的key可以独立的查询每个 RowSet( 甚至可以并行的 ) ,然后执行求和,因为 key 的顺序是不重要的, 显然查询的效率更高。 Kudu的劣势是,不像 BigTable insert mutation 是不同的操作: insert 写入数据至MemRowSet,而 mutation delete update )写入存在这条数据的 RowSet DeltaMemStore里。性能影响有一下几点:
a )写入时必须确定这是一条新数据。这会产生一个 bloom filter查询所有 RowSet 。如果布隆过滤器得到一个可能的 match (即计算出可能在一个 RowSet里),接着为了确定是否是insert 还是 update ,一个寻址就必须被执行。 假设,只要RowSet 足够小, bloom filter的结果就会足够精确,那么大部分插入将不需要物理磁盘寻址。另外,如果插入的 key 是有序
的,例如 timeseries+“_”+xxx ,由于频繁使用, key 所在的 block 可能会被保存在数据块缓存中。
b Update 时,需要确定 key 在哪个 RowSet 。与上雷同,需要执行 bloom filter 。 这有点类似于关系型数据库RDBMS ,当插入一条主键存在的数据时会报错,且不会更新这条数
据。类似的,更新一条数据时,如果这条数据不存在也会报错。 BigTable 的语法却不是这样。
2. Mutation 操作磁盘数据,是通过 rowid 的,而不是实际意义上的 key 。 BigTable中,同一个主键数据是可以存在多个 SSTable 里的,为了让 mutation 和磁盘的存的 key 组合在一起,BigTable 需要基于 rowkey 执行 merge Rowkey 可以是任意长度的字符串,因此对比rowkey是非常耗性能的。另外,在一个查询中,即使 key 列没有被使用(例如聚合计算),它们也要被读取出来,这导致了额外的IO 。复合主键在 BigTable 应用中很常见,主键的大小可能比你关注的列大一个数量级,特别是查询的列被压缩的情况下。 相比之下,kudu mutation 是与 rowid 绑定的。所以 merge 会更加高效,通过维护计数器的方式:给定下一个需要保存的mutation ,我们可以简单的相减,就可以得到从 base data到当前版本有多少个 mutation 。或者,直接寻址可以用来高效的获取最新版本的数据。 另外,如果在查询中没有指定key ,那执行计划就不会查阅 key ,除了需要确定 key 边界情况。 举
例: 如上表的主键是 (host,unitx_time) ,在 kudu 里的执行伪代码如下:
sum = 0 foreach RowSet:
start_rowid = rowset.lookup_key(1349658729)
end_rowid = rowset.lookup_key(1352250720)
iter = rowset.new_iterator(“cpu_usage”) iter.seek(start_rowid)
remaining = end_rowid - start_rowid while remaining > 0:
block = iter.fetch_upto(remaining) sum += sum(block)。
获取 block 也非常的高效,因为 mutation 直接指向了 block 的索引地址。
3. timgstamp 不是数据模型里的一部分。 BigTable-like的系统中,每个 cell timstamp 都是暴露给用户的,本质上组成了这个 cell 的一个符合主键。意味着,这种方式可以高效的直接访问指定版本的cell ,且它存储了一个 cell 的整个时间序列的所有版本。而Kudu 却不高效(需要执行多个 mutation ),它的 timestamp 是从 MVCC 实现而来的,它不是主键的另外一个描述。 作为替代,kudu 可以使用原生的复合主键来满足时间序列场景,例如主键( host unix_time )。
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值