|NO.Z.00008|——————————|BigDataEnd|——|Hadoop&OLAP_Kudu.V08|——|kudu.v08|表设计.V1|
一、kudu表设计(扩展)
### --- 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
### --- Handling Insertions
~~~ 一个RowSet存储在内存中,它被称为MemRowSet,一个tablet中只有一个MemRowSet。
~~~ MemRowSet是一个inmemory的B-Tree树,且按照表的主键排序。
~~~ 所有的insert直接写入进MemRowSet。
~~~ 受益于MVCC(Multi-Version Concurrency Control 多版本并发控制,下文中会讲述),
~~~ 一旦数据写入到MemRowSet,后续的reader能立马查询到。
~~~ 注意:不同于BigTable,Kudu只有插入和插入与flush前的mutation才会被记录到MemRowSet。
~~~ mutation例如基于磁盘数据的update、deletion,下文会有介绍。
~~~ 任何一条数据都以entry的形式精确的存在于一个MemRowSet中,
~~~ entry由一个特殊的header和实际的row data内容组成。
~~~ 由于MemRowSet只存于内存中,最终会被写满,
~~~ 然后Flush到磁盘里(一个或者多个DiskRowSet中)。(下文会详细介绍)
三、MVCC overview
### --- 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的MvccManager能保证在这个tablet中timestamp是唯一的不重复的。
~~~ MvccManager决定了数据提交的timestamp,
~~~ 从而这个时间点后的查询都可以获取到刚刚提交的数据。
~~~ 查询在被创建的时候,scanner提取了一个MvccManager时间状态的快照,
~~~ 所有对于这个scanner可见的数据都会跟这个MvccSnapshot比较,
~~~ 从而决定到底是哪个insertion、update或者detete操作后的数据可见。
~~~ 每个tablet的Timestamp都是单调递增的。
~~~ 我们使用HybridTime技术来创建时间戳,它能保证节点之间的时间戳一致。
~~~ 为了支持快照和历史快照功能,多个版本的数据必须被存储。
~~~ 为了防止空间无限扩展,用户可以配置一个保留时间,
~~~ 并将这个时间之前的记录GC(这个功能可以防止每次查询都从最原始版本开始读取)。
四、MVCC Mutations in MemRowSet
### --- 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:
~~~ 如果mutation的timestamp在MVCC snapshot里,在内存的缓存中执行这个更新。
~~~ 如果不在,则跳过此mutation。
~~~ 如果mutation是一个DELETE操作,则在buffer中标记为已经被删除了,
~~~ 并清空之前加载缓存里的数据。
~~~ # 注意,mutation可以是如下的任何一种:
~~~ UPDATE:更新value,一行数据里的一列或者多列 DELETE: 删除一行数据 REINSERT:
~~~ 重新插入一行数据(这种情况只在之前有一个DELETE mutation且数据在MemRowSet里时发生。)
~~~ 举个真实例子,表结构(key STRING, valUINT32),
~~~ # 经过如下操作:
~~~ 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 Flushes
~~~ 当MemRowSet满了,会触发Flush操作,它会持续将数据写入disk。
~~~ 数据flush到disk成了CFiles文件(参见src/kudu/cfile/README)。
~~~ 数据里的每行都通过一个有序的rowid标识了,
~~~ 而且这个rowid在DiskRowSet中是密集的、不可变的、唯一的。
~~~ 举个例子,如果一个给定的DiskRowSet包含有5行数据,
~~~ 那么它们会以key上升的顺序被分配为rowid0~4。不同的DiskRowSet,会有不同的行(rows),
~~~ 但却可能有相同rowid。
~~~ 读取时,系统会使用一个索引结构,把用户可见的主键key和系统内部的rowid映射起来。
~~~ 上述例子中的主键是一个简单的key,它的结构嵌入在主键列的cfile里。
~~~ 另外,一个独立的index cfile保存了编码后的组合key,使用了提供了类似的方法。(不懂)
~~~ 注意:rowid不是精确的跟每行数据的data存在一起,
~~~ 而是在这个cfile里根据数据有序的index的一个隐式识别。
~~~ 在一部分源码中,将rowid定义为 “row indexes” 或者 “ordinal indexes”。
~~~ 注意:其他系统,例如C-Store把MemRowSet称为”write optimized store” (WOS),
~~~ 把DiskRowSet称为”readoptimizedstore” (ROS)。
六、Historical MVCC in DiskRowSets

### --- HistoricalMVCCinDiskRowSets
~~~ 为了让on-disk data具备MVCC功能,每个on-disk的Rowset不仅仅包含当前版本row的data,
~~~ 还包含UNDO的记录,如此,可以获取这行数据的历史版本。
~~~ 当用户想读取flush后最新版本的数据时,只需要获取base data。
~~~ 因为base data是列式存储的,这种查询性能是非常高的。
~~~ 如果不是读取最新数据,而是time-travel查询,就得回滚到指定历史时间的一个版本,
~~~ 此时就需要借助UNDO record数据。


### --- 当一个查询拿到一条数据,它处理MVCC信息的流程是:
~~~ 读取base data 循环每条UNDO record:
~~~ 如果相关的操作timestamp还未提交,则执行回滚操作。
~~~ 即查询指定的快照timestamp小于mutation的timestamp,mutation还未发生。
~~~ # 举个例子,回顾一下之前MVCC Mutations in MemRowSet章节例子的一系列操作:
~~~ 当这条数据flush进磁盘,它将会被存成如下形式:
~~~ 每条UNDO record是执行处理的反面。
~~~ 例如在UNDO record里,第一条INSERT事务会被转化成DELETE。
~~~ UNDOrecod旨在保留插入或者更新数据的时间戳:
~~~ 查询的MVCC快照指定的时间早于Tx1时,Tx1还未提交,
~~~ 此时将会执行DELETE操作,那么这时这条数据是不存在的。

### --- 再举两个不同查询的例子:
~~~ 每个例子都处理了正确时间的UNDO record,以产生正确的数据。
~~~ 最常见的场景是查询最新的数据。
~~~ 此时,我们需要优化查询策略,避免处理所有的UNDO records。
~~~ 为了达到这个目标,我们引入文件级别的元数据,指向UNDO record的数据范围。
~~~ 如果查询的MVCC快照符合的所有事务都已经提交了(查询最新的数据),
~~~ 这组deltas就会短路(不处理UNDO record),这时查询将没有MVCC开销。
Walter Savage Landor:strove with none,for none was worth my strife.Nature I loved and, next to Nature, Art:I warm'd both hands before the fire of life.It sinks, and I am ready to depart
——W.S.Landor
分类:
bdv022-kudu
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」