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定点数更多用于财务数字方面。
尽可能使用合适的精度和标度,以减少存储的消耗和计算的消耗。
列编码
编码列表如下,
列编码.png
- 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-partitioning-example.png
在第一个例子中,使用了默认的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
hash-partitioning-example.png
该例子,在host和metric上分成4个buckets,这点不同于range分区,hash分区可以使得write操作spread所有tablets,提高写的吞吐量,同时,scan操作也可以利用分区的prune。
严重的一点是,hash策略会导致每个分区越来越大,因为buckets个数固定了。这点range分区不会,因为range分区可以通过增加新的分区来扩容。
Hash和Range结合的例子
先看下二者之间的优缺点。
优缺点.png
Hash分区能够提高写的吞吐量,Range分区能够避免无限的tablet增长问题。所以,通常可以结合两种策略,来利用各自的优点。如下图,
结合的分区方案.png
在time上进行range分区,在host和metric上进行hash分区,结合了hash和range两种策略。写操作会并发到hash buckets的数据,这里是4个buckets,读操作还可利用time bound和明确host以及metric实现分区prune。新的分区也可以添加进来,同时会生成4个新的tablets。
Hash和Range结合的例子
两个hash策略结合,可以不同的buckets
hash-hash-partitioning-example.png
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.
网友评论