文章目录
- 前言
- 2. 表设计
- 2.1 StarRocks表设计
- 2.1.1 列式存储
- 2.1.2 索引
- 2.1.3 加速处理
- 2.1.3.1 预先聚合
- 2.1.3.2 分区分桶
- 2.1.3.3 物化视图
- 2.1.3.4 列级索引
- 2.2 数据模型
- 2.2.1 明细模型
- 2.2.1.1 适用场景
- 2.2.1.2 创建表
- 2.2.1.3 使用说明
- 2.2.2 聚合模型
- 2.2.2.1 适用场景
- 2.2.2.2 原理
- 2.2.2.3 创建表
- 2.2.2.4 使用说明
- 2.2.3 更新模型
- 2.2.3.1 适用场景
- 2.2.3.2 原理
- 2.2.3.3 创建表
- 2.2.3.4 使用说明
- 2.2.4 主键模型
- 2.2.4.1 适用场景
- 2.2.4.2 原理
- 2.2.4.3 创建表
- 2.2.4.4 使用说明
前言
本文为Flink-StarRocks详解后续章节:主要详解StarRocks表设计,聚合,更新,主键三大数据模型。
由于篇幅过长,后续接着下面进行详解:
StarRocks分区分桶
StarRocks查询数据湖
实现即席查询案例
2. 表设计
2.1 StarRocks表设计
2.1.1 列式存储
StarRocks 中的表由行和列构成。每行数据对应用户一条记录,每列数据具有相同的数据类型。所有数据行的列数相同,可以动态增删列。在 StarRocks 中,一张表的列可以分为维度列(也称为 Key 列)和指标列(也称为 Value 列)。维度列用于分组和排序,指标列的值可以通过聚合函数 sum、count、min、max、hll_union_agg 和 bitmap_union 等累加起来。
在 StarRocks 中,表数据按列存储。物理上,一列数据会经过分块编码、压缩等操作,然后持久化存储到非易失设备上。但在逻辑上,一列数据可以看成是由相同类型的元素构成的一个数组。 一行数据的所有列值在各自的数组中按照列顺序排列,即拥有相同的数组下标。数组下标是隐式的,不需要存储。表中所有的行按照维度列,做多重排序,排序后的位置就是该行的行号。
查询时,如果指定了维度列上的等值条件或者范围条件、并且这些条件中的维度列可以构成表的维度列前缀,则可以利用数据的有序性,使用二分查找法快速锁定目标行。例如,表 table1 包含 event_day、siteid、citycode 和 username 四列,其中 event_day 和 siteid 是维度列。如果查询条件为 event_day = 2020-09-18 和 siteid = 2,因为 event_day 和 siteid 可以构成维度列前缀,因此可以使用二分查找法,只需要处理指定范围内的数据;如果查询条件为 citycode = 4 和 username = Andy,因为 citycode 和 username 不能构成维度列前缀,因此无法使用二分查找法,必须处理整表的数据。
2.1.2 索引
StarRocks 通过前缀索引 (Prefix Index) 和列级索引,能够快速找到目标行所在数据块的起始行号。
StarRocks 表设计原理如下图所示。
注意:因为不同的行数据量不一样,所以有的前缀在列级索引中会对应2个行号,有的1个,有的0个。
一张表中的数据组织主要由三部分构成:
前缀索引
表中每 1024 行数据构成一个逻辑数据块 (Data Block)。每个逻辑数据块在前缀索引表中存储一个索引项,索引项的内容为数据块中第一行数据的维度列所构成的前缀,长度不超过 36 字节。前缀索引是一种稀疏索引。使用表中某行数据的维度列所构成的前缀查找前缀索引表,可以确定该行数据所在逻辑数据块的起始行号。
列级数据块
表中每列数据都按 64 KB 分块存储。数据块作为一个单位单独编码、压缩,也作为 I/O 单位,整体写回设备或者读出。
列级索引
表中每列数据都有一个独立的行号索引。行号索引表中,该列的数据块和行号一一对应。每个行号索引项由对应数据块的起始行号、位置和长度信息构成。用某行数据的行号查找行号索引表,可以获取包含该行号对应的数据块所在的位置,读取目标数据块后,可以进一步查找数据。
由此可见,通过某行数据的维度列所构成的前缀查找该行数据的过程包含以下五个步骤:
- 先查找前缀索引表,获得逻辑数据块的起始行号。
- 查找维度列的行号索引,定位到维度列的数据块。
- 读取数据块。
- 解压、解码数据块。
- 从数据块中找到维度列前缀对应的数据项。
2.1.3 加速处理
StarRocks 通过如下机制实现数据的加速处理:
2.1.3.1 预先聚合
StarRocks 支持聚合模型,维度列取值相同的数据行可合并一行。合并后,数据行的维度列取值不变,指标列的取值为这些数据行的聚合结果。用户需要给指标列指定聚合函数。通过预先聚合,可以加速聚合操作。
2.1.3.2 分区分桶
StarRocks 中,表被划分成多个 Tablet,每个 Tablet 多副本冗余存储在 BE 上。BE 和 Tablet 的数量可以根据计算资源和数据规模的变化而弹性伸缩。查询时,多台 BE 可以并行地查找 Tablet,从而快速获取数据。此外,Tablet 的副本可以复制和迁移,从而增强数据可靠性,并避免数据倾斜。总之,分区分桶有效保证了数据访问的高效性和稳定性。
2.1.3.3 物化视图
前缀索引可以加速数据查找,但是前缀索引依赖维度列的排列次序。如果使用非前缀的维度列构造查找谓词,则无法使用前缀索引。用户可以为数据表创建物化视图。物化视图的数据组织和存储与数据表相同,但物化视图拥有自己的前缀索引。在为物化视图创建索引时,可指定聚合的粒度、列的数量和维度列的次序,使频繁使用的查询条件能够命中相应的物化视图索引。
2.1.3.4 列级索引
StarRocks 支持布隆过滤器 (Bloom Filter)、ZoneMap 索引和 位图 (Bitmap) 索引等列级别的索引技术:
布隆过滤器有助于快速判断数据块中不含所查找的值。
ZoneMap 索引有助于通过数据范围快速过滤出待查找的值。
位图索引有助于快速计算出枚举类型的列满足一定条件的行。
2.2 数据模型
建表时,需要指定数据模型 (Data Model),这样数据导入至数据模型时,StarRocks 会按照排序键对数据进行排序、处理和存储。本节介绍 StarRocks 支持的各种数据模型,满足在不同业务场景下的需求。
StarRocks 支持四种数据模型,分别是明细模型 (Duplicate Key Model)、聚合模型 (Aggregate Key Model)、更新模型 (Unique Key Model) 和主键模型 (Primary Key Model)。这四种数据模型能够支持多种数据分析场景,例如日志分析、数据汇总分析、实时分析等。
2.2.1 明细模型
明细模型是默认的建表模型。如果在建表时未指定任何模型,默认创建的是明细类型的表。
创建表时,支持定义排序键。如果查询的过滤条件包含排序键,则 StarRocks 能够快速地过滤数据,提高查询效率。明细模型适用于日志数据分析等场景,支持追加新数据,不支持修改历史数据。
2.2.1.1 适用场景
分析原始数据,例如原始日志、原始操作记录等。
查询方式灵活,不需要局限于预聚合的分析方式。
导入日志数据或者时序数据,主要特点是旧数据不会更新,只会追加新的数据。
2.2.1.2 创建表
例如,需要分析某时间范围的某一类事件的数据,则可以将事件时间(event_time)和事件类型(event_type)作为排序键。使用DUPLICATE KEY来指定排序键。
在该业务场景下,建表语句如下:
CREATE TABLE IF NOT EXISTS test.detail (
event_time DATETIME NOT NULL COMMENT "事件时间",
event_type INT NOT NULL COMMENT "事件类型",
user_id INT COMMENT "用户ID",
device_code INT COMMENT "设备编码",
channel INT COMMENT ""
)
DUPLICATE KEY(event_time, event_type)
DISTRIBUTED BY HASH(user_id)
PROPERTIES (
"replication_num" = "1"
);
注意
建表时必须使用 DISTRIBUTED BY HASH 子句指定分桶键,否则建表失败。
自 2.5.7 版本起,StarRocks 支持在建表和新增分区时自动设置分桶数量 (BUCKETS),无需手动设置分桶数量。
这里对建表过程进行演示。
进入starrrcoks管理平台EMR StarRocks Manager,点击左侧SQL Editor。新建查询文件,首先创建数据库
CREATE DATABASE IF NOT EXISTS test;
选中代码,点击左侧的运行。
创建完成后,点击查询列表右侧的数据库,点击刷新,可以看到test数据库。
然后将上述建表代码写入查询文件,选中,点击运行
创建完成后,可以在数据库下看到此张表信息。
点击左侧导航栏元数据管理,点击test数据库,同样可以看到detail表。点击进去,可以看到元数据信息。
2.2.1.3 使用说明
排序键的相关说明:
在建表语句中,排序键必须定义在其他列之前。
排序键可以通过 DUPLICATE KEY 显式定义。本示例中排序键为 event_time 和 event_type。如果未指定,则默认选择表的前三列作为排序键。
明细模型中的排序键可以为部分或全部维度列。
建表时,支持为指标列创建 BITMAP、Bloom Filter 等索引。
2.2.2 聚合模型
建表时,支持定义排序键和指标列,并为指标列指定聚合函数。当多条数据具有相同的排序键时,指标列会进行聚合。在分析统计和汇总数据时,聚合模型能够减少查询时所需要处理的数据,提升查询效率。
2.2.2.1 适用场景
适用于分析统计和汇总数据。比如:
通过分析网站或 APP 的访问流量,统计用户的访问总时长、访问总次数。
广告厂商为广告主提供的广告点击总量、展示总量、消费统计等。
通过分析电商的全年交易数据,获得指定季度或者月份中,各类消费人群的爆款商品。
在这些场景中,数据查询和导入,具有以下特点:
多为汇总类查询,比如 SUM、MAX、MIN等类型的查询。
不需要查询原始的明细数据。
旧数据更新不频繁,只会追加新的数据。
2.2.2.2 原理
从数据导入至数据查询阶段,聚合模型内部同一排序键的数据会多次聚合,聚合的具体时机和机制如下:
- 数据导入阶段:数据按批次导入至聚合模型时,每一个批次的数据形成一个版本。在一个版本中,同一排序键的数据会进行一次聚合。
- 后台文件合并阶段 (Compaction) :数据分批次多次导入至聚合模型中,会生成多个版本的文件,多个版本的文件定期合并成一个大版本文件时,同一排序键的数据会进行一次聚合。
- 查询阶段:所有版本中同一排序键的数据进行聚合,然后返回查询结果。
因此,聚合模型中数据多次聚合,能够减少查询时所需要的处理的数据量,进而提升查询的效率。
例如,导入如下数据至聚合模型中,排序键为 Date、Country:
Date Country PV
2020.05.01 CHN 1
2020.05.01 CHN 2
2020.05.01 USA 3
2020.05.01 USA 4
在聚合模型中,以上四条数据会聚合为两条数据。这样在后续查询处理的时候,处理的数据量就会显著降低。
Date Country PV
2020.05.01 CHN 3
2020.05.01 USA 7
2.2.2.3 创建表
例如需要分析某一段时间内,来自不同城市的用户,访问不同网页的总次数。则可以将网页地址 site_id、日期 date 和城市代码 city_code 作为排序键,将访问次数 pv 作为指标列,并为指标列 pv 指定聚合函数为 SUM。
在该业务场景下,建表语句如下:
CREATE TABLE IF NOT EXISTS test.aggregate_tbl (
site_id LARGEINT NOT NULL COMMENT "id of site",
date DATE NOT NULL COMMENT "time of event",
city_code VARCHAR(20) COMMENT "city_code of user",
pv BIGINT SUM DEFAULT "0" COMMENT "total page views"
)
AGGREGATE KEY(site_id, date, city_code)
DISTRIBUTED BY HASH(site_id)
PROPERTIES (
"replication_num" = "1"
);
2.2.2.4 使用说明
排序键的相关说明:
在建表语句中,排序键必须定义在其他列之前。
排序键可以通过 AGGREGATE KEY 显式定义。
如果 AGGREGATE KEY 未包含全部维度列(除指标列之外的列),则建表会失败。
如果不通过 AGGREGATE KEY 显示定义排序键,则默认除指标列之外的列均为排序键。
排序键必须满足唯一性约束,必须包含全部维度列,并且列的值不会更新。
指标列:通过在列名后指定聚合函数,定义该列为指标列。一般为需要汇总统计的数据。
聚合函数:指标列使用的聚合函数。聚合模型支持的聚合函数,请参见 CREATE TABLE。常见的有SUM、MAX、MIN、REPLACE。
查询时,排序键在多版聚合之前就能进行过滤,而指标列的过滤在多版本聚合之后。因此建议将频繁使用的过滤字段作为排序键,在聚合前就能过滤数据,从而提升查询性能。
建表时,不支持为指标列创建 BITMAP、Bloom Filter 等索引。
2.2.3 更新模型
建表时,支持定义主键和指标列,查询时返回主键相同的一组数据中的最新数据。相对于明细模型,更新模型简化了数据导入流程,能够更好地支撑实时和频繁更新的场景。
2.2.3.1 适用场景
实时和频繁更新的业务场景,例如分析电商订单。在电商场景中,订单的状态经常会发生变化,每天的订单更新量可突破上亿。
2.2.3.2 原理
更新模型可以视为聚合模型的特殊情况,指标列指定的聚合函数为 REPLACE,返回具有相同主键的一组数据中的最新数据。
数据分批次多次导入至更新模型,每一批次数据分配一个版本号,因此同一主键的数据可能有多个版本,查询时返回版本最新(即版本号最大)的数据。相对于明细模型,更新模型通过简化导入流程,能够更好地支持实时和频繁更新。
例如下表中,ID 是主键,value 是指标列,_version 是 StarRocks 内部的版本号。其中,ID 为 1 的数据有两个导入批次,版本号分别为 1 和 2;ID 为 2 的数据有三个导入批次,版本号分别为 3、4、5。
ID value _version
1 100 1
1 101 2
2 100 3
2 101 4
2 102 5
查询 ID 为 1 的数据时,仅会返回最新版本 2 的数据,而查询 ID 为 2 的数据时,仅会返回最新版本 5 的数据,最终查询结果如下:
ID value
1 101
2 102
2.2.3.3 创建表
在电商订单分析场景中,经常按照日期对订单状态进行统计分析,则可以将经常使用的过滤字段订单创建时间 create_time、订单编号 order_id 作为主键,其余列订单状态 order_state 和订单总价 total_price 作为指标列。这样既能够满足实时更新订单状态的需求,又能够在查询中进行快速过滤。
在该业务场景下,建表语句如下:
CREATE TABLE IF NOT EXISTS test.update_orders (
create_time DATE NOT NULL COMMENT "create time of an order",
order_id BIGINT NOT NULL COMMENT "id of an order",
order_state INT COMMENT "state of an order",
total_price BIGINT COMMENT "price of an order"
)
UNIQUE KEY(create_time, order_id)
DISTRIBUTED BY HASH(order_id)
PROPERTIES (
"replication_num" = "1"
);
2.2.3.4 使用说明
主键的相关说明:
在建表语句中,主键必须定义在其他列之前。
主键通过 UNIQUE KEY 定义。
主键必须满足唯一性约束,且列的值不会修改。
设置合理的主键。
查询时,主键在聚合之前就能进行过滤,而指标列的过滤通常在多版本聚合之后,因此建议将频繁使用的过滤字段作为主键,在聚合前就能过滤数据,从而提升查询性能。
聚合过程中会比较所有主键,因此需要避免设置过多的主键,以免降低查询性能。如果某个列只是偶尔会作为查询中的过滤条件,则不建议放在主键中。
建表时,不支持为指标列创建 BITMAP、Bloom Filter 等索引。
2.2.4 主键模型
主键模型支持分别定义主键和排序键。数据导入至主键模型的表时先按照排序键排序后存储。查询时返回主键相同的一组数据中的最新数据。相对于更新模型,主键模型在查询时不需要执行聚合操作,并且支持谓词和索引下推,能够在支持实时和频繁更新等场景的同时,提供高效查询。
说明
3.0 版本之前,主键模型不支持分别定义主键和排序键。
自 3.1 版本起,存算分离模式支持创建主键模型表,但是不支持主键模型表开启持久化索引。
2.2.4.1 适用场景
主键模型适用于实时和频繁更新的场景,例如:
实时对接事务型数据至 StarRocks。事务型数据库中,除了插入数据外,一般还会涉及较多更新和删除数据的操作,因此事务型数据库的数据同步至 StarRocks 时,建议使用主键模型。通过 Flink-CDC 等工具直接对接 TP 的 Binlog,实时同步增删改的数据至主键模型,可以简化数据同步流程,并且相对于 Merge-On-Read 策略的更新模型,查询性能能够提升 3~10 倍。
利用部分列更新轻松实现多流 JOIN。在用户画像等分析场景中,一般会采用大宽表方式来提升多维分析的性能,同时简化数据分析师的使用模型。而这种场景中的上游数据,往往可能来自于多个不同业务(比如来自购物消费业务、快递业务、银行业务等)或系统(比如计算用户不同标签属性的机器学习系统),主键模型的部分列更新功能就很好地满足这种需求,不同业务直接各自按需更新与业务相关的列即可,并且继续享受主键模型的实时同步增删改数据及高效的查询性能。
2.2.4.2 原理
主键模型是由 StarRocks 全新设计开发的存储引擎支撑。相比于更新模型,主键模型的元数据组织、读取、写入方式完全不同,不需要执行聚合操作,并且支持谓词和索引下推,极大地提高了查询性能。
更新模型整体上采用了 Merge-On-Read 的策略。虽然写入时处理简单高效,但是查询时需要在线聚合多版本。并且由于 Merge 算子的存在,谓词和索引无法下推,严重影响了查询性能。
而主键模型采用了 Delete+Insert 的策略,保证同一个主键下仅存在一条记录,这样就完全避免了 Merge 操作。具体实现方式如下:
StarRocks 收到对某记录的更新操作时,会通过主键索引找到该条记录的位置,并对其标记为删除,再插入一条新的记录。相当于把 Update 改写为 Delete+Insert。
StarRocks 收到对某记录的删除操作时,会通过主键索引找到该条记录的位置,对其标记为删除。
这样,查询时不需要执行聚合操作,不影响谓词和索引的下推,保证了查询的高效执行。
2.2.4.3 创建表
例如,需要按天实时分析订单,则可以将时间 dt、订单编号 order_id 作为主键,其余列为指标列。建表语句如下:
CREATE TABLE IF NOT EXISTS test.primary_orders (
dt date NOT NULL,
order_id bigint NOT NULL,
user_id int NOT NULL,
merchant_id int NOT NULL,
good_id int NOT NULL,
good_name string NOT NULL,
price int NOT NULL,
cnt int NOT NULL,
revenue int NOT NULL,
state tinyint NOT NULL
) PRIMARY KEY (dt, order_id)
PARTITION BY RANGE(`dt`) (
PARTITION p20210820 VALUES [('2021-08-20'), ('2021-08-21')),
PARTITION p20210821 VALUES [('2021-08-21'), ('2021-08-22')),
PARTITION p20210929 VALUES [('2021-09-29'), ('2021-09-30')),
PARTITION p20210930 VALUES [('2021-09-30'), ('2021-10-01'))
) DISTRIBUTED BY HASH(order_id)
PROPERTIES (
"replication_num" = "1",
"enable_persistent_index" = "true"
);
例如,需要按地域、最近活跃时间实时分析用户情况,则可以将表示用户 ID 的 user_id 列作为主键,表示地域的 address 列和表示最近活跃时间的 last_active 列作为排序键。建表语句如下:
CREATE TABLE IF NOT EXISTS test.primary_users (
user_id bigint NOT NULL,
name string NOT NULL,
email string NULL,
address string NULL,
age tinyint NULL,
sex tinyint NULL,
last_active datetime,
property0 tinyint NOT NULL,
property1 tinyint NOT NULL,
property2 tinyint NOT NULL,
property3 tinyint NOT NULL
) PRIMARY KEY (user_id)
DISTRIBUTED BY HASH(user_id)
ORDER BY(`address`,`last_active`)
PROPERTIES (
"replication_num" = "1",
"enable_persistent_index" = "true"
);
2.2.4.4 使用说明
主键相关的说明:
在建表语句中,主键必须定义在其他列之前。
主键通过 PRIMARY KEY 定义。
主键必须满足唯一性约束,且列的值不会修改。本示例中主键为 dt、order_id。
主键支持以下数据类型:BOOLEAN、TINYINT、SMALLINT、INT、BIGINT、LARGEINT、DATE、DATETIME、VARCHAR/STRING。并且不允许为 NULL。
分区列和分桶列必须在主键中。
enable_persistent_index:是否持久化主键索引,同时使用磁盘和内存存储主键索引,避免主键索引占用过大内存空间。通常情况下,持久化主键索引后,主键索引所占内存为之前的 1/10。可以在建表时,在PROPERTIES中配置该参数,取值范围为 true 或者 false(默认值)。
自 2.3.0 版本起,StarRocks 支持配置该参数。
如果磁盘为固态硬盘 SSD,则建议设置为 true。如果磁盘为机械硬盘 HDD,并且导入频率不高,则也可以设置为 true。
建表后,如果需要修改该参数,可使用 ALTER TABLE 修改表的属性 。
存算分离模式不支持主键模型表开启持久化索引。
如果未开启持久化索引,导入时主键索引存在内存中,可能会导致占用内存较多。因此建议遵循如下建议:
合理设置主键的列数和长度。建议主键为占用内存空间较少的数据类型,例如 INT、BIGINT 等,暂时不建议为 VARCHAR。
在建表前根据主键的数据类型和表的行数来预估主键索引占用内存空间,以避免出现内存溢出。以下示例说明主键索引占用内存空间的计算方式:
假设存在主键模型,主键为dt、id,数据类型为 DATE(4 个字节)、BIGINT(8 个字节)。则主键占 12 个字节。
假设该表的热数据有 1000 万行,存储为三个副本。
则内存占用的计算方式:(12 + 9(每行固定开销) ) * 1000W * 3 * 1.5(哈希表平均额外开销) = 945 M
通过 ORDER BY 关键字指定排序键,可指定为任意列的排列组合。
注意:如果指定了排序键,就根据排序键构建前缀索引;如果没指定排序键,就根据主键构建前缀索引。
支持使用 ALTER TABLE 进行表结构变更,但是存在如下注意事项:
不支持修改主键。
对于排序键,支持通过 ALTER TABLE … ORDER BY … 重新指定排序键。不支持删除排序键,不支持修改排序键中列的数据类型。
不支持调整列顺序。