Doris数据模型
在 Doris 中,数据以表(Table)的形式进行逻辑上的描述。 一张表包括行(Row)和列(Column)。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。
Column 可以分为两大类:Key 和 Value。从业务角度看,Key 和 Value 可以分别对应维度列和指标列。Doris的key列是建表语句中指定的列,建表语句中的关键字'unique key'或'aggregate key'或'duplicate key'后面的列就是 Key 列,除了 Key 列剩下的就是 Value 列。
Doris的数据模型一共有Aggregate、Unique、Duplicate(默认)三类,分别对应建表语句指定'aggregate key'、'unique key'、'duplicate key'。
一、Aggregate数据模型
Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量。聚合模型的特点就是将表中的列分为了Key和Value两种。 Key 就是数据的维度列,比如时间,地区等等。 Value 则是数据的指标列,比如点击量,花费等。
支持的聚合类型如下:
- SUM:求和,多行的 Value 进行累加。
- REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。
- MAX:保留最大值。
- MIN:保留最小值。
- REPLACE_IF_NOT_NULL:非空值替换。和 REPLACE 的区别在于对于null值,不做替换。
- HLL_UNION:HLL 类型的列的聚合方式,通过 HyperLogLog 算法聚合。
- BITMAP_UNION:BIMTAP 类型的列的聚合方式,进行位图的并集聚合。
1、示例:
现有数据表结构如下
ColumnName |
Type |
AggregationType |
Comment |
ser_month |
varchar(6) |
|
月份 |
latn_id |
INT |
|
本地网标识 |
latn_name |
VARCHAR(30) |
|
本地网名称 |
telecom_area_id |
INT |
|
区局标识 |
telecom_area_name |
VARCHAR(50) |
|
区局名称 |
user_count |
INT |
SUM |
5G合约本月新增用户数 |
last_update_date |
DATE |
REPLACE |
最后更新日期 |
- 创建聚合表
CREATE TABLE IF NOT EXISTS ctyun_liucy.user_count_info
(
`ser_month` varchar(6) COMMENT "月份",
`latn_id` INT COMMENT "本地网标识",
`latn_name` varchar(30) COMMENT "本地网名称",
`telecom_area_id` INT COMMENT "区局标识",
`telecom_area_name` varchar(50) COMMENT "区局名称",
`user_count` INT SUM DEFAULT "0" COMMENT "5G合约本月新增用户数",
`last_update_date` DATE REPLACE COMMENT "最后更新日期"
)
AGGREGATE KEY(`ser_month`, `latn_id`, `latn_name`,`telecom_area_id`,`telecom_area_name`)
DISTRIBUTED BY HASH(`ser_month`, `latn_id`) BUCKETS 3
PROPERTIES (
"replication_allocation" = "tag.location.default:3"
);
- 插入数据
insert into ctyun_liucy.user_count_info (ser_month,latn_id,latn_name,telecom_area_id,telecom_area_name,user_count,last_update_date) values
('202401','1001','武汉','100101','洪山区','21','20240110'),
('202401','1005','宜昌','100511','西陵区','13','20240110'),
('202401','1009','十堰','100905','竹山县','8','20240110'),
('202401','1001','武汉','100101','洪山区','33','20240111'),
('202401','1001','武汉','100101','洪山区','10','20240112'),
('202401','1009','十堰','100908','茅箭区','6','20240112');
- 查询数据
可以看到user_count字段根据AGGREGATE KEY做了SUM操作,last_update_date以最后一条记录进行了更新。
2、应用场景:
Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常
适合有固定模式的报表类查询场景。
3、实现原理:
Doris 采用了类似于 LSM-Tree 的数据写入模型,将数据以追加的方式顺序写入磁盘,实现了写优化。这样可以提高系统的写入性能,但是也带来了读取时的额外开销。为了处理多次写入造成的数据变化,Doris 引入后台compaction进行优化。
聚合模型可能发生合并(compaction)的阶段有两个,不同批次导入的数据会根据key发生一次聚合合并,另外一个阶段是当后台数据合并未完成,但是进行了查询,查询时时会将数据按key进行合并,保证查询的结果正确。
注意:select count(*)在doris中非常耗时,可以直接某一个key列进行计数 。但是在聚合模型查出的结果不一定正确,因为此时可能并未聚合,所以聚合模型时尽量减少count操作,相关操作结果可能跟自己想要的结果存在偏差。
二、 Unique数据模型
在某些多维分析场景下,用户更关注的是如何保证 Key 的唯一性(如数据更新需求),即如何获得 Primary Key 唯一性约束。 因此,Doris引入了 Unique 数据模型。Unique模型能够保证Key的唯一性,当用户更新一条数据时,新写入的数据会覆盖具有相同key的旧数据。
Unique模型提供了两种实现方式:
- 读时合并(merge-on-read)。在读时合并实现中,用户在进行数据写入时不会触发任何数据去重相关的操作,所有数据去重的操作都在查询或者compaction时进行。因此,读时合并的写入性能较好,查询性能较差,同时内存消耗也较高。
- 写时合并(merge-on-write)。在1.2版本中,我们引入了写时合并实现,该实现会在数据写入阶段完成所有数据去重的工作,因此能够提供非常好的查询性能。
1、读时合并模型
读时合并的Unique数据模型是Aggregate数据模型的一个特例,它的底层实现原理与聚合模型一直,合并操作发生在compaction和查询时合并。
2、写时合并模型
Unique模型的写时合并实现,查询性能更接近于duplicate模型,在有主键约束需求的场景上相比聚合模型有较大的查询性能优势,尤其是在聚合查询以及需要用索引过滤大量数据的查询中。
在开启了写时合并选项的Unique表上,数据在导入阶段就会去将被覆盖和被更新的数据进行标记删除,同时将新的数据写入新的文件。在查询的时候, 所有被标记删除的数据都会在文件级别被过滤掉,读取出来的数据就都是最新的数据,消除掉了读时合并中的数据聚合过程,并且能够在很多情况下支持多种谓词的下推。因此在许多场景都能带来比较大的性能提升,尤其是在有聚合查询的情况下。
3、Unique写时合并模型数据更新
Unique Key模型目前仅支持在Merge-on-Write实现上进行列更新
用户通过正常的导入方式将一部分列的数据写入Doris的Memtable,此时Memtable中并没有整行数据,在Memtable下刷的时候,会查找历史数据,用历史数据补齐一整行,并写入数据文件中,同时将历史数据文件中相同key的数据行标记删除
当出现并发导入时,Doris会利用MVCC机制来保证数据的正确性。如果两批数据导入都更新了一个相同key的不同列,则其中系统版本较高的导入任务会在版本较低的导入任务成功后,使用版本较低的导入任务写入的相同key的数据行重新进行补齐
使用建议:
- 对写入性能要求较高,查询性能要求较低的用户,建议使用Aggregate Key模型
- 对查询性能要求较高,对写入性能要求不高(例如数据的写入和更新基本都在凌晨低峰期完成),或者写入频率不高的用户,建议使用Unique Key模型merge-on-write实现
由于Merge-on-Write实现需要在数据写入的时候,进行整行数据的补齐,以保证最优的查询性能,因此使用Merge-on-Write实现进行部分列更新会有较为明显的导入性能下降。
写入性能优化建议:
- 使用配备了NVMe的SSD,或者极速SSD云盘。因为补齐数据时会大量的读取历史数据,产生较高的读IOPS,以及读吞吐
- 开启行存将能够大大减少补齐数据时产生的IOPS,导入性能提升明显,用户可以在建表时通过如下property来开启行存:
"store_row_column" = "true"
读时合并的Unique模型在写入过程中不做任何额外处理,所以写入性能不受影响,与普通的数据导入相同。但是在查询时进行聚合的代价较大,典型的聚合查询性能相比Unique Key模型的Merge-on-Write实现会有5-10倍的下降。
三、 Duplicate数据模型
在某些多维分析场景下,数据既没有主键,也没有聚合需求。因此,我们引入 Duplicate 数据模型来满足这类需求。
CREATE TABLE
`dsj_2024_kmh_fj_hz1` (
`lx` varchar(200) NOT NULL,
`cty_flg` varchar(20) NULL,
`region_id` bigint(20) NULL,
`region_name` text NULL,
`group_region_id` bigint(20) NULL,
`group_region_name` text NULL,
`val_d` bigint(20) NULL,
`val_d1` bigint(20) NULL,
`val_l` bigint(20) NULL,
`date_id` bigint(20) NULL
) ENGINE = OLAP DUPLICATE KEY(`lx`) COMMENT 'OLAP' DISTRIBUTED BY HASH(`date_id`) BUCKETS 30 PROPERTIES (
"replication_allocation" = "tag.location.default: 3",
"is_being_synced" = "false",
"storage_format" = "V2",
"light_schema_change" = "true",
"disable_auto_compaction" = "false",
"enable_single_replica_compaction" = "false"
);
这种数据模型区别于 Aggregate 和 Unique 模型。数据完全按照导入文件中的数据进行存储,不会有任何聚合。即使两行数据完全相同,也都会保留。 而在建表语句中指定的 DUPLICATE KEY,只是用来指明底层数据按照那些列进行排序。(更贴切的名称应该为 “Sorted Column”, 这里取名 “DUPLICATE KEY” 只是用以明确表示所用的数据模型。)。在 DUPLICATE KEY 的选择上,我们建议适当的选择前 2-4 列就可以。
对于实际使用上,因为我们很多表是从Hive迁移过来,Hive中的表也没有聚合、去重相关概念,因此Hive导入的数据大部分表都可以选用该模型进行创建。
- 无排序键Duplicate数据模型
当创建表的时候没有指定Unique、Aggregate或Duplicate时,会默认创建一个Duplicate模型的表,并自动指定排序列。
当用户并没有排序需求的时候,可以通过在表属性中配置:
"enable_duplicate_without_keys_by_default" = "true"
创建明细模型的时候,就会不再指定排序列,也不会给该表创建前缀索引,以此减少在导入和存储上额外的开销。