性能优化:Doris-亿级数据秒出结果

背景

我司有个广告竞价系统,地区数据报表的维度非常多,导致数据量急剧膨胀,达到亿级别,使用 Mysql 数据库,查询非常卡顿,急需优化,使用 Doris 数据库代替 Mysql 存储和查询,秒出结果,另外可以借助 Doirs 的分区功能,自动保留 6 个月的数据。

Doris是什么?

Apache Doris 是一个基于 MPP 架构的高性能、实时的分析型数据库,以极速易用的特点被人们所熟知,仅需亚秒级响应时间即可返回海量数据下的查询结果,不仅可以支持高并发的点查询场景,也能支持高吞吐的复杂分析场景。基于此,Apache Doris 能够较好的满足报表分析、即席查询、统一数仓构建、数据湖联邦查询加速等使用场景,用户可以在此之上构建用户行为分析、AB 实验平台、日志检索分析、用户画像分析、订单分析等应用。

发展历程:诞生于百度广告报表业务的 Palo 项目 → 2017 年正式对外开源 → 2018 年 7 月由百度捐赠给 Apache 基金会进行孵化 → 2022 年 6 月,Apache Doris 成功从 Apache 孵化器毕业,正式成为 Apache 顶级项目

使用场景:

  • 报表分析
  • 即席查询
  • 统一数仓构建

二、Doris三种数据模型

Doris 数据模型上目前分为三类: AGGREGATE KEY, UNIQUE KEY, DUPLICATE KEY。三种模型中数据都是按KEY进行排序。

1、AGGREGATE KEY

AGGREGATE KEY相同时,新旧记录进行聚合,目前支持的聚合函数有SUM, MIN, MAX, REPLACE。

AGGREGATE KEY模型可以提前聚合数据, 适合报表和多维分析业务。

CREATE DATABASE IF NOT EXISTS example_db;

CREATE TABLE IF NOT EXISTS example_db.example_tbl_agg1
(
    `user_id` LARGEINT NOT NULL COMMENT "用户id",
    `date` DATE NOT NULL COMMENT "数据灌入日期时间",
    `city` VARCHAR(20) COMMENT "用户所在城市",
    `age` SMALLINT COMMENT "用户年龄",
    `sex` TINYINT COMMENT "用户性别",
    `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
    `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费",
    `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间",
    `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间"
)
AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

2、UNIQUE KEY

UNIQUE KEY 相同时,新记录覆盖旧记录。在1.2版本之前,UNIQUE KEY 实现上和 AGGREGATE KEY 的 REPLACE 聚合方法一样,二者本质上相同,自1.2版本我们给UNIQUE KEY引入了merge on write实现,该实现有更好的聚合查询性能。适用于有更新需求的分析业务。

CREATE TABLE IF NOT EXISTS example_db.example_tbl_unique
(
    `user_id` LARGEINT NOT NULL COMMENT "用户id",
    `username` VARCHAR(50) NOT NULL COMMENT "用户昵称",
    `city` VARCHAR(20) COMMENT "用户所在城市",
    `age` SMALLINT COMMENT "用户年龄",
    `sex` TINYINT COMMENT "用户性别",
    `phone` LARGEINT COMMENT "用户电话",
    `address` VARCHAR(500) COMMENT "用户地址",
    `register_time` DATETIME COMMENT "用户注册时间"
)
UNIQUE KEY(`user_id`, `username`)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

3、DUPLICATE KEY

只指定排序列,相同的行不会合并。适用于数据无需提前聚合的分析业务。

CREATE TABLE IF NOT EXISTS example_db.example_tbl_duplicate
(
    `timestamp` DATETIME NOT NULL COMMENT "日志时间",
    `type` INT NOT NULL COMMENT "日志类型",
    `error_code` INT COMMENT "错误码",
    `error_msg` VARCHAR(1024) COMMENT "错误详细信息",
    `op_id` BIGINT COMMENT "负责人id",
    `op_time` DATETIME COMMENT "处理时间"
)
DUPLICATE KEY(`timestamp`, `type`, `error_code`)
DISTRIBUTED BY HASH(`type`) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

三、数据模型的选择建议

  1. Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。
  2. Unique 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。
  3. Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)。

四、Doris分区

1、动态分区

动态分区是在 Doris 0.12 版本中引入的新功能。旨在对表级别的分区实现生命周期管理(TTL),减少用户的使用负担。

动态分区只支持 Range 分区。

CREATE TABLE tbl1
(...)
PROPERTIES
(
    "dynamic_partition.prop1" = "value1",
    "dynamic_partition.prop2" = "value2",
    ...
)
  • dynamic_partition.enable true or false,默认为TRUE``
  • dynamic_partition.time_unit(必选参数)动态分区调度的单位。可指定为 HOURDAYWEEKMONTHYEAR。分别表示按小时、按天、按星期、按月、按年进行分区创建或删除。
  • dynamic_partition.time_zone 动态分区的时区,如果不填写,则默认为当前机器的系统的时区
  • dynamic_partition.start 动态分区的起始偏移,为负数。根据 time_unit 属性的不同,以当天(星期/月)为基准,分区范围在此偏移之前的分区将会被删除
  • dynamic_partition.end(必选参数)动态分区的结束偏移,为正数。根据 time_unit 属性的不同,以当天(星期/月)为基准,提前创建对应范围的分区。
  • dynamic_partition.prefix(必选参数)动态创建的分区名前缀。

举例说明:
表 tbl1 分区列 k1 类型为 DATE,创建一个动态分区规则。按月分区,不删除历史分区,并且预先创建未来2个月的分区。同时设定以每月3号为起始日。

CREATE TABLE tbl1
(
    k1 DATE,
    ...
)
PARTITION BY RANGE(k1) ()
DISTRIBUTED BY HASH(k1)
PROPERTIES
(
    "dynamic_partition.enable" = "true",
    "dynamic_partition.time_unit" = "MONTH",
    "dynamic_partition.end" = "2",
    "dynamic_partition.prefix" = "p",
    "dynamic_partition.buckets" = "8",
    "dynamic_partition.start_day_of_month" = "3"
);

假设当前日期为 2020-05-29。则根于以上规则,tbl1 会产生以下分区:

p202005: ["2020-05-03", "2020-06-03")
p202006: ["2020-06-03", "2020-07-03")
p202007: ["2020-07-03", "2020-08-03") 

2、静态分区

见官方文档,链接:doris.apache.org/zh-CN/docs/advanc...

五、Doris简单应用

talk is cheap,show me code,以猜你地区报表为例,目前表的数据上亿,查询非常慢,急需优化

创建表:

CREATE TABLE `ad_area_reports_test` (
  `day` date NULL COMMENT '日期',
  `hour` char(2) NOT NULL COMMENT '小时',
  `province` varchar(50) NOT NULL COMMENT '省',
  `city` varchar(50) NOT NULL COMMENT '市',
  `user_id` bigint(20) NULL COMMENT '账户ID',
  `ad_id` bigint(20) NULL COMMENT '广告ID',
  `ad_group_id` bigint(20) NULL COMMENT '广告组ID',
  `ad_creative_id` bigint(20) NULL COMMENT '创意ID',
  `ad_form` tinyint(4) NULL,
  `view` bigint(20) SUM NULL DEFAULT "0" COMMENT '展现',
  `click` bigint(20) SUM NULL DEFAULT "0" COMMENT '点击',
  `cost` bigint(20) SUM NULL DEFAULT "0" COMMENT '消费金额'
) ENGINE=OLAP
AGGREGATE KEY(`day`, `hour`, `province`, `city`, `user_id`, `ad_id`, `ad_group_id`, `ad_creative_id`, `ad_form`)
COMMENT 'OLAP'
PARTITION BY RANGE(`day`)()
DISTRIBUTED BY HASH(`day`) BUCKETS 10
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "MONTH",
"dynamic_partition.time_zone" = "Asia/Shanghai",
"dynamic_partition.start" = "-6",
"dynamic_partition.end" = "2",
"dynamic_partition.prefix" = "p_",
"dynamic_partition.buckets" = "8",
"dynamic_partition.start_day_of_month" = "18",
);

六、踩过的坑

  1. 需要提前创建分区,不然数据无法写入
  2. 字段类型要一致,不然数据无法写入,hive 表中 cost 字段为 string,而 doris 数据库地区表中 cost 字段为int类型,导致数据无法写入,需要使用 cast 字段转化
本作品采用《CC 协议》,转载必须注明作者和本文链接
yefy
本帖由系统于 1年前 自动加精
讨论数量: 4
Dcatplus-杨光

很好的干货,学习了

1年前 评论

Doris使用成本怎么样,我的也是上亿的数据,有时候需要统计分析

1年前 评论
yefy (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!