Kudu有着和RDBMS类似你的结构数据存储机制,Schema的设计非常重要
- 读写能够spread到整个tablet servers,而不会出现热点,partition作用
- Tablets能够按照均匀,可预测的速度增长,负载平稳
- 一次查询,最小的数据访问需求
Column设计
属于Primary Key的列不能为空,支持的数据类型如下
- boolean
- 8-bit signed integer
- 16-bit signed integer
- 32-bit signed integer
- 64-bit signed integer
- 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
- UTF-8 encoded string (up to 64KB uncompressed)
- binary (up to 64KB uncompressed)
Kudu会对列进行高效的编码和序列化,这也是它做列式存储的初衷,因此应该正确使用列的类型,而不是简单一味的全部使用string或者binary。
Decimal Type
该类型是一个数值类型,通过fixed scale和precision,来满足财务或者数学相关数据的表示。都有float和double了,为什么还需要decimal type?
float和double是浮点数,而decimal是定点数,浮点数会出现精确度不高的问题,所以,decimal定点数更多用于财务数字方面。
尽可能使用合适的精度和标度,以减少存储的消耗和计算的消耗。
列编码
编码列表如下,
- Plain Encoding,数据按照自有的格式存储,比如,int32的值就按照fixed-size 32-bit little-endian整形进行存储
- Bitshuffle Encoding,数据块bit被重新编排,所有值的最重要bit第一位,其次放次重要的bit,以此类推...,最终结果是lz4压缩,该编码适合有很多重复的数值情况,或者当根据primary key进行排序,对应某个数值集合改动很小。
- Run Length Encoding,通过存储值value和数量count来对一列进行压缩,适合当对primary key进行排序后,连续重复的数值进行压缩
- 构造包含unique values的字典,在字典中存储每列编码后的值和其对应的字典index完成编码工作,对于具有low cardinality的列高效。在对数据进行flush的时候,如果列的unique values的数量过多的话,kudu自动将该编码降解为plain Encoding
- Prefix Encoding,前缀编码,对于列值大多数有共同前缀的情况下,使用该编码,对于第一列为主键的列,使用该编码。
列压缩
支持Lz4,Snappy,Zlib。目前lz4最好,bitshuffle-Encoding默认是lz4编码,不需要重复设置。
主键的设计
每个Kudu表必须声明primary key,并且primary key具有唯一性的约束,试图插入一个primary key已经存在的数据,会导致duplicate error。
primary key涉及的列,必须是非空的,不能是boolean,float,double类型(decimal可以),一旦设置完primary key后的表,不能修改primary key。
和RDBMS不同,Kudu不支持自动增加的primary key,因此insert时候,必须提供全部的primary key;同理,修改删除的时候,也需要指明修改删除row对应的全部的primary key,目前,Kudu不支持natively的范围delete和update;虽然Kudu的主键不支持修改,但是可以先删除后插入。
主键索引
Tablet中的所有rows是按照primary key排序的。基于主键的,相等,范围查找,效率高。
Backfill 插入//todo
考虑一个问题,primary key是timestamp或者primary key的第一个列是timestamp,当插入数据时,Kudu会去查找key是否存在,如果存在则duplicate error。
分区
选择一个合适的分区策略需要理解数据模型和table表的负载期望,对于写多的应用场景,能够让写操作spread所有tablet至关重要,这样可以避免某个tablet过大。同样的,对于很多short scans的情况,如果对于scan的数据都在一个tablet上的话,会有更高的效率。上面两点是我们进行分区策略设置需要铭记于心的。
Range分区
Range分区使用全局有序的range分区key来分布数据,每一个分区都被赋予一部分连续的keys,这部分一定会是整体primary key 列的keys的子集。如果没有hash分区参与,对于range分区,一个分成的range分区都是一个tablet。
Range分区必须不能彼此重叠,每个row都只能落在唯一一个分区中。
Range分区可以动态的增加和删除,如果删除一个range分区,则对应的tablet也会删除,数据也会删除,
Hash分区
Hash分区通过hash值将数据分布到多个buckets中,buckets的数量在create表的时候设置好。当不需要顺序访问表的时候,使用hash分区是非常有效的,能有效的将数据spread所有tablets
多级别分区
Hash和Range两种分区,各种好坏,通过结合,能够充分利用二者的优势,当然要结合业务情况。
分区Prune
Kudu能够自动skip扫描所有分区,当他能够对scan语句进行确定是否能够过滤掉某些不需要的分区。对于hash分区,scan扫描能够在每一个hashed列上相等才行,对于range扫描,能够在range partitioned列上进行相等或者范围上判断。
分区的例子
接下来,我们通过例子来说明两种分区的某些特点。考虑如下建表语句,metric表
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有2014,2015,2016年的,通常time字段设置range分区,两种方式
- unbounded range分区
-
bounded range分区
在第一个例子中,使用了默认的range分区,将会在2015-01-01和2016-01-01两个点进行分区,这样导致了三个tablets,1)2015-01-01之前,2)2015-01-01到2016-01-01,3)2016-01-01之后,
第二个例子,使用分区范围bounded方式,[(2014-01-01), (2017-01-01)],分割点是2015-01-01,和2016-01-01。最终,分区为,[(2014-01-01), (2015-01-01)], [(2015-01-01), (2016-01-01)], 和[(2016-01-01), (2017-01-01)]
第一个例子是unbounded,第二个是bounded分区。
上述两种方式都支持time-bounded扫描的分区prune(前提是被pruned分区的time bound都落在scan time的bound之外)。对于写操作,上述两种方式都面临着写热点的问题,因此,指标metric写入都是基于当前时间的,大多数写操作都会落入到一个单独range分区中。
Hash分区
假设,我们在host和metric上同时hash
该例子,在host和metric上分成4个buckets,这点不同于range分区,hash分区可以使得write操作spread所有tablets,提高写的吞吐量,同时,scan操作也可以利用分区的prune。
严重的一点是,hash策略会导致每个分区越来越大,因为buckets个数固定了。这点range分区不会,因为range分区可以通过增加新的分区来扩容。
Hash和Range结合的例子
先看下二者之间的优缺点。
Hash分区能够提高写的吞吐量,Range分区能够避免无限的tablet增长问题。所以,通常可以结合两种策略,来利用各自的优点。如下图,
在time上进行range分区,在host和metric上进行hash分区,结合了hash和range两种策略。写操作会并发到hash buckets的数据,这里是4个buckets,读操作还可利用time bound和明确host以及metric实现分区prune。新的分区也可以添加进来,同时会生成4个新的tablets。
Hash和Range结合的例子
两个hash策略结合,可以不同的buckets
host 4 buckets,metric 3 buckets,总共12个tablet,写操作可以spread所有tablets,读操作可以重复利用host和metric进行分区prune。
缺陷
Number of Columns
By default, Kudu will not permit the creation of tables with more than 300 columns. We recommend schema designs that use fewer columns for best performance.Size of Cells
No individual cell may be larger than 64KB before encoding or compression. The cells making up a composite key are limited to a total of 16KB after the internal composite-key encoding done by Kudu. Inserting rows not conforming to these limitations will result in errors being returned to the client.Size of Rows
Although individual cells may be up to 64KB, and Kudu supports up to 300 columns, it is recommended that no single row be larger than a few hundred KB.Valid Identifiers
Identifiers such as table and column names must be valid UTF-8 sequences and no longer than 256 bytes.Immutable Primary Keys
Kudu does not allow you to update the primary key columns of a row.Non-alterable Primary Key
Kudu does not allow you to alter the primary key columns after table creation.Non-alterable Partitioning
Kudu does not allow you to change how a table is partitioned after creation, with the exception of adding or dropping range partitions.Non-alterable Column Types
Kudu does not allow the type of a column to be altered.Partition Splitting
Partitions cannot be split or merged after table creation.