Doris 2.0 | 高并发点查询性能提升!

科技   2024-07-03 19:41   浙江  

背景

Doris 基于列存格式引擎构建,在高并发服务场景中,用户总是希望从系统中获取整行数据。但是,当表宽时,列存格式将大大放大随机读取 IO。Doris 查询引擎和计划对于某些简单的查询(如点查询)来说太重了。需要一个在 FE 的查询规划中规划短路径来处理这样的查询。FE 是 SQL 查询的访问层服务,使用 Java 编写,分析和解析 SQL 也会导致高并发查询的高 CPU 开销。为了解决上述问题,我们在 Doris 中引入了行存、短查询路径、PreparedStatement 来解决上诉问题,下面是开启这些优化的指南。

行存

用户可以在 Olap 表中开启行存模式,但是需要额外的空间来存储行存。目前的行存实现是将行存编码后存在单独的一列中,这样做是用于简化行存的实现。行存模式仅支持在建表的时候开启,需要在建表语句的 property 中指定如下属性:

"store_row_column" = "true"

在 Unique 模型下的点查优化

上述的行存用于在 Unique 模型下开启 Merge-On-Write 策略是减少点查时的 IO 开销。当enable_unique_key_merge_on_write与store_row_column在创建 Unique 表开启时,对于主键的点查会走短路径来对 SQL 执行进行优化,仅需要执行一次 RPC 即可执行完成查询。下面是点查结合行存在 在 Unique 模型下开启 Merge-On-Write 策略的一个例子:

CREATE TABLE `tbl_point_query` (
    `key` int(11) NULL,
    `v1` decimal(27, 9) NULL,
    `v2` varchar(30) NULL,
    `v3` varchar(30) NULL,
    `v4` date NULL,
    `v5` datetime NULL,
    `v6` float NULL,
    `v7` datev2 NULL
) ENGINE=OLAP
UNIQUE KEY(`key`)
COMMENT 'OLAP'
DISTRIBUTED BY HASH(`key`) BUCKETS 1
PROPERTIES (
    "replication_allocation" = "tag.location.default: 1",
    "enable_unique_key_merge_on_write" = "true",
    "light_schema_change" = "true",
    "store_row_column" = "true"
);

注意:

  1. enable_unique_key_merge_on_write应该被开启,存储引擎需要根据主键来快速点查

  2. 当条件只包含主键时,如select * from tbl_point_query where key = 123,类似的查询会走短路径来优化查询

  3. light_schema_change应该被开启,因为主键点查的优化依赖了轻量级 Schema Change 中的column unique id来定位列

  4. 只支持单表 key 列等值查询不支持 join、嵌套子查询, where 条件里需要有且仅有 key 列的等值,可以认为是一种 key value 查询

  5. 开启行存会导致空间膨胀,占用更多的磁盘空间,如果只需要查询部分列,在Doris 2.1后建议使用"row_store_columns"="key,v1,v2" 类似的方式指定部份列作为行存,查询的时候只查询这部份列,例如

SELECT key, v1, v2 FROM tbl_point_query WHERE key = 1

使用 PreparedStatement

为了减少 SQL 解析和表达式计算的开销,我们在 FE 端提供了与 MySQL 协议完全兼容的PreparedStatement特性(目前只支持主键点查)。当PreparedStatement在 FE 开启,SQL 和其表达式将被提前计算并缓存到 Session 级别的内存缓存中,后续的查询直接使用缓存对象即可。当 CPU 成为主键点查的瓶颈,在开启 PreparedStatement 后,将会有 4 倍 + 的性能提升。下面是在 JDBC 中使用 PreparedStatement 的例子

  1. 设置 JDBC url 并在 Server 端开启 prepared statement
url = jdbc:mysql://127.0.0.1:9030/ycsb?useServerPrepStmts=true
  1. 使用 PreparedStatement
// use `?` for placement holders, readStatement should be reused
PreparedStatement readStatement = conn.prepareStatement("select * from tbl_point_query where key = ?");
...
readStatement.setInt(1,1234);
ResultSet resultSet = readStatement.executeQuery();
...
readStatement.setInt(1,1235);
resultSet = readStatement.executeQuery();
...

开启行缓存

Doris 中有针对 Page 级别的 Cache,每个 Page 中存的是某一列的数据,所以 Page cache 是针对列的缓存,对于前面提到的行存,一行里包括了多列数据,缓存可能被大查询给刷掉,为了增加行缓存命中率,单独引入了行存缓存,行缓存复用了 Doris 中的 LRU Cache 机制来保障内存的使用,通过指定下面的的 BE 配置来开启

  • disable_storage_row_cache是否开启行缓存,默认不开启
  • row_cache_mem_limit指定 Row cache 占用内存的百分比,默认 20% 内存

性能优化

通常,通过增加 Observer 数量来提升处理 query 能力是有效的

query 负载均衡:点查中如果发现接受点查请求的 fe cpu 使用过高,或请求响应变慢,可使用 jdbc load balance 进行负载均衡,将请求分散到多个节点,分担压力(同时也可以使用其他方式进行 query 负载均衡配置,如 Nginx,proxySQL)

通过将点查请求定向发送至 Observer 角色来分担高并发点查的请求压力,减少向 fe master 发送点查请求,通常可以解决 Fe Master 节点查询耗时上下浮动问题,以获得更好性能与稳定性.

Q&A

  1. 如何确定配置无误使用了并发点查的短路径优化

A:explain sql,当执行计划中出现 SHORT-CIRCUIT,证明使用了短路径优化

mysql> explain select * from tbl_point_query where `key` = -2147481418 ;                                                                                                                                
      +-----------------------------------------------------------------------------------------------+                                                                                                       
      | Explain String(Old Planner)                                                                   |                                                                                                       
      +-----------------------------------------------------------------------------------------------+                                                                                                       
      | PLAN FRAGMENT 0                                                                               |                                                                                                       
      |   OUTPUT EXPRS:                                                                               |                                                                                                       
      |     `test`.`tbl_point_query`.`key`                                                            |                                                                                                       
      |     `test`.`tbl_point_query`.`v1`                                                             |                                                                                                       
      |     `test`.`tbl_point_query`.`v2`                                                             |                                                                                                       
      |     `test`.`tbl_point_query`.`v3`                                                             |                                                                                                       
      |     `test`.`tbl_point_query`.`v4`                                                             |                                                                                                       
      |     `test`.`tbl_point_query`.`v5`                                                             |                                                                                                       
      |     `test`.`tbl_point_query`.`v6`                                                             |                                                                                                       
      |     `test`.`tbl_point_query`.`v7`                                                             |                                                                                                       
      |   PARTITION: UNPARTITIONED                                                                    |                                                                                                       
      |                                                                                               |                                                                                                       
      |   HAS_COLO_PLAN_NODE: false                                                                   |                                                                                                       
      |                                                                                               |                                                                                                       
      |   VRESULT SINK                                                                                |                                                                                                       
      |      MYSQL_PROTOCAL                                                                           |                                                                                                       
      |                                                                                               |                                                                                                       
      |   0:VOlapScanNode                                                                             |                                                                                                       
      |      TABLE: test.tbl_point_query(tbl_point_query), PREAGGREGATION: ON                         |                                                                                                       
      |      PREDICATES: `key` = -2147481418 AND `test`.`tbl_point_query`.`__DORIS_DELETE_SIGN__` = 0 |                                                                                                       
      |      partitions=1/1 (tbl_point_query), tablets=1/1, tabletList=360065                         |                                                                                                       
      |      cardinality=9452868, avgRowSize=833.31323, numNodes=1                                    |                                                                                                       
      |      pushAggOp=NONE                                                                           |                                                                                                       
      |      SHORT-CIRCUIT                                                                            |                                                                                                       
      +-----------------------------------------------------------------------------------------------+
  1. 如何确定 prepared statement 生效

A:当发送请求到 Doris 之后,在 fe.audit.log 中找到相应的 query 请求,发现 Stmt=EXECUTE() ,说明 prepared statement 生效

  1. 非主键查询能否使用到高并发点查的特殊优化

A:不能,高并发点查只针对于 key 列的等值查询,且查询中不能包含 join,嵌套子查询

  1. useServerPrepStmts 在普通查询中是否有用

A:Prepared Statement 目前只在主键点查的情况下生效

  1. 优化器选择需要进行全局设置吗

A:在使用 prepared statement 进行查询时,Doris 会选择性能最好的查询方式,不需要手动设置优化器


300万字!全网最全大数据学习面试社区等你来!


如果这个文章对你有帮助,不要忘记 「在看」 「点赞」 「收藏」 三连啊喂!

全网首发|大数据专家级技能模型与学习指南(胜天半子篇)
互联网最坏的时代可能真的来了
我在B站读大学,大数据专业
我们在学习Flink的时候,到底在学习什么?
193篇文章暴揍Flink,这个合集你需要关注一下
Flink生产环境TOP难题与优化,阿里巴巴藏经阁YYDS
Flink CDC我吃定了耶稣也留不住他!| Flink CDC线上问题小盘点
我们在学习Spark的时候,到底在学习什么?
在所有Spark模块中,我愿称SparkSQL为最强!
硬刚Hive | 4万字基础调优面试小总结
数据治理方法论和实践小百科全书
标签体系下的用户画像建设小指南
4万字长文 | ClickHouse基础&实践&调优全视角解析
【面试&个人成长】社招和校招的经验之谈
大数据方向另一个十年开启 |《硬刚系列》第一版完结
我写过的关于成长/面试/职场进阶的文章
当我们在学习Hive的时候在学习什么?「硬刚Hive续集」

大数据技术与架构
王知无,大数据卷王,专注大数据技术分享。
 最新文章