Apache Doris 排序键及ShortKey Index

作者: 张家锋

Apache Doris中为加速查询,在内部组织并存储数据时,会把表中数据按照指定的列进行排序,这部分用于排序的列(可以是一个或多个列),可以称之为Sort Key。明细模型中Sort Key就是指定的用于排序的列(即 DUPLICATE KEY 指定的列),聚合模型中Sort Key列就是用于聚合的列(即 AGGREGATE KEY 指定的列),唯一主键模型中Sort Key就是指定的满足唯一性约束的列(即 UNIQUE KEY 指定的列)。下图中的建表语句中Sort Key都为 (user_id, date, city, age, sex)。

CREATE TABLE user_access_dup
(
    `user_id` LARGEINT NOT NULL COMMENT "用户id",
    `date` DATE NOT NULL COMMENT "数据灌入日期时间",
    `city` VARCHAR(20) COMMENT "用户所在城市",
    `age` SMALLINT COMMENT "用户年龄",
    `sex` TINYINT COMMENT "用户性别",
     user_name VARCHAR(32) DEFAULT '',
    `last_visit_date` DATETIME DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
    `cost` BIGINT DEFAULT "0" COMMENT "用户总消费",
    `max_dwell_time` INT DEFAULT "0" COMMENT "用户最大停留时间",
    `min_dwell_time` INT DEFAULT "99999" COMMENT "用户最小停留时间"
)
DUPLICATE KEY(user_id, date, city, age, sex)
DISTRIBUTED BY HASH(city) BUCKETS 10;
CREATE TABLE user_access_agg
(
    `user_id` LARGEINT NOT NULL COMMENT "用户id",
    `date` DATE NOT NULL COMMENT "数据灌入日期时间",
    `city` VARCHAR(20) COMMENT "用户所在城市",
    `age` SMALLINT COMMENT "用户年龄",
    `sex` TINYINT COMMENT "用户性别",
     user_name VARCHAR(32) DEFAULT '',
    `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(city) BUCKETS 10;
CREATE TABLE user_access_unique
(
    `user_id` LARGEINT NOT NULL COMMENT "用户id",
    `date` DATE NOT NULL COMMENT "数据灌入日期时间",
    `city` VARCHAR(20) COMMENT "用户所在城市",
    `age` SMALLINT COMMENT "用户年龄",
    `sex` TINYINT COMMENT "用户性别",
     user_name VARCHAR(32) DEFAULT '',
    `last_visit_date` DATETIME DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
    `cost` BIGINT DEFAULT "0" COMMENT "用户总消费",
    `max_dwell_time` INT DEFAULT "0" COMMENT "用户最大停留时间",
    `min_dwell_time` INT EFAULT "99999" COMMENT "用户最小停留时间"
)
UNIQUE KEY(user_id, date, city, age, sex)
DISTRIBUTED BY HASH(city) BUCKETS 10;

各表数据都依照user_id, date, city, age, sex这四列排序。这里有两点需要注意:

  1. 排序列的定义必须出现在建表语句中其他列的定义之前。的建表语句为例,三个表的排序列可以是user_id, date, city, age, sex,或者user_id, date, city, age, sex,user_name,但不能是user_id, date, city, ,user_name,或者user_id, date, city, age, sex
  2. 排序列的顺序是由create table语句中的列顺序决定的。DUPLICATE/UNIQUE/AGGREGATE KEY中顺序需要和create table语句保持一致。以user_access_dup表为例,也就是说下面的建表语句会报错。
    -- 错误的建表语句
    CREATE TABLE user_access_dup
    (
    `user_id` LARGEINT NOT NULL COMMENT "用户id",
    `date` DATE NOT NULL COMMENT "数据灌入日期时间",
    `city` VARCHAR(20) COMMENT "用户所在城市",
    `age` SMALLINT COMMENT "用户年龄",
    `sex` TINYINT COMMENT "用户性别",
     user_name VARCHAR(32) DEFAULT '',
    `last_visit_date` DATETIME DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
    `cost` BIGINT DEFAULT "0" COMMENT "用户总消费",
    `max_dwell_time` INT DEFAULT "0" COMMENT "用户最大停留时间",
    `min_dwell_time` INT EFAULT "99999" COMMENT "用户最小停留时间"
    )
    DUPLICATE KEY(date,user_id,city,age,sex)
    DISTRIBUTED BY HASH(city) BUCKETS 10;
    -- 正确的建表语句
    CREATE TABLE user_access_dup
    (
    `user_id` LARGEINT NOT NULL COMMENT "用户id",
    `date` DATE NOT NULL COMMENT "数据灌入日期时间",
    `city` VARCHAR(20) COMMENT "用户所在城市",
    `age` SMALLINT COMMENT "用户年龄",
    `sex` TINYINT COMMENT "用户性别",
     user_name VARCHAR(32) DEFAULT '',
    `last_visit_date` DATETIME DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间",
    `cost` BIGINT DEFAULT "0" COMMENT "用户总消费",
    `max_dwell_time` INT DEFAULT "0" COMMENT "用户最大停留时间",
    `min_dwell_time` INT EFAULT "99999" COMMENT "用户最小停留时间"
    )
    DUPLICATE KEY(user_id, date, city, age, sex)
    DISTRIBUTED BY HASH(city) BUCKETS 10;
    
    DUPLICATE KEY列顺序与CREATE TABLE中不一致

再来看一下排序列在查询中的效果,图1中排序列的效果可分三种情况进行描述:

  1. 用户查询时如果条件包含上述两列,则可以大幅地降低扫描数据行,如:
    select sum(cost) from user_access_dup where user_id = 123 and city = 2 ;
    
  2. 如果查询只包含site_id一列,也能定位到只包含user_id 的数据行,如:
    select sum(cost) from user_access_dup where user_id = 123;
    
  3. 如果查询只包含city一列,那么需要扫描所有的数据行,排序的效果相当于大打折扣,如:
    select sum(cost) from user_access_dup  where city = 2;
    

在第一个case中,为了定位到数据行的位置,需进行二分查找,以找到指定区间。假设数据行非常多,直接对user_id, date, city, age, sex进行二分查找,需要把两列数据都加载到内存中,这会消耗大量内存空间。为优化这个细节,Doris在Sort Key的基础上引入稀疏的shortkey index,Sort Index的内容会比数据量少1024倍,因此会全量缓存在内存中,实际查找的过程中可以有效加速查询。当Sort Key列数非常多时,会占用大量内存, 为了避免这种情况, 对shortkey index索引项做了限制:

  • shortkey 的列只能是排序键的前缀;
  • shortkey 列数不超过3;
  • 字节数不超过36字节;
  • 不包含FLOAT/DOUBLE类型的列;
  • VARCHAR类型列只能出现一次, 并且是末尾位置;
  • 当shortkey index的末尾列为CHAR或者VARCHAR类型时, shortkey的长度会超过36字节;

2. 如何选择排序列

从上面的介绍可以看出,如果用户在查询user_access_dup表时只选择city做查询条件,排序列相当于失去了功效。因此排序列的选择是和查询模式息息相关的,经常作为查询条件的列建议放在Sort Key中。

当Sort Key涉及多个列的时候,谁先谁后也有讲究,区分度高、经常查询的列建议放在前面。在user_access_dup表中,city的取值个数是固定的(城市数目是固定的),而site_id的取值个数要比city大得多,而且还在不断变多,因此user_id区分度就比city要高不少。

还是以user_access_dup表为例:

  • 如果用户需要经常按user_id + date + city + age + sex的组合进行查询,那么把user_id放在Sort Key第一列就是更加有效的一种方式。
  • 如果用户需要经常用 city进行查询,偶尔按照user_id + date + city + age + sex 组合查询,那么把 city 放在 Sort Key 的第一列就更为合适。
  • 当然有一种极端情况,就是按user_id + date组合查询、以及city + date 查询的比例不相上下。那么这个时候,可以创建一个city + date为组合 key 的 RollUp 表,RollUp表会为city,date再建一个Sort Index。

3. 注意事项

由于Doris的shortkey索引大小固定(只有36字节),所以不会存在内存膨胀的问题。需要注意的是:

  1. 排序列中包含的列必须是从第一列开始,并且连续的。
  2. 排序列的顺序是由create table语句中的列顺序决定的。
  3. Sort Key不应该包含过多的列。如果选择了大量的列用于Sort Key,那么排序的开销会导致数据导入的开销增加。
  4. 在大多数时候,Sort Key的前面几列也能很准确的定位到数据行所在的区间,更多列的排序也不会带来查询的提升

文章列表

更多推荐

更多
  • Pulsar消息队列-一套高可用实时消息系统实现 实时消息【即时通信】系统,有群聊和单聊两种方式,其形态异于消息队列:1 大量的 group 信息变动,群聊形式的即时通信系统在正常服务形态下,瞬时可能有大量用户登入登出。2 ...
  • Pulsar消息队列-Pulsar对比Kafka笔记 很多人查看 Pulsar 之前可能对 Kafka 很熟悉,参照上图可见二者内部结构的区别,Pulsar 和 Kafka 都是以 Topic 描述一个基本的数据集合,Topic 数据又分为若干 Partition,即对数据进行逻辑上的 ...
  • Pulsar消息队列-对 2017 年一套 IM 系统的反思 信系统的开发,前前后后参与或者主导了六七个 IM 系统的研发。上一次开发的 IM 系统的时间点还是 2018 年,关于该系统的详细描述见 [一套高可用实时消息系统实现][1] ...
  • Apache APISIX文档-快速入门指南-如何构建 Apache APISIX 如何构建 Apache APISIX,步骤1:安装 Apache APISIX,步骤2:安装 etcd,步骤3:管理 Apache APISIX 服务,步骤4:运行测试案例,步骤5:修改 Admin API key,步骤6:为 Apac
  • Apache APISIX文档-快速入门指南-快速入门指南 快速入门指南,概述,前提条件,第一步:安装 Apache APISIX,第二步:创建路由,第三步:验证,进阶操作,工作原理,创建上游服务Upstream,绑定路由与上游服务,添加身份验证,为路由添加前缀,APISIX Dashboard
  • Apache APISIX文档-架构设计-APISIX APISIX,软件架构,插件加载流程,插件内部结构,配置 APISIX,插件加载流程,比如指定 APISIX 默认监听端口为 8000,并且设置 etcd 地址为 http://foo:2379, 其他配置保持默认。在 ...
  • Apache APISIX文档-架构设计-Service Service 是某类 API 的抽象(也可以理解为一组 Route 的抽象)。它通常与上游服务抽象是一一对应的,Route 与 Service 之间,通常是 N:1 的关系,参看下图。不同 Route 规则同时绑定到一个 Service ...
  • Apache APISIX文档-架构设计-Plugin Config 如果你想要复用一组通用的插件配置,你可以把它们提取成一个 Plugin config,并绑定到对应的路由上。举个例子,你可以这么做:创建 Plugin config,如果这个路由已经配置了 plugins,那么 Plugin config ...
  • Apache APISIX文档-架构设计-Debug Mode 注意:在 APISIX 2.10 之前,开启基本调试模式曾经是设置 conf/config.yaml 中的 apisix.enable_debug 为 true。设置 conf/debug.yaml 中的选项,开启高级调试模式。由于 ...
  • Apache APISIX文档-架构设计-Consumer 如上图所示,作为 API 网关,需要知道 API Consumer(消费方)具体是谁,这样就可以对不同 API Consumer 配置不同规则。授权认证:比如有 [key-auth] 等。获取 consumer_...
  • 近期文章

    更多
    文章目录

      推荐作者

      更多