Kudu的模型设计
翻译自 Kudu官网的一篇介绍,原文点击:【这里】 查看
背景
Kudu 的表跟关系型数据库中的表类似,也有主键,也包括复合主键。
schema设计的好坏对Kudu性能、稳定性都有很大影响
对于建表来说,有三点需要考虑:
- 列的设计
- 主键设计
- 分区设计
一个最佳的schema包括:
- 读和写被均匀的分布到每台机器上;这点受分区的影响
- Tablets以平滑、可预测的速率增长,随时间推移Tablets持续保持稳定;这点也受分区影响
- scan操作时尽可能减少读取的数据量,这点受主键的影响,而分区的裁剪也有影响
列类型
主键必须是非空的,非主键字段可以为空,Kudu的类类型如下:
- boolean
- 8-bit signed integer
- 16-bit signed integer
- 32-bit signed integer
- 64-bit signed integer
- date (32-bit days since the Unix epoch)
- unixtime_micros (64-bit microseconds since the Unix epoch)
- single-precision (32-bit) IEEE-754 floating-point number
- double-precision (64-bit) IEEE-754 floating-point number
- decimal
- varchar
- UTF-8 encoded string (up to 64KB uncompressed)
- binary (up to 64KB uncompressed)
kudu是基于列类型存储的,而且每个列都有具体的类型,相比无类型的存储系统,可以提供更好的序列化和编码
kudu不提供“版本”或时间戳列,需要手动定义版本列、或者时间戳列
decimal 类型
相比float
和double
类型,decimal
更适合金融和数学计算,也可以用于超过int64
位的整数、或者用于带小数的主键
percision
:代表数字的总位数,不关心小数点的具体位置;范围是 1 - 38;比如percision为4
表示最大为9999
,或者99.99
,也可以表示负数如:-9999
scale
:小数部分的位数,范围是[0,percision],如果是0
表示decimal是一个整数值;如果跟percision相等表示所有数字都在小数点后面,如:0.999
Kudu在存储 decimal 类型时,会尽量减少存储空间,所以不要把 percision设置的太大
- Decimal values with precision of 9 or less are stored in 4 bytes
- Decimal values with precision of 10 through 18 are stored in 8 bytes
- Decimal values with precision greater than 18 are stored in 16 bytes
varchar 类型
varchar是UTF-8编码的string类型(最大是64KB),可以设置长度范围是 1 - 65535,超过会被截断;一般数据库的长度是指byte,而Kudu用的是多字节的UTF-8,所以其存储范围会更大一些
列的编码
Column Type | Encoding | Default |
---|---|---|
int8, int16, int32, int64 | plain, bitshuffle, run length | bitshuffle |
date, unixtime_micros | plain, bitshuffle, run length | bitshuffle |
float, double, decimal | plain, bitshuffle | bitshuffle |
bool | plain, run length | run length |
string, varchar, binary | plain, prefix, dictionary | dictionary |
下面介绍这几种编码类型:
- Plain Encoding,以 little-endian 方式存储
- Bitshuffle Encoding,一个值的块被重排列,存储每个值的最有效位,然后是第二有效为,以此类推,采用LZ4压缩,对于重复值效果较好
- Run Length Encoding,只存储值和计数,将连续的重复值压缩到一列中,比如主键排序时
- Dictionary Encoding,每一个列值编码为对应的字典索引
- Prefix Encoding,对多个值共享相同前缀时,或者复合主键的第一列时
Kudu对每个列都可以采用:LZ4
、Snappy
、zlib
压缩,Bitshuffle
天生就使用了LZ4
压缩了,所以不用再设置压缩
一般来说,LZ4
压缩性能最好,zlib
使压缩后的数据最小,
主键
Kudu是按照主键做插入、更新、删除的,这点跟关系型数据库是类似的
避免 backfill 问题
主键是不允许重复的,如果插入了一个重复的主键,会报错
一般来说新插入的数据是一段小范围的,这些数据都在 cache中,因此检查会很快
但如果插入的数据key落在很大的范围上,比如几个月之前的,那就会从磁盘中查找数据,这种情况叫做 backfill
这会大幅度降低性能
对此有如下建议
- 将主键做压缩,32位的随机ID会产生 10亿级别的key,需要32G内存,如果能将其压缩缓存就可避免很多随机I/O
- 使用SSD
- 改变主键结构,使其命中连续的主键范围
分区
为提供可扩展能力,Kudu将表划分为成多个 tablets,tablets分布在多台机器上,一行属于单个tablet,分区是在建表的时候创建的。
对于写来说,要尽可能的将其分散到多台机器上,避免单机瓶颈;对于读要尽量减少读的范围,如本地读取。
Kudu提供了两种分区模式,也可以将他们组合使用
- range分区,key是完全有序的,每个分区是一组有序的key,对于像时间日志这种结构非常适合,可以动态的添加和删除
- hash分区,使用hash将主键分区,对于写负载很有用,hash桶的数量在建表时创建
可以创建多级分区,但是多级hash分区,不能hash到相同的列
对于hash、range分区时会自动进行分区裁剪,对于多级分区同样也会裁剪
分区的例子
SQL如下
|
|
range分区的例子如下:
上图中使用了 time
字段做分区;蓝色部分使用了默认的分区范围,得到三个分区:
- 2015之前
- 2015年
- 2015年之后
上图第二个例子的边界是 [(2014-01-01), (2017-01-01)]
,并在2015-01-01
和2016-01-01
做了split
第二个例子的分区是:
- [(2014-01-01), (2015-01-01)]
- [(2015-01-01), (2016-01-01)]
- [(2016-01-01), (2017-01-01)]
第一个例子是无边界的,第二个例子是有边界的,实际使用来说,第二个例子更好
当然这两个例子对于都会出现写热点问题
hash分区的例子如下,使用了host
和metric
列做分区
hash分区可以将写均匀分散到每台机器上,对于scan来说,可以指定host
和metric
来做分区裁剪
hash分区的问题是,当数据无限增大后,tablet会变得无限大
hash分区 vs range分区
Strategy | Writes | Reads | Tablet Growth |
---|---|---|---|
range(time) | ✗ - all writes go to latest partition | ✓ - time-bounded scans can be pruned | ✓ - new tablets can be added for future time periods |
hash(host, metric) | ✓ - writes are spread evenly among tablets | ✓ - scans on specific hosts and metrics can be pruned | ✗ - tablets could grow too large |
从上面表格可以看出,hash分区对于 写负载很好,而range分区避免了无限增长问题
将 两种分区的优点结合后如下:
这里结合了hash和range两个维度:
- 对于写,因为有hash分区,写被分散到 4 个分区上,避免了热点
- 对于读,time列会做裁剪,加上 host 和 metric也会做裁剪
- 新增时,相当于自动增加了上图中的一列,避免无限增长
Kudu对于一个表可以支持任意级别的hash列,只要这些级别没有共同的哈希列
上图中,按照host
分为4
个buckets,按照metric
分为3
个buckets,一共12
个tablets
相比多个独立列上进行hash分区,这种方式更容易出现热点问题,因为单个host
、metrics
的所有值都属于单个tablets。scan时可以利用host
和mertics
做分区裁剪
其他
使用下列方式修改表结构,多个操作可以将下面操作组合成事务来完成
- rename
- rename主键列
- rename、add、drop非主键列
- add、drop range分区
限制
- 主键不能修改
- 分区不能修改 ,可以删除再创建
- 列类型不能修改