Doris 模型使用说明

Doris 三种模型的使用场景、方法说明。


模型 关键字 特点
明细模型 DUPLICATE KEY 默认模型
聚合模型 AGGREGATE KEY 按照 AGGREGATE KEY 指定的字段和 表中定义了的聚合字段进行聚合
更新模型 UNIQUE KEY 按照 AGGREGATE KEY 指定的字段判断是否重复,如果重复则覆盖更新

明细模型

适用场景

Doris 建表的默认模型是明细模型。

一般用明细模型来处理的场景有如下特点:

1
2
3
1.需要保留最原始的数据(如,原始日志,原始操作记录等)来进行分析;
2.分析方式一般不固定,并且相对灵活。因为查询方式很灵活,所以传统的预聚合方式难以命中,效率差;
3.数据更新不是很频繁。导入的数据一般来源于日志类数据,产生后就不太会发生变化。

模型原理

Doris 会为明细模型的表维护排序列,表中的数据会按照排序列进行排序存储。

排序列可以是一列,也可以由多列构成。排序列可以由用户指定,如果用户没有明确指定,那么 Doris 会为表选择默认的几个列作为排序列。

这样,在查询中,如果有相关排序列的过滤条件时,Doris 能够 快速地过滤数据,降低整个查询的时延。

使用案例

创建表的默认数据模型就是明细模型,在向 Doris 明细模型表中导入完全相同的两行数据时,Doris会认为是两行数据。

因为在使用过程中,用户经常会用到时间以及事件类型作为过滤条件,所以将这两个列放在建表的前两 列(事件时间和事件类型)作为排序列。以下是一个使用明细模型创建数据表的例子:

建表

1
2
3
4
5
6
7
8
9
10
drop table if exists demo_model_detail;
CREATE TABLE IF NOT EXISTS demo_model_detail (
event_time DATETIME NOT NULL COMMENT "datetime of event",
event_type INT NOT NULL COMMENT "type of event",
user_id INT COMMENT "id of user",
device_code INT COMMENT "device of ",
channel INT COMMENT ""
)
DUPLICATE KEY(event_time, event_type, user_id)
DISTRIBUTED BY HASH(user_id) BUCKETS 8;

插入数据

1
2
3
insert into demo_model_detail values
("2020-05-25 11:22:33", 1, 101, 3302, 13),
("2020-05-25 11:22:33", 1, 101, 3302, 13);

查询

1
select * from demo_model_detail;

Tips

充分利用排序列,在建表时将经常在查询中用于过滤的列放在表的前面,这样能够提升查询速度。

聚合模型

适用场景

在数据分析领域中,有很多场景需要对数据进行统计和汇总操作。

例如:

  1. 在网站、APP分析中对访问流量进行分析,获得用户的访问时长总计、访问次数总计等统计;

  2. 广告厂商为广告主提供的广告 点击总量、展示总量、消费统计等。

上述的场景就比较适合使用聚合模型来进行查询加速,以下是一些适合聚合模型的场景:

1
2
3
1.业务方进行的查询都是汇总类查询,比如sum、count等类型的查询;
2.不需要查询最原始的明细数据;
3.原始数据不会被频繁更新,只会进行追加新数据。

模型原理

Doris 会将指标按照相同维度进行聚合。当多条数据具有相同的维度时,Doris会把指标进行聚合。
从而能够减少查询时所需要的处理的数据量,提升查询的效率。

以下面的原始数据为例:

date Country PV
2021-04-10 CHN 1
2021-04-10 CHN 2
2021-04-10 USA 3
2021-04-10 USA 4

在 Doris 聚合模型的表中,存储内容会从四条数据变为两条数据。

这样在后续查询处理的时候,处理的数据量就会有指数级的下降。

date Country PV
2021-04-10 CHN 3
2021-04-10 USA 7

使用案例

在建表时,为需要进行聚合的列分配对应的聚合操作就能够使用聚合模型。

以下是一个使用聚合模型创建数据表的例子:

建表

1
2
3
4
5
6
7
8
drop table if exists example_db.demo_model_aggregate;
CREATE TABLE IF NOT EXISTS example_db.demo_model_aggregate (
date DATE NOT NULL COMMENT "日期",
country VARCHAR(20) NOT NULL COMMENT "国家",
pv BIGINT SUM DEFAULT "0" COMMENT "total page views"
)
AGGREGATE KEY(date, country)
DISTRIBUTED BY HASH(country) BUCKETS 8;

插入数据

1
2
3
4
5
6
insert into demo_model_aggregate 
values
("2021-04-10", "USA", 4),
("2021-04-10", "CHN", 2),
("2021-04-10", "USA", 3),
("2021-04-10", "USA", 4);

查询

1
select * from demo_model_aggregate;

Tips

1.对于聚合列上的过滤条件,Doris 并不能够直接对于扫描出来的单行数据进行过滤。

Doris 会将所有 Key 列(指的是上面的 date, country 两个列,他们没有被定义为聚合列)数据以及相应的聚合列数据全部读取出来。

然后将 Key 列相同的不同版本数据进行合并计算出对应的聚合列内容。最后再对计算出的最终聚合列内容进行条件过滤。

所以聚合列上的过滤会比 Key 列上的查询速度慢。这里可以进行优化,如果这个列没有聚合的操作,那么可以将这个列创建为 Key 列。

2.聚合模型目前支持的聚合函数有SUM, MIN, MAX, REPLACE。

更新模型

适用场景

在有些分析场景中,分析的数据经常会发生改变。对于这些分析场景的需求,可以使用 Doris 的更新模型来实现。

比如在电商场景中,定单的状态经常会发生变化,而且变化量又比较大,大型电商公司,每天的订单更新量可能上亿。

要在这种量级的更新场景下进行实时数据分析,如果在明细模型下通过 delete + insert 的方式,是无法满足这么频繁的更新需求的。

这种场景下,用户需要使用更来满足数据分析需求。

以下是一些适合更新模型的场景:

1
2
1.已经写入的数据有大量的更新需求;
2.需要进行实时数据分析。

模型原理

Doris 存储内部会给每一个批次导入数据分配一个版本号,在查询的时候对于主键相同的两条数据,

版本大的数据(也就是更新的数据)被返回,而版本较小的数据不会被返回。

ID value __version
1 100 1
1 101 2
2 100 3
2 101 4
2 102 5

具体的示例如上表所示,ID是表的主键,value是表的内容,而 __version 是 Doris 内部的版本号。

其中 ID为1的数据有两个导入批次,版本分别为1,2;

ID 为 2 的数据有三个批次导入,版本分别为 3,4,5。

在查询的时候对于 ID 为 1 只会返回最新版本 2 的数据,而对于 ID 为 2 只会返回最新版本 5 的数据,

那么对于用户能能够看到的数据如下表所示:

ID value
1 100
2 102

通过这种机制,Doris可以支持对于频繁更新数据的分析。

使用案例

例如在订单分析场景中,经常会有根据订单状态进行的统计分析需求。

因为订单状态经常改变,而 create_time 和order_id 不会改变,并且经常会在查询中作为过滤条件。

所以可以将 create_time 和 order_id 两个列作为这个表的主键(即:在建表时用 UNIQUE KEY 关键字定义),

这样既能够满足订单状态的更新需求,又能够在查询中进行快速过滤。

以下是一个使用更新模型创建数据表的例子:

建表

1
2
3
4
5
6
7
8
9
drop table if exists example_db.demo_model_update;
CREATE TABLE IF NOT EXISTS example_db.demo_model_update (
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) BUCKETS 8;

插入数据

1
2
3
4
5
insert into example_db.demo_model_update 
values
("2021-04-10 11:22:33", 101, 1, 3344),
("2021-04-10 11:22:33", 101, 2, 4455),
("2021-04-10 11:22:33", 101, 3, 5566);

查询

1
select * from example_db.demo_model_update;

Tips

1.导入数据时需要将所有字段补全才能够完成更新操作,

如上述例子中的 create_time、order_id、order_state 和 total_price 四个字段都需必须存在。

2.对于更新模型的数据读取,需要在查询时完成多版本合并,当版本过多时会导致查询性能降低。

所以在向更新模型导入数据时,应该适当降低导入频率,从而提升查询性能。

建议在设计导入频率时以满足业务对实时性的要求为准。如果业务对实时性的要求是分钟级别,那么每分钟导入一次更新数据即可,不需要秒级导入。

3.在查询时,对于 value 字段的过滤通常在多版本合并之后。

可以将经常过滤字段且不会被修改的字段放在主键上。这样能够在合并之前就将数据过滤掉,从而提升查询性能。

4.因为合并过程需要将所有主键字段进行比较,所以应该避免放置过多的主键字段,以免降低查询能。

如果某个字段只是偶尔会作为查询中的过滤条件存在,不需要放在主键中。