006 Kudu | Schema设计

Kudu有着和RDBMS类似你的结构数据存储机制,Schema的设计非常重要

  1. 读写能够spread到整个tablet servers,而不会出现热点,partition作用
  2. Tablets能够按照均匀,可预测的速度增长,负载平稳
  3. 一次查询,最小的数据访问需求

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
  1. Plain Encoding,数据按照自有的格式存储,比如,int32的值就按照fixed-size 32-bit little-endian整形进行存储
  2. Bitshuffle Encoding,数据块bit被重新编排,所有值的最重要bit第一位,其次放次重要的bit,以此类推...,最终结果是lz4压缩,该编码适合有很多重复的数值情况,或者当根据primary key进行排序,对应某个数值集合改动很小。
  3. Run Length Encoding,通过存储值value和数量count来对一列进行压缩,适合当对primary key进行排序后,连续重复的数值进行压缩
  4. 构造包含unique values的字典,在字典中存储每列编码后的值和其对应的字典index完成编码工作,对于具有low cardinality的列高效。在对数据进行flush的时候,如果列的unique values的数量过多的话,kudu自动将该编码降解为plain Encoding
  5. 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分区,两种方式

  1. unbounded range分区
  2. 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.

你可能感兴趣的:(006 Kudu | Schema设计)