翻译自 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 类型
相比floatdouble类型,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对每个列都可以采用:LZ4Snappyzlib压缩,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如下

1
2
3
4
5
6
7
CREATE TABLE metrics (
    host STRING NOT NULL,
    metric STRING NOT NULL,
    time INT64 NOT NULL,
    value DOUBLE NOT NULL,
    PRIMARY KEY (host, metric, time)
);

range分区的例子如下:

上图中使用了 time字段做分区;蓝色部分使用了默认的分区范围,得到三个分区:

  • 2015之前
  • 2015年
  • 2015年之后

上图第二个例子的边界是 [(2014-01-01), (2017-01-01)],并在2015-01-012016-01-01做了split
第二个例子的分区是:

  • [(2014-01-01), (2015-01-01)]
  • [(2015-01-01), (2016-01-01)]
  • [(2016-01-01), (2017-01-01)]

第一个例子是无边界的,第二个例子是有边界的,实际使用来说,第二个例子更好
当然这两个例子对于都会出现写热点问题

hash分区的例子如下,使用了hostmetric列做分区

hash分区可以将写均匀分散到每台机器上,对于scan来说,可以指定hostmetric来做分区裁剪
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分区,这种方式更容易出现热点问题,因为单个hostmetrics的所有值都属于单个tablets。scan时可以利用hostmertics做分区裁剪

其他

使用下列方式修改表结构,多个操作可以将下面操作组合成事务来完成

  • rename
  • rename主键列
  • rename、add、drop非主键列
  • add、drop range分区

限制

  • 主键不能修改
  • 分区不能修改 ,可以删除再创建
  • 列类型不能修改