暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

TIDB 读书笔记 (2)

dba笔记 2018-03-18
1013

三篇文章了解 TiDB 技术内幕——说计算

官网:https://pingcap.com/docs-cn/


读后感

tidb通过实现raft保证数据分布式强一致性,能保证金融行业数据安全。对硬件网络和磁盘要求也更高了。分布式强大的计算能力,理论上可以废弃目前流行的oltp抽取到hadoop,然后计算的方式。具体还要看官方的最佳实践了。


问题

SQL 和 KV 结构之间存在巨大的区别,那么如何能够方便高效地进行映射。


需求

Table存储的数据包括三部分:

1. 表的元信息

2. Table 中的 Row

3. 索引数据

Row,TiDB 面向的首要目标是 OLTP 业务,这类业务需要支持快速地读取、保存、修改、删除一行数据,所以采用行存是比较合适的。

对于 Index,TiDB 不只需要支持 Primary Index,还需要支持 Secondary Index。Index 的作用的辅助查询,提升查询性能,以及保证某些 Constraint。

对于 Insert 语句,需要将 Row 写入 KV,并且建立好索引数据。

对于 Update 语句,需要将 Row 更新的同时,更新索引数据(如果有必要)。

对于 Delete 语句,需要在删除 Row 的同时,将索引也删除。

对于 Select 语句,情况会复杂一些。首先我们需要能够简单快速地读取一行数据,所以每个 Row 需要有一个 ID (显示或隐式的 ID)。其次可能会读取连续多行数据,比如 Select * from user;。最后还有通过索引读取数据的需求,对索引的使用可能是点查或者是范围查询


实现

一个全局有序的分布式 Key-Value 引擎

TiDB 对每个表分配一个 TableID,每一个索引都会分配一个 IndexID,每一行分配一个 RowID(如果表有整数型的 Primary Key,那么会用 Primary Key 的值当做 RowID),其中 TableID 在整个集群内唯一,IndexID/RowID 在表内唯一,这些 ID 都是 int64 类型。

表数据行 Key-Value pair:

Key: tablePrefix_rowPrefix_tableID_rowID Value: [col1, col2, col3, col4]

unique Index  Key-Value pair:

Key: tablePrefix_idxPrefix_tableID_indexID_indexColumnsValue Value: rowID

normal Index :

Key: tablePrefix_idxPrefix_tableID_indexID_ColumnsValue_rowID Value:null


一个 Table 内部所有的 Row 都有相同的前缀,一个 Index 的数据也都有相同的前缀。这样具体相同的前缀的数据,在 TiKV 的 Key 空间内,是排列在一起。同时只要我们小心地设计后缀部分的编码方案,保证编码前和编码后的比较关系不变,那么就可以将 Row 或者 Index 数据有序地保存在 TiKV 中。采用这种编码后,一个表的所有 Row 数据就会按照 RowID 的顺序排列在 TiKV 的 Key 空间中,某一个 Index 的数据也会按照 Index 的 ColumnValue 顺序排列在 Key 空间内。


假设表中有 3 行数据:

1, "TiDB", "SQL Layer", 10

2, "TiKV", "KV Engine", 20

3, "PD", "Manager", 30

那么首先每行数据都会映射为一个 Key-Value pair,注意这个表有一个 Int 类型的 Primary Key,所以 RowID 的值即为这个 Primary Key 的值。假设这个表的 Table ID 为 10,其 Row 的数据为:

t_r_10_1  --> ["TiDB", "SQL Layer", 10]

t_r_10_2 --> ["TiKV", "KV Engine", 20]

t_r_10_3 --> ["PD", "Manager", 30]

除了 Primary Key 之外,这个表还有一个 Index,假设这个 Index 的 ID 为 1,则其数据为:

t_i_10_1_10_1 —> null

t_i_10_1_20_2 --> null

t_i_10_1_30_3 --> null


SQL运算

比如 Select count(*) from user where name="TiDB"; 这样一个语句,我们需要读取表中所有的数据,然后检查 Name 字段是否是 TiDB,如果是的话,则返回这一行。这样一个操作流程转换为 KV 操作流程:

  • 构造出 Key Range:一个表中所有的 RowID 都在 [0, MaxInt64) 这个范围内,那么我们用 0 和 MaxInt64 根据 Row 的 Key 编码规则,就能构造出一个 [StartKey, EndKey) 的左闭右开区间

  • 扫描 Key Range:根据上面构造出的 Key Range,读取 TiKV 中的数据

  • 过滤数据:对于读到的每一行数据,计算 name="TiDB" 这个表达式,如果为真,则向上返回这一行,否则丢弃这一行数据

  • 计算 Count:对符合要求的每一行,累计到 Count 值上面

这个方案肯定是可以 Work 的,但是并不能 Work 的很好,原因是显而易见的:

1. 在扫描数据的时候,每一行都要通过 KV 操作同 TiKV 中读取出来,至少有一次 RPC 开销,如果需要扫描的数据很多,那么这个开销会非常大;

2. 并不是所有的行都有用,如果不满足条件,其实可以不读取出来;

3. 符合要求的行的值并没有什么意义,实际上这里只需要有几行数据这个信息就行。


如何避免上述缺陷也是显而易见的,首先我们需要将计算尽量靠近存储节点,以避免大量的 RPC 调用。其次,我们需要将 Filter 也下推到存储节点进行计算,这样只需要返回有效的行,避免无意义的网络传输。最后,我们可以将聚合函数、GroupBy 也下推到存储节点,进行预聚合,每个节点只需要返回一个 Count 值即可,再由 tidb-server 将 Count 值 Sum 起来。



本文分享自微信公众号 - dba笔记,如有侵权,请联系 service001@enmotech.com 删除。
最后修改时间:2019-12-18 15:44:27
文章转载自dba笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论