Hologres学习

Hologres

一.什么是实时数仓Hologres

Hologres是阿里巴巴自主研发的一站式实时数仓引擎(Real-Time Data Warehouse),支持海量数据实时写入、实时更新、实时加工、实时分析,支持标准SQL(兼容PostgreSQL协议和语法,支持大部分PostgreSQL函数),支持PB级数据多维分析(OLAP)与即席分析(Ad Hoc),支持高并发低延迟的在线数据服务(Serving),支持多种负载的细粒度隔离与企业级安全能力,与MaxCompute、Flink、DataWorks深度融合,提供企业级离在线一体化全栈数仓解决方案。

Hologres致力于高性能、高可靠、低成本、可扩展的实时数仓引擎研发,为用户提供海量数据的实时数据仓库解决方案和亚秒级交互式查询服务,广泛应用在实时数据中台建设、精细化分析、自助式分析、营销画像、人群圈选、实时风控等场景。

二.功能特性

1.多场景查询分析

Hologres支持行存、列存、行列共存等多种存储模式和索引类型,同时满足简单查询、复杂查询、即席查询等多样化的分析查询需求。Hologres使用大规模并行处理架构,分布式处理SQL,提高资源利用率,实现海量数据极速分析。

2.亚秒级交互式分析

Hologres采用可扩展的大规模并行处理(MPP)架构全并行计算,通过向量化算子发挥CPU最佳算力,基于AliORC压缩存储,面向SSD存储优化IO吞吐,支持PB级数据亚秒级交互式分析体验。

3.在线高性能主键点查

基于行存表的主键索引和查询引擎的短路径优化,Hologres支持每秒数十万QPS高性能在线点查、前缀扫描,支持高吞吐实时更新,相比开源系统性能提升10倍以上,可用于实时加工链路的维表关联、ID-Mapping等场景。

4.联邦查询,数据湖加速

Hologres无缝对接MaxCompute,支持外部表透明加速查询和元数据自动导入,相比原生MaxCompute访问加速5-10倍,支持冷热数据关联分析,同时支持MaxCompute与Hologres之间百万行每秒高速同步,支持OSS数据湖格式读写,简化数据入湖入仓。

5.半结构数据分析

原生支持半结构化JSON数据类型,支持JSONB列式存储压缩,支持丰富的JSON相关表达算子,使JSON数据存储和分析效率接近原生列存效率。

6.原生实时数仓

针对实时数仓数据更新频繁、数据模型简单和分析场景敏捷的特性,Hologres支持高并发实时写入与更新,支持事务隔离与原子性,数据写入即可查。

7.高吞吐实时写入与更新

Hologres与Flink、Spark等计算框架原生集成,通过内置Connector,支持高通量数据实时写入与更新,支持源表、结果表、维度表多种场景,支持多流合并等复杂操作。

8.所见即所得的开发

数据实时写入即可查询,支持DB、Schema、Table三级体系,支持视图View,原生支持Update/Delete/Upsert,支持关联、嵌套、窗口等丰富表达能力,原生支持半结构化JSON数据分析,支持MySQL等数据库数据整库一键入库,实时同步。

9.全链路事件驱动

支持表更新事件的Binlog透出能力,通过Flink消费Hologres Binlog,实现数仓层次间全链路实时开发,满足分层治理的前提下,缩短数据加工端到端延迟。

10.实时物化视图

支持定义实时物化视图,简化数据加工聚合等开发,数据实时写入,聚合实时更新,完善支持实时加工场景。

11.企业级运维能力

支持计算负载、访问权限等细粒度管控要求,提供丰富的监控和告警指标,支持计算资源弹性扩展,支持系统热升级,满足企业级安全可靠的运维需求。

12.数据安全

支持细粒度访问控制策略,支持BYOK数据存储加密和数据脱敏,支持数据保护伞、IP白名单,支持RAM、STS及独立账号等多种认证体系,通过PCI-DSS安全认证。支持数据备份与恢复。

13.负载隔离

多个计算实例组成一主多从模式,实例间共享一份存储,计算资源隔离,实现写入和读取隔离,查询和服务隔离,实现故障管理,支持故障节点快速自动恢复。无需本地盘,盘古三副本高可靠冗余存储。

14.自运维能力

内置查询历史、元仓表等运维诊断信息,用户可以基于查询历史和表的元数据,快速定位系统瓶颈和风险点,提升自运维能力。

15.生态与可扩展性

兼容PostgreSQL生态,与大数据计算引擎及大数据智能研发平台DataWorks无缝打通。无需额外学习,即可上手开发。

16.兼容PostgreSQL生态

Hologres兼容PostgreSQL生态,提供JDBC/ODBC接口,轻松对接第三方ETL和BI工具,包括Quick BI、DataV、Tableau、帆软等。支持GIS空间数据分析,支持Oracle函数扩展包。

17.DataWorks开发集成

Hologres与DataWorks深度集成,提供图形化、智能化、一站式的数仓搭建和交互式分析服务工具,支持数据资产、数据血缘、数据实时同步、数据服务等企业级能力。

18.Hadoop生态集成

支持Hive/Spark Connector,通过Hadoop平台加工的数据可以高吞吐导入Hologres,并对外提供服务。支持加速读取外部表OSS-HDFS格式存储,支持Hudi、Delta等存储格式。

19.达摩院Proxima向量检索

Hologres与机器学习平台PAI紧密结合,内置达摩院Proxima向量检索插件,支持在线实时特征存储、实时召回、向量检索。

三.产品架构

1.架构优势

1.1.在传统的分布式系统中,常用的存储计算架构有如下三种。

1.1.1.Shared Disk/Storage (共享存储)

有一个分布式的存储集群,每个计算节点像访问单机数据一样访问这个共享存储上的数据。这种架构的存储层可以比较方便的扩展,但是计算节点需要引入分布式协调机制保证数据同步和一致性,因此计算节点的可扩展性有一个上限。

1.1.2.Shared Nothing

每个计算节点自己挂载存储,一个节点只能处理一个分片的数据,节点之间可以通信,最终有一个汇总节点对数据进行汇总。这种架构能比较方便的扩展,但是它的缺点是节点Failover需要等待数据加载完成之后才能提供服务;并且存储和计算需要同时扩容,不够灵活,扩容后,有漫长的数据Rebalance过程。

1.1.3.Storage Disaggregation(存储计算分离架构)

存储和Shared Storage类似,有一个分布式的共享存储集群,计算层处理数据的模式和Shared Nothing类似,数据是分片的,每个Shard只处理自己所在分片的数据,每个计算节点还可以有本地缓存。

1.2.存储计算分离的架构存在以下优势。

1.2.1.一致性问题处理简单:计算层只需要保证同一时刻有一个计算节点写入同一分片的数据。

1.2.2.扩展更灵活:计算和存储可以分开扩展,计算不够扩计算节点,存储不够扩存储节点。这样在大促等场景上会非常灵活。计算资源不够了,马上扩容计算就好了,不需要像Shared Nothing那样做耗时耗力的数据Rebalance;也不会像Shared Storage那样,出现单机的存储容量瓶颈。

1.2.3.计算节点故障恢复快:计算节点发生Failover之后,数据可以按需从分布式的共享存储异步拉取。因此Failover的速度非常快。

Hologres采用的是第三种存储计算分离架构,Hologres的存储使用的是阿里自研的Pangu分布式文件系统(类似HDFS)。用户可以根据业务需求进行弹性扩缩容,轻松应对在线系统不同的流量峰值。

  1. 架构组件介绍

2.1.Hologres架构图如下所示。

整个架构从上往下分为如下组件。

2.1.1.计算层

2.1.1.1.接入节点(Frontend,FE):Hologres接入节点,主要用于SQL的认证、解析、优化,一个实例有多个FE接入节点。在生态上兼容Postgres 11,因此用户可以使用Postgres标准语法进行开发,也可以用Postgres兼容的开发工具和BI工具直接连接Hologres。

2.1.1.2.计算HoloWorker

HoloWorker分为执行引擎、存储引擎、调度等组件,主要负责用户任务的计算、调度。

其中执行引擎(Query Engine,QE)主要有三个,执行引擎的技术原理请参见Hologres执行引擎技术揭秘

2.1.1.3.HQE(Hologres Query Engine)

Hologres自研执行引擎,采用可扩展的MPP架构全并行计算,向量化算子发挥CPU极致算力,从而实现极致的查询性能。(QE主要由HQE组成)。

2.1.1.4.PQE(Postgres Query Engine)

用于兼容Postgres提供扩展能力,支持PG生态的各种扩展组件,如PostGIS,UDF(PL/JAVA,PL/SQL,PL/Python)等。部分HQE还没有支持的函数和算子,会通过PQE执行,每个版本都在持续优化中,最终目标是去掉PQE。

2.1.1.5.SQE(Seahawks Query Engine)

无缝对接MaxCompute(ODPS)的执行引擎,实现对MaxCompute的本地访问,无需迁移和导入数据,就可以高性能和全兼容的访问各种MaxCompute文件格式,以及Hash/Range clustered table等复杂表,实现对PB级离线数据的交互式分析,技术原理请参见Hologres加速查询MaxCompute技术揭秘

2.1.1.6.存储引擎Storage Engine(SE)

主要用于管理和处理数据, 包括创建、查询、更新和删除(简称 CRUD)数据等,关于存储引擎详细的技术原理请参见Hologres存储引擎技术揭秘

2.1.1.7.Cache(缓存)

主要是结果缓存,提高查询性能。

2.1.1.8.HOS Scheduler

2.1.1.9轻量级调度

2.1.1.10.Meta Service

主要用于管理元数据Meta信息(包括表结构信息以及数据在Storage Engine节点上的分布情况),并将Meta信息提供给FE节点。

2.1.1.11.Holo Master

Hologres原生部署在K8s上,当某个Worker出现故障时,由K8s进行快速拉起创建一个新的Worker,保障Worker级别的可用性。在Worker内部,每个组件的可用性则由Holo Master负责,当组件出现状态不正常时,Holo Master则会快速重新拉起组件,从而恢复服务,高可用技术原理请参见Hologres高可用技术揭秘

2.2.存储层

2.2.1.数据直接存储在Pangu File System

MaxCompute在存储层打通,能直接访问MaxCompute存储在盘古的数据,实现高效相互访问。

支持直接访问OSSDLF数据,类型包含CSVORCParquetHudiDeltaMeta Data等,加速数据湖探索,也可以将数据回流至OSS,降低存储成本。

四.执行引擎

1.执行引擎优势

Hologres的执行引擎(主要以HQE为主)是自研的执行引擎,通过与大数据领域最新技术结合,实现了对各种查询类型的高性能处理,主要具有如下优势。

1.1.分布式执行

执行引擎是一个和存储计算分离架构配合的分布式执行模型。执行计划由异步算子组成的执行图DAG(有向无环图)表示,可以表达各种复杂查询,并且完美适配Hologres的数据存储模型,方便对接查询优化器,利用各种查询优化技术。

1.2.全异步执行

端到端的全异步处理框架,可以避免高并发系统的瓶颈,充分利用资源,并且最大可能地避免存储计算分离系统带来的读数据延迟的影响。

1.3.向量化和列处理

算子内部处理数据时最大可能地使用向量化执行,与存储引擎深度集成,通过灵活的执行模型,充分利用各种索引,最大化地延迟向量物化和延迟计算,避免不必要的读数据和计算。

1.4.自适应增量处理

对常见实时数据应用查询模式进行自适应增量处理。

1.5.特定查询深度优化

对一些特定查询模式的独特优化。更多技术原理请参见Hologres执行引擎揭秘2.Query执行过程

当客户端下发一个Query后,在执行引擎中实际上会有多个worker节点,以其中的一个worker节点为例,当客户端发起一个SQL后,执行过程如下。

2.1.Frontend(FE)节点对SQL进行解析和认证,并分发至执行引擎(Query Engine)的不同执行模块。

2.2.执行引擎(Query Engine)会根据SQL的特征走不同的执行路径。如果是点查/点写的场景,会跳过优化器(Query Optimizer,QO),直接分发至后端获取数据,减少数据传送链路,从而实现更优的性能。整个执行链路也叫Fixed Plan,点查(与HBase的KV查询)、点写场景会直接走Fixed Plan。

2.3.OLAP查询和写入场景:首先会由优化器(Query Optimizer,QO)对SQL进行解析,生成执行计划,在执行计划中会预估出算子执行Cost、统计信息、空间裁剪等。QO会通过生成的执行计划,决定使用HQE、PQE、SQE或者Hive QE对算子进行真正的计算。

2.4.HQE、PQE、SQE的对比

2.4.1.HQE(Hologres Query Engine):Hologres自研执行引擎,采用可扩展的MPP架构全并行计算,向量化算子发挥CPU极致算力,从而实现极致的查询性能。(QE主要由HQE组成)。

2.4.2.PQE(Postgres Query Engine)用于兼容Postgres提供扩展能力,支持PG生态的各种扩展组件,如PostGIS,UDF(PL/JAVA,PL/SQL,PL/Python)等。部分HQE还没有支持的函数和算子,会通过PQE执行,每个版本都在持续优化中,最终目标是去掉PQE。

2.4.3.SQE(Seahawks Query Engine)无缝对接MaxCompute(ODPS)的执行引擎,实现对MaxCompute的本地访问,无需迁移和导入数据,就可以高性能和全兼容的访问各种MaxCompute文件格式,以及Hash/Range clustered table等复杂表,实现对PB级离线数据的交互式分析,技术原理请参见Hologres加速查询MaxCompute技术揭秘

2.5.执行引擎决定正确的执行计划,然后会通过存储引擎(Storage Engine,SE)进行数据获取,最后对每个Shard上的数据进行合并,返回至客户端。

五.实时数仓Hologres的应用场景

1.Hologres的典型应用场景如下:

1.1.搭建实时数仓实时写入业务数据至实时计算后,使用ETL(Extract Transformation Load)方式清洗、转换及整理数据。您可以通过Hologres实时查询并输出数据至第三方分析工具进行实时分析。典型应用场景如下:数据部门搭建阿里云实时数仓产品、展示实时大屏和分析实时Reporting报表。运维和数据应用部门执行实时监控、实时异常检测预警与实时调试。业务部门进行实时风控、实时推荐、实时效果分析和实时训。

 

1.1.2.MaxCompute加速查询写入业务数据至离线数据仓库MaxCompute,通过Hologres直接加速查询或导入数据至Hologres查询,并对接BI分析工具,实现实时分析离线数据。典型应用场景如下:实时查询MaxCompute离线数据。分析MaxCompute离线数据报表。输出MaxCompute离线数据的在线应用,例如RESTful API的使用。   

1.3.联邦分析实时数据和离线数据业务数据分为冷数据和热数据,冷数据存储在离线数据仓库MaxCompute中,热数据存储在Hologres中。Hologres可以联邦分析实时数据和离线数据,对接BI分析工具,快速响应简单查询与复杂查询的业务需求。    

六.基本概念

1.账号

阿里云账号:阿里云账号即主账号,用于创建和管理Hologres实例,可以授权给其他用户管理员权限。主账号支持的表达格式如下表所示。

账号格式

描述

示例

ALIYUN$<AccountName>@aliyun.com

<AccountName>为主账号的登录名称。

ALIYUN$company@aliyun.com

<AccountName>@aliyun.com

company@aliyun.com

<Account ID>

主账号的ID。

1344445678xxx

账号格式

描述

示例

p4_<Account ID>

p使用小写字母。

<Account ID>为主账号的ID。

p4_12345678xxx

<subUserName>@<Account Name>.onaliyun.com

<subUserName>为子账号的登录名称。

<AccountName>为主账号的登录名称。

<AccountID>为主账号的ID。

holouser@company.onaliyun.com

<subUserName>@<Account Name>

<subUserName>@<Account ID>.onaliyun.com

RAM$<subUserName>

RAM$<AccountName>:<subUserName>

RAM$<Account ID>:<subUserName>

<subUserName>@<Account ID>

RAM用户:RAM用户即子账号。阿里云账号授予RAM用户一定权限后,RAM用户可以在权限范围内创建和管理实例中的数据库和表。RAM用户支持的表达格式如下表所示。

1.3.用户角色:超级管理员,系统默认设置购买实例的主账号为超级管理员Superuser。Superuser拥有整个实例的所有权限,例如创建数据库、删除数据库、创建角色以及为角色授权。普通用户:普通用户需要被Superuser授权后才能访问实例,并在权限范围内进行数据开发。普通用户也可以被授权为Superuser。实例:实例(Instance)是您使用和管理数据库存储服务的实体。您对数据库的所有操作都需要在实例中完成。1.4数据库数据库是按照数据结构来组织、存储和管理数据的仓库。您可以在数据库中创建、更新或删除表,以及执行与函数相关的操作。购买Hologres实例后,系统自动创建postgres数据库。该数据库分配到的资源较少,仅用于管理,开发实际业务需要新建数据库。新建数据库请参见CREATE DATABASE

1.5.SchemaSchema是数据库对象的集合,相当于文件系统中的目录。实例中的表和函数等对象存放于各个Schema中。成功创建数据库后,系统默认创建命名为public的Schema,您对实例对象的所有操作默认都在该Schema中执行。您也可以新建Schema,详情请参见CREATE SCHEMASchema不能嵌套Schema。同一个Schema中的对象名称不能重复。不同Schema中的对象名称可以重复。

1.6.表是由行和列组成的数据存储单元。列的数量和顺序是固定的,每一列拥有一个名称。行的数量是变化的,表示在给定的时刻,表中存储的数据量。表的每一列都有一个数据类型,表示该列的取值类型,详情请参见数据类型汇总Hologres中的表包括以下两种类型:内部表:指数据存储在Hologres中的表。表中的数据类型可以是Hologres支持的任意一种类型。外部表:指在Hologres中不存储数据,只进行字段映射的表。外部表的数据都是只读的,您在外部表中不能执行DML语句或创建索引。您可以在Hologres中创建外部表,加速查询外部数据源的数据,例如查询MaxCompute的数据。Hologres中创建外部表请参见CREATE FOREIGN TABLE

1.7.分区表分区表是根据键值属性划分父表为许多小的子集,这些子集称为分区。不同类型的数据存放于不同分区,Hologres当前仅支持对单个键值进行列表分区。查询数据时,您可以通过分区键值过滤查询语句,排除无关的分区,从而提升查询速度,Hologres中创建或删除分区表请参见CREATE PARTITION TABLEDROP PARTITION TABLE1.8引擎组件Hologres是一个分布式实时数仓,在内部会有很多组件,实际业务中,无需所有组件都有所了解,只需要在实际业务中了解需要使用的组件,助您更好的理解和使用Hologres。接入节点(Frontend,FE)Hologres接入节点,主要用于SQL的认证、解析、优化,一个实例有多个FE接入节点。在生态上兼容Postgres 11,因此您可以使用Postgres标准语法进行开发,也可以用Postgres兼容的开发工具和BI工具直接连接Hologres。执行引擎(Query Engine,QE)主要有3个,执行引擎的技术原理请参见Hologres执行引擎技术揭秘HQE(Hologres Query Engine):Hologres自研执行引擎,采用可扩展的MPP架构全并行计算,向量化算子发挥CPU极致算力,从而实现极致的查询性能。(QE主要由HQE组成)。PQE(Postgres Query Engine):用于兼容Postgres提供扩展能力,支持PG生态的各种扩展组件,如PostGIS,UDF(PL/JAVA,PL/SQL,PL/Python)等。部分HQE还没有支持的函数和算子,会通过PQE执行,每个版本都在持续优化中,终极目标是去掉PQE。SQE(Seahaws Query Engine):无缝对接MaxCompute的执行引擎,实现对MaxCompute的本地访问,无需迁移和导入数据,就可以高性能和全兼容的访问各种MaxCompute文件格式,以及Hash/Range clustered table等复杂表,实现对PB级离线数据的交互式分析,技术原理请参见Hologres加速查询MaxCompute技术揭秘。存储引擎Storage Engine(SE):主要用于管理和处理数据, 包括创建、查询、更新和删除(简称 CRUD)数据等,关于存储引擎详细的技术原理请参见Hologres存储引擎技术揭秘

七.Hologres推荐的数仓分层

1.背景信息

Hologres与Flink、MaxCompute、DataWorks深度兼容,能够提供实时离线一体化联合解决方案。在该方案下有着非常丰富的应用场景,例如实时大屏、实时风控、精细化运营等。不同的应用场景对处理的数据量、数据复杂度、数据来源、数据实时性等会有不一样的要求。传统数仓的开发按照经典的方法论,采用ODS(Operational Data Store) > DWD(Data Warehouse Detail) > DWS(Data WareHouse Summary) > ADS(Application Data Service)逐层开发的方法,层与层之间采用事件驱动,或者微批次的方式调度。分层带来更好的语义层抽象和数据复用,但也增加了调度的依赖、降低了数据的时效性、减少了数据灵活分析的敏捷性。

实时数仓驱动了业务决策的实时化,在决策时通常需要丰富的上下文信息,因此传统高度依据业务定制ADS的开发方法受到了较大挑战,成千上万的ADS表维护困难,利用率低,更多的业务方希望通过DWS甚至DWD进行多角度数据对比分析,这对查询引擎的计算效率、调度效率、IO效率都提出了更高的要求。

随着计算算子向量化重写、精细化索引、异步化执行、多级缓存等多种查询引擎优化技术的应用,Hologres的计算能力在每个版本都有较大改善。因此越来越多的用户采用了敏捷化的开发方式,在计算前置的阶段,只做数据质量清理、基本的大表关联拉宽,建模到DWD、DWS即可,减少建模层次。同时将灵活查询在交互式查询引擎中执行,通过秒级的交互式分析体验,支撑了数据分析民主化的重要趋势。为了满足业务场景的不同需求,建议您通过如下图所示三种方式进行数据分层和处理,以实现更加敏捷的开发需求。

1.1.场景一(即席查询,写入即服务):Flink中进行DWD数据明细层预加工,加工完的数据直接写入Hologres,由Hologres提供OLAP查询和在线服务。

1.2.场景二(分钟级准实时,微批次加工):Flink中进行DWD数据明细层预加工,写入Hologres后,在Hologres中进行汇聚层加工,再对接上层应用。

1.3.场景三(增量数据实时统计,事件驱动加工):DWD明细层预加工和DWS汇聚层预加工全部由Flink完成,写入Hologres提供上层应用。

2.场景选择原则

当数据写入Hologres之后,Hologres里定义了三种实现实时数仓的方式:实时要求非常高,要求写入即可查,更新即反馈,有即席查询需求,且资源较为充足,查询复杂度较低,适合实时数仓场景一:即席查询。有实时需求,以分析为主,实时性满足分析时数据在业务场景具备实时含义,不追求数据产生到分析的秒级绝对值,但开发效率优先,推荐分钟级准实时方案,这个方案适合80%以上的实时数仓场景,平衡了时效性与开发效率,适合实时数仓场景二:分钟级准实时。实时需求简单、数据更新少、只需要增量数据即可统计结果,以大屏和风控等在线服务场景为主,需要数据产生到分析尽量实时,可以接受一定开发效率的降低和计算成本的上升,适合实时数仓场景

3.增量数据实时统计

3.1.实时数仓场景一:即席查询,即席查询通俗来说就是不确定应用的具体查询模式,先把数据存下来,后续支撑尽量多灵活性的场景,如下图所示。

因此建议您应用如下策略:将操作层(ODS层)的数据经过简单的清理、关联,然后存储到明细数据,暂不做过多的二次加工汇总,明细数据直接写入Hologres。Flink加工增量数据,实时更新明细数据至Hologres,MaxCompute加工后的离线表写入Hologres。因为上层的分析SQL无法固化,在CDM/ADS层以视图(View)封装成SQL逻辑。上层应用直接查询封装好的View,实现即席查询。方案优势:灵活性强,可随时根据业务逻辑调整View。指标修正简单,上层都是View逻辑封装,只需要刷新一层数据,更新底表的数据即可,因为上层没有汇聚表,无需再次更新上层应用表。方案缺点:当View的逻辑较为复杂,数据量较多时,查询性能较低。适用场景:数据来源于数据库和埋点系统,适合对QPS要求不高,对灵活性要求比较高,且计算资源较为充足的场景。3.2.实时数仓场景二:分钟级准实时

场景一的计算效率在某些场景上还存在不足,无法支撑更高的QPS,场景二是场景一的升级,把场景一中视图的部分物化成表,逻辑与场景一相同,但是最终落在表上的数据量变少,显著提升查询性能,可以获得更高的QPS,如下图所示。

建议您应用如下策略:将操作层(ODS层)的数据经过简单的清理、关联,然后存储到明细数据,暂不做过多的二次加工汇总,明细数据直接写入Hologres。Flink加工增量数据实时更新明细数据至Hologres。CDM/ADS层为实际的物理表,通过DataWorks等调度工具调度周期性写入数据。前端实时请求实际的物理表,数据的实时性依赖DataWorks调度周期配置,例如5分钟调度、10分钟调度等,实现分钟级准实时。方案优势:查询性能强,上层应用只查最后汇总的数据,相比View,查询的数据量更好,性能会更强。数据重刷快,当某一个环节或者数据有错误时,重新运行DataWorks调度任务即可。因为所有的逻辑都是固化好的,无需复杂的订正链路操作。业务逻辑调整快,当需要新增或者调整各层业务,可以基于SQL所见即所得开发对应的业务场景,业务上线周期缩短。方案缺点:时效性低于方案一,因为引入了更多的加工和调度。

适用场景:数据来源于数据库和埋点系统,对QPS和实时性均有要求,适合80%实时数仓场景使用,能满足大部分业务场景需求。

3.3.实时数仓场景三:增量数据实时统计

增量计算的场景是因为一些场景对数据延迟非常敏感,数据产生的时候必须完成加工,此时通过增量计算的方式,提前用Flink将明细层、汇总层等层数据进行汇聚,汇聚之后把结果集存下来再对外提供服务,如下图所示。

在增量计算中,建议您应用如下策略:增量计算的数据由Flink进行清洗加工转换和聚合汇总,ADS层应用数据存储在Hologres中。Flink加工的结果集采取双写的方式,一方面继续投递给下一层消息流Topic,一方面Sink到同层的Hologres中,方便后续历史数据的状态检查与刷新。在Flink内通过增量流、增量流连接静态维表、增量流连接增量流这三种场景统计出数据,写入Hologres。Hologres通过表的形式直接对接上层应用,实现应用实时查询。方案优势:实时性强,能满足业务对实时性敏感的场景。指标修正简单,与传统增量计算方式不一样的是,该方案将中间的状态也持久储在Hologres中,提升了后续分析的灵活性,当中间数据质量有问题时,直接对表修正,重刷数据即可。方案缺点:大部分实时增量计算都依赖Flink,对使用者Flink的技能和熟练度要求会更高一些;不适合数据频繁更新,无法累加计算的场景,不适合多流Join等计算复杂资源开销大场景。适用场景:实时需求简单,数据量不大,以埋点数据统计为主的数据,只需要增量数据即可统计结果的场景,实时性最强。

八.基于Flink+Hologres搭建实时数仓

1.背景信息

随着社会数字化发展,企业对数据时效性的需求越来越强烈。除传统的面向海量数据加工场景设计的离线场景外,大量业务需要解决面向实时加工、实时存储、实时分析的实时场景问题。传统离线数仓搭建的方法论比较明确,通过定时调度实现数仓分层(ODS->DWD->DWS->ADS);但对于实时数仓的搭建,目前缺乏明确的方法体系。基于Streaming Warehouse理念,实现数仓分层之间实时数据的高效流动,可以解决实时数仓分层问题。

2.方案架构

实时计算Flink版是强大的流式计算引擎,支持对海量实时数据高效处理。Hologres是一站式实时数仓,支持数据实时写入与更新,实时数据写入即可查。Hologres与Flink深度集成,能够提供一体化的实时数仓联合解决方案。本文基于Flink+Hologres搭建实时数仓的方案架构如下:Flink将数据源写入Hologres,形成ODS层。Flink订阅ODS层的Binlog进行加工,形成DWD层再次写入Hologres。Flink订阅DWD层的Binlog,通过计算形成DWS层,再次写入Hologres。最后由Hologres对外提供应用查询。

 

该方案有如下优势:Hologres的每一层数据都支持高效更新与修正、写入即可查,解决了传统实时数仓解决方案的中间层数据不易查、不易更新、不易修正的问题,Hologres的每一层数据都可单独对外提供服务,数据的高效复用,真正实现数仓分层复用的目标。模型统一,架构简化。实时ETL链路的逻辑是基于Flink SQL实现的;ODS层、DWD层和DWS层的数据统一存储在Hologres中,可以降低架构复杂度,提高数据处理效率。该方案依赖于Hologres的3个核心能力,详情如下表所示。

 

Hologres核心能力

详情

Binlog

Hologres提供Binlog能力,用于驱动Flink进行实时计算,以此作为流式计算的上游。Hologres的Binlog能力详情请参见订阅Hologres Binlog

行列共存

Hologres支持行列共存的存储格式。一张表同时存储行存数据和列存数据,并且两份数据强一致。该特性保证中间层表不仅可以作为Flink的源表,也可以作为Flink的维表进行主键点查与维表Join,还可以供其他应用(OLAP、线上服务等)查询。Hologres的行列共存能力详情请参见表存储格式:列存、行存、行列共存

资源强隔离

Hologres实例的负载较高时,可能影响中间层的点查性能。Hologres支持通过主从实例读写分离部署(共享存储)计算组实例架构实现资源强隔离,从而保证Flink对Hologres Binlog的数据拉取不影响线上服务。

3.实践场景

本文以某个电商平台为例,通过搭建一套实时数仓,实现数据的实时加工清洗和对接上层应用数据查询,形成实时数据的分层和复用,支撑各个业务方的报表查询(交易大屏、行为数据分析、用户画像标签)以及个性化推荐等多个业务场景。构建ODS层:业务数据库实时入仓:MySQL有orders(订单表),orders_pay(订单支付表),product_catalog(商品类别字典表)3张业务表,这3张表通过Flink实时同步到Hologres中作为ODS层。

 

构建DWD层:实时主题宽表:将订单表、商品类别字典表、订单支付表进行实时打宽,生成DWD层宽表。

构建DWS层:实时指标计算:实时消费宽表的binlog,事件驱动的聚合出相应的DWS层指标表。

前提条件已购买独享通用型Hologres实例,详情请参见购买Hologres。购买实例后,需要创建order_dw数据库和用户(为用户赋予admin权限),推荐使用简单权限模型创建数据库,详情请参见简单权限模型的使用DB管理。如果在被授权账号的下拉列表找不到对应的账号,则说明该账号并未添加至当前实例,您需要前往用户管理页面添加用户为SuperUser。Hologres1.3版本在创建完数据库后,需要执行create extension hg_binlog命令才能开启binlog扩展。Hologres2.0之后版本默认开启binlog扩展,无需手动执行,已开通Flink全托管,详情请参见开通Flink全托管。说明:Flink全托管需要与Hologres实例处于同一VPC。已准备MySQL CDC数据源,order_dw数据库中的三张业务表的建表DDL以及插入的数据如下。

CREATE TABLE `orders` (

  order_id bigint not null primary key,

  user_id varchar(50) not null,

  shop_id bigint not null,

  product_id bigint not null,

  buy_fee numeric(20,2) not null,   

  create_time timestamp not null,

  update_time timestamp not null default now(),

  state int not null 

);

 

CREATE TABLE `orders_pay` (

  pay_id bigint not null primary key,

  order_id bigint not null,

  pay_platform int not null,

  create_time timestamp not null

);

 

CREATE TABLE `product_catalog` (

  product_id bigint not null primary key,

  catalog_name varchar(50) not null

);

-- 准备数据INSERT INTO product_catalog VALUES(1, 'phone_aaa'),(2, 'phone_bbb'),(3, 'phone_ccc'),(4, 'phone_ddd'),(5, 'phone_eee');

INSERT INTO orders VALUES

(100001, 'user_001', 12345, 1, 5000.05, '2023-02-15 16:40:56', '2023-02-15 18:42:56', 1),

(100002, 'user_002', 12346, 2, 4000.04, '2023-02-15 15:40:56', '2023-02-15 18:42:56', 1),

(100003, 'user_003', 12347, 3, 3000.03, '2023-02-15 14:40:56', '2023-02-15 18:42:56', 1),

(100004, 'user_001', 12347, 4, 2000.02, '2023-02-15 13:40:56', '2023-02-15 18:42:56', 1),

(100005, 'user_002', 12348, 5, 1000.01, '2023-02-15 12:40:56', '2023-02-15 18:42:56', 1),

(100006, 'user_001', 12348, 1, 1000.01, '2023-02-15 11:40:56', '2023-02-15 18:42:56', 1),

(100007, 'user_003', 12347, 4, 2000.02, '2023-02-15 10:40:56', '2023-02-15 18:42:56', 1);

INSERT INTO orders_pay VALUES

(2001, 100001, 1, '2023-02-15 17:40:56'),

(2002, 100002, 1, '2023-02-15 17:40:56'),

(2003, 100003, 0, '2023-02-15 17:40:56'),

(2004, 100004, 0, '2023-02-15 17:40:56'),

(2005, 100005, 0, '2023-02-15 18:40:56'),

(2006, 100006, 0, '2023-02-15 18:40:56'),

(2007, 100007, 0, '2023-02-15 18:40:56');

使用限制仅实时计算引擎VVR 6.0.7及以上版本支持该实时数仓方案。仅1.3及以上版本的Hologres支持该实时数仓方案。

4.建实时数仓

4.1.创建Catalog 创建Hologres Catalog。在实时计算控制台上,新建一个名为test的SQL作业,将如下代码拷贝到test作业的SQL编辑器上,修改目标参数取值后,选中代码片段后单击左侧代码行上的运行。

CREATE CATALOG dw WITH (

  'type' = 'hologres',

  'endpoint' = '<ENDPOINT>',

  'username' = '<USERNAME>',

  'password' = '${secret_values.ak_holo}',

  'dbname' = 'order_dw',

  'binlog' = 'true', -- 创建catalog时可以设置源表、维表和结果表支持的with参数,之后在使用此catalog下的表时会默认添加这些默认参数。

  'sdkMode' = 'jdbc', -- 推荐使用jdbc模式。

  'cdcmode' = 'true',

  'connectionpoolname' = 'the_conn_pool',

  'ignoredelete' = 'true',  -- 宽表merge需要开启,防止回撤。

  'partial-insert.enabled' = 'true', -- 宽表merge需要开启此参数,实现部分列更新。

  'mutateType' = 'insertOrUpdate', -- 宽表merge需要开启此参数,实现部分列更新。

  'table_property.binlog.level' = 'replica', --也可以在创建catalog时传入持久化的hologres表属性,之后创建表时,默认都开启binlog。

  'table_property.binlog.ttl' = '259200'

);您需要修改以下参数取值为您实际Hologres服务信息。

 

参数

说明

备注

endpoint

HologresEndpoint地址。

详情请参见实例配置

username

阿里云账号的AccessKey ID

当前配置的AccessKey对应的用户需要能够访问所有的Hologres数据库,Hologres数据库权限请参见Hologres权限模型概述

为了避免您的AK信息泄露,本示例通过使用名为ak_holo密钥的方式填写AccessKey Secret取值,详情请参见密钥管理

password

阿里云账号的AccessKey Secret

创建Catalog时可以设置默认的源表、维表和结果表的WITH参数,也可以设置创建Hologres物理表的默认属性,例如上方table_property开头的参数。详情请参见管理Hologres Catalog实时数仓Hologres WITH参数

4.2.创建MySQL Catalog

实时计算控制台,将如下代码拷贝到test作业的SQL编辑器上,修改目标参数取值后,选中代码片段后单击左侧代码行上的运行。

CREATE CATALOG mysqlcatalog WITH(

  'type' = 'mysql',

  'hostname' = '<hostname>',

  'port' = '<port>',

  'username' = '<username>',

  'password' = '${secret_values.mysql_pw}',

  'default-database' = 'order_dw'

);您需要修改以下参数取值为您实际MySQL服务信息。

参数

说明

hostname

MySQL数据库的IP地址或者Hostname

port

MySQL数据库服务的端口号,默认值为3306

username

MySQL数据库服务的用户名。

password

MySQL数据库服务的密码。

本示例通过使用名为mysql_pw密钥的方式填写密码取值,避免信息泄露,详情请参见密钥管理

5.构建ODS层:业务数据库实时入仓

基于Catalog的CREATE DATABASE AS(CDAS)语句功能,可以一次性把ODS层建出来。ODS层一般不直接做OLAP或SERVING(KV点查),主要作为流式作业的事件驱动,开启binlog即可满足需求。

5.1.创建CDAS同步作业ODS

实时计算控制台上,新建名为ODS的SQL流作业,并将如下代码拷贝到SQL编辑器。CREATE DATABASE IF NOT EXISTS dw.order_dw   -- 创建catalog时设置了table_property.binlog.level参数,因此通过CDAS创建的所有表都开启了binlog。AS DATABASE mysqlcatalog.order_dw INCLUDING all tables -- 可以根据需要选择上游数据库需要入仓的表。/*+ OPTIONS('server-id'='8001-8004') */ ;   -- 指定mysql-cdc源表。说明本示例默认将数据同步到数据库order_dw的Public Schema下。您也可以将数据同步到Hologres目标库的指定Schema中,详情请参见作为CDAS的目标端Catalog,指定后使用Catalog时的表名格式也会发生变化,详情请参见使用Hologres Catalog

查看MySQL同步到Hologres的3张表数据。在HoloWeb开发页面连接Hologres实例并登录目标数据库后,在SQL编辑器上执行如下命令。

--查orders中的数据。SELECT * FROM orders;

---查orders_pay中的数据。SELECT * FROM orders_pay;

---查product_catalog中的数据。SELECT * FROM product_catalog;

 

6. 构建DWD层:实时主题宽表

通过Flink Catalog功能在Hologres中建DWD层的宽表dwd_orders。在实时计算控制台上,将如下代码拷贝到test作业的SQL编辑器后,选中目标片段后单击左侧代码行上的运行:-- 宽表字段要nullable,因为不同的流写入到同一张结果表,每一列都可能出现null的情况。CREATE TABLE dw.order_dw.dwd_orders (

  order_id bigint not null,

  order_user_id string,

  order_shop_id bigint,

  order_product_id bigint,

  order_product_catalog_name string,

  order_fee numeric(20,2),

  order_create_time timestamp,

  order_update_time timestamp,

  order_state int,

  pay_id bigint,

  pay_platform int comment 'platform 0: phone, 1: pc',

  pay_create_time timestamp,

  PRIMARY KEY(order_id) NOT ENFORCED

);

-- 支持通过catalog修改Hologres物理表属性。ALTER TABLE dw.order_dw.dwd_orders SET (

  'table_property.binlog.ttl' = '604800' --修改binlog的超时时间为一周。

);

实现实时消费ODS层orders、orders_pay表的binlog。在实时计算控制台上,新建名为DWD的SQL作业,并将如下代码拷贝到SQL编辑器后,部署并启动作业。通过如下SQL作业,orders表会与product_catalog表进行维表关联,将最终结果写入dwd_orders表中,实现数据的实时打宽。

BEGIN STATEMENT SET;

INSERT INTO dw.order_dw.dwd_orders

 (

   order_id,

   order_user_id,

   order_shop_id,

   order_product_id,

   order_fee,

   order_create_time,

   order_update_time,

   order_state,

   order_product_catalog_name

 ) SELECT o.*, dim.catalog_name

   FROM dw.order_dw.orders as o

   LEFT JOIN dw.order_dw.product_catalog FOR SYSTEM_TIME AS OF proctime() AS dim

   ON o.product_id = dim.product_id;

INSERT INTO dw.order_dw.dwd_orders

  (pay_id, order_id, pay_platform, pay_create_time)

   SELECT * FROM dw.order_dw.orders_pay;

END;

查看宽表dwd_orders数据。在HoloWeb开发页面连接Hologres实例并登录目标数据库后,在SQL编辑器上执行如下命令。

SELECT * FROM dwd_orders;

 

7. 构建DWS层:实时指标计算

通过Flink Catalog功能,在Hologres中创建dws层的聚合dws_users以及dws_shops。在实时计算控制台上,将如下代码拷贝到test作业的SQL编辑器,选中目标片段后单击左侧代码行上的运行。

-- 用户维度聚合指标表。CREATE TABLE dw.order_dw.dws_users (

  user_id string not null,

  ds string not null,

  paied_buy_fee_sum numeric(20,2) not null comment '当日完成支付的总金额',

  primary key(user_id,ds) NOT ENFORCED

);

-- 商户维度聚合指标表。CREATE TABLE dw.order_dw.dws_shops (

  shop_id bigint not null,

  ds string not null,

  paied_buy_fee_sum numeric(20,2) not null comment '当日完成支付总金额',

  primary key(shop_id,ds) NOT ENFORCED

);

实时消费DWD层的宽表dw.order_dw.dwd_orders,在Flink中做聚合计算,最终写入Hologres中的DWS表。在实时计算控制台上,新建名为DWS的SQL流作业,并将如下代码拷贝到SQL编辑器后,部署并启动作业。

BEGIN STATEMENT SET;

INSERT INTO dw.order_dw.dws_users

  SELECT 

    order_user_id,

    DATE_FORMAT (pay_create_time, 'yyyyMMdd') as ds,

    SUM (order_fee)

    FROM dw.order_dw.dwd_orders c

    WHERE pay_id IS NOT NULL AND order_fee IS NOT NULL -- 订单流和支付流数据都已写入宽表。

    GROUP BY order_user_id, DATE_FORMAT (pay_create_time, 'yyyyMMdd');

INSERT INTO dw.order_dw.dws_shops

  SELECT 

    order_shop_id,

    DATE_FORMAT (pay_create_time, 'yyyyMMdd') as ds,

    SUM (order_fee)

   FROM dw.order_dw.dwd_orders c

   WHERE pay_id IS NOT NULL AND order_fee IS NOT NULL -- 订单流和支付流数据都已写入宽表。

   GROUP BY order_shop_id, DATE_FORMAT (pay_create_time, 'yyyyMMdd');END;查看DWS层的聚合结果,其结果会根据上游数据的变更实时更新。在HoloWeb开发页面连接Hologres实例并登录目标数据库后,在SQL编辑器上执行如下命令。查询dws_users表结果。SELECT * FROM dws_users;

 

查询dws_shops表结果。SELECT * FROM dws_shops;

 

8.数据探查

如果对中间结果需要即系(Ad-hoc)性质的业务数据探查,或者对最终计算结果进行数据正确性排查,此方案的每一层数据都实现了持久化,可以便捷的探查中间过程。流模式探查:新建并启动数据探查流作业。在实时计算控制台上,新建名为Data-exploration的SQL流作业,并将如下代码拷贝到SQL编辑器后,部署并启动作业。-- 流模式探查,打印到print可以看到数据的变化情况。CREATE TEMPORARY TABLE print_sink(

  order_id bigint not null,

  order_user_id string,

  order_shop_id bigint,

  order_product_id bigint,

  order_product_catalog_name string,

  order_fee numeric(20,2),

  order_create_time timestamp,

  order_update_time timestamp,

  order_state int,

  pay_id bigint,

  pay_platform int,

  pay_create_time timestamp,

  PRIMARY KEY(order_id) NOT ENFORCED

) WITH (

  'connector' = 'print'

);

INSERT INTO print_sink SELECT *FROM dw.order_dw.dwd_orders /*+ OPTIONS('startTime'='2023-02-15 12:00:00') */ --这里的startTime是binlog生成的时间WHERE order_user_id = 'user_001';

查看数据探查结果。在作业运维详情页面,单击目标作业名称,在作业探查页签下左侧运行日志页签,单击运行Task Managers页签下的Path, ID。在Stdout页面搜索user_001相关的日志信息。

批模式探查:在实时计算控制台上,创建SQL流作业,并将如下代码拷贝到SQL编辑器后,单击调试。详情请参见作业调试。批模式探查是获取当前时刻的终态数据,在Flink作业开发界面调试结果如下图所示。

SELECT *FROM dw.order_dw.dwd_orders /*+ OPTIONS('binlog'='false') */ WHERE order_user_id = 'user_001' and order_create_time > '2023-02-15 12:00:00'; --批量模式支持filter下推,提升批作业执行效率。

 

9.使用实时数仓

上一小节展示了通过Flink Catalog,可以仅在Flink侧搭建一个基于Flink和Hologres的Streaming Warehouse实时分层数仓。本节则展示数仓搭建完成之后的一些简单应用场景。

9.1.Key-Value服务

根据主键查询DWS层的聚合指标表,支持百万级RPS。在HoloWeb开发页面查询指定用户指定日期的消费额的代码示例如下。

 -- holo sqlSELECT * FROM dws_users WHERE user_id ='user_001' AND ds = '20230215';

 

9.2.明细查询

DWD层宽表进行OLAP分析。在HoloWeb开发页面查询某个客户23年2月特定支付平台支付的订单明细的代码示例如下。-- holo sqlSELECT * FROM dwd_ordersWHERE order_create_time >= '2023-02-01 00:00:00'  and order_create_time < '2023-03-01 00:00:00'AND order_user_id = 'user_001'AND pay_platform = 0ORDER BY order_create_time LIMIT 100;

 

10.实时报表

基于DWD层宽表数据展示实时报表,支持秒级响应。在HoloWeb开发页面查询23年2月内每个品类的订单总量和订单总金额的代码示例如下。

 

-- holo sqlSELECT

  TO_CHAR(order_create_time, 'YYYYMMDD') AS order_create_date,

  order_product_catalog_name,

  COUNT(*),

  SUM(order_fee)FROM

  dwd_ordersWHERE

  order_create_time >= '2023-02-01 00:00:00'  and order_create_time < '2023-03-01 00:00:00'GROUP BY

  order_create_date, order_product_catalog_nameORDER BY

  order_create_date, order_product_catalog_name;

 

十.优化内部表的性能

更新统计信息

统计信息决定是否能够生成正确的执行计划。例如,Hologres需要收集数据的采样统计信息,包括数据的分布和特征、表的统计信息、列的统计信息、行数、列数、字段宽度、基数、频度、最大值、最小值、长键值、分桶分布特征等信息。这些信息将为优化器更新算子执行预估COST、搜索空间裁剪、估算最优Join Order、估算内存开销、估算并行度,从而生成更优的执行计划。关于统计信息更多的介绍,请参见Using Explain

统计信息的收集也存在一定局限,主要是针对非实时、手动触发或者周期性触发,不一定反映最准确的数据特征。您需要先检查explain的信息,查看explain中包含的统计信息是否正确。统计信息中每个算子的rows和width表示该算子的行数和宽度。

查看统计信息是否正确通过查看执行计划

未及时同步统计信息导致生成较差的执行计划,示例如下:mp1表的数据量为1000万行,tmp表的数据量为1000行。 Hologres默认统计信息中的行数为1000行,通过执行explainSQL语句,如下展示结果所示,tmp1表的行数与实际的行数不符,该展示结果表明未及时更新统计信息。Seq Scan on tmp1 (cost=0.00..5.01 rows=1000 width=1)

 

通过查看系统表您可以通过查系统表查看行数、宽度等是否正确。

查询系统表hologres.hg_table_properties中的analyze_tuple列,确认数据行数是否正确。或者直接查看Scan节点中rows的值。

查询系统表hologres.hg_stats可看到每一列的直方图、平均宽度、不同值的数量等信息,如下图所示。

更新统计信息

tmp1tmpJoin时,正确的explain信息展示为数据量大的表tmp1在数据量小的表tmp上方,Hash Join应该采用数据量小的tmp表。因为tmp1表未及时更新统计信息,导致Hologres选择tmp1表创建Hash表进行Hash Join,效率较低,并且可能造成OOM(Out Of Memory,内存溢出)。因此,需要参与Join的两张表均执行analyze收集统计信息,语句如下。

 

analyze tmp;

analyze tmp1;

执行analyze命令后,Join的顺序正确。数据量大的表tmp1在数据量小的表tmp上方,使用数据量小的表tmp做Hash表,如下图所示。并且tmp1表展示的行数为1000万行,表明统计信息已经更新。

当发现explain返回结果中rows=1000,说明缺少统计信息。一般性能不好时,其原因通常是优化器缺少统计信息,需要通过及时更新统计信息,执行analyze <tablename>,可以简单快捷优化查询性能。

推荐更新统计信息的场景推荐在以下情况下运行analyze <tablename>命令。

导入数据之后。

大量的INSERT、UPDATE以及DELETE操作之后。

内部表、外部表均需要ANALYZE。

分区表针对父表做ANALYZE。如果遇到以下问题,您需要先执行analyze <tablename>,再运行导入任务,可以系统地优化效率。多表JOIN超出内存OOM。

通常会产生Query executor exceeded total memory limitation xxxxx: yyyy bytes used报错。导入效率较低。

Hologres查询或导入数据时,效率较低,运行的任务长时间不结束。

设置适合的Shard

Shard数代表查询执行的并行度。Shard个数对查询性能影响至关重要,Shard数设置少,会导致并行度不足。Shard数设置过多,也会引起查询启动开销大,降低查询效率,同时引起小文件过多,占用内存更多的元数据管理空间。设置与实例规格匹配的Shard数,可以改善查询效率,降低内存开销。

Hologres为每个实例设置了默认的Shard数,Shard数约等于实例中用于核心查询的Core数。这里的core数,略小于实际购买的Core数(实际购买的Core会被分配给不同的节点,包括查询节点、接入节点、控制节点和调度节点等)。不同规格实例默认的Shard数,请参见实例规格概述。当实例扩容后,扩容之前旧的DB对应的默认Shard数不会自动修改,需要根据实际情况修改Shard数,扩容后新建DB的Shard数为当前规格的默认数量。默认的Shard数是已经考虑扩容的场景,在资源扩容5倍以上的场景中,建议考虑重新设置Shard数,小于5倍的场景,无需修改也能带来执行效率的提升。具体操作请参见Table Group设置最佳实践

如下场景需要修改Shard数:

扩容后,因业务需要,原有业务有规模增长,需要提高原有业务的查询效率。此时,您需要创建新的Table Group,并为其设置更大的Shard数。原有的表和数据仍然在旧的Table Group中,您需要将数据重新导入新的Table Group中,完成Resharding的过程。

扩容后,需要上线新业务,但已有业务并不变化。此时,建议您创建新的Table Group,并为其设置适合的Shard数,并不调整原有表的结构。

说明

一个DB内可以创建多个Table Group,但所有Table Group的Shard总数之和不应超过Hologres推荐的默认Shard数,这是对CPU资源的最有效利用。

JOIN场景优化

当有两表或多表JOIN时,为了提高JOIN的性能,有如下几种优化方式。

更新统计信息

如上述查看统计信息中,参与Join的表如果未及时更新统计信息,可能会导致数据量大的表做了Hash表,从而导致Join效率变低。因此可以通过更新表的统计信息,提升SQL性能。

 

analyze <tablename>;

选择合适的分布列(Distribution Key)

分布列(Distribution Key)用于将数据划分到多个Shard,划分均衡可以避免数据倾斜。多个相关的表设计为相同的Distribution Key,可以起到Local Join的加速效果。创建表时,您可以通过如下原则选择合适的分布列:

Distribution Key设置建议

选择Join查询时的连接条件列作为分布列。

选择Group By频繁的列作为分布列。选择数据分布均匀离散的列作为分布列。

更多关于Distribution Key的原理和使用详情请参见分布键Distribution Key

设置Distribution Key场景示例

例如设置Distribution Key,表tmp和tmp1做Join,通过执行explain SQL语句看到执行计划中有Redistribution Motion,说明数据有重分布,没有Local Join,导致查询效率比较低。您需要重新建表并同时设置Join Key为Distribution Key,避免多表连接时数据重分布带来的额外开销。重新建表后两个表的DDL示例语句如下。begin;create table tmp(a int, b int, c int);call set_table_property('tmp', 'distribution_key', 'a');commit;

begin;create table tmp1(a int, b int, c int);call set_table_property('tmp1', 'distribution_key', 'b');commit;

-- 设置分布列为Join Key。select count(1) from tmp join tmp1 on tmp.a = tmp1.b ;通过重新设置表的Distribution Key,再次执行explain SQL语句,可以看到执行计划中,红框内的算子被优化掉了,数据按照相同的Hash Key分布于Shard中。因为数据分布相同,Motion算子被优化(上图中红框内的算子),表明数据不会重新分布,从而避免了冗余的网络开销。

 

使用Runtime Filter

V2.0版本开始,Hologres开始支持Runtime Filter,通常应用在多表Join(至少2张表),尤其是大表Join小表的场景中,无需手动设置,优化器和执行引擎会在查询时自动优化Join过程的过滤行为,使得扫描更少的数据量,从而降低IO开销,以此提升Join的查询性能,详情请参见Runtime Filter

优化Join Order算法

SQL Join关系比较复杂时,或者Join的表多时,优化器(QO)消耗在连接关系最优选择上的时间会更多,调整Join Order策略,在一定场景下会减少Query Optimization的耗时,设置优化器Join Order算法语法如下。set optimizer_join_order = '<value>';

参数说明

参数

说明

value

优化器Join Order算法,有如下三种。

 

exhaustive(默认):通过动态规划算法进行Join Order转换,会生成最优的执行计划,但优化器开销最高。

 

 

query:不进行Join Order转换,按照SQL书写的连接顺序执行,优化器开销最低。

 

 

greedy:通过贪心算法进行Join Order的探索,优化器开销适中。

 

补充说明

使用默认的exhaustive算法可以全局探索最优的执行计划,但对于很多表的Join(例如表数量大于10),优化耗时可能较高。使用query或者greedy算法可以减少优化器耗时,但无法生成最优的执行计划。

优化Broadcast等Motion算子

目前Hologres包含四种Motion Node,分别对应四种数据重分布场景,如下表所示。

 

类型

描述

Redistribute Motion

数据通过哈希分布或随机分布,Shuffle到一个或多个Shard

Broadcast Motion

复制数据至所有Shard

仅在Shard数量与广播的表的数量均较少时,Broadcast Motion的优势较大。

Gather Motion

汇总数据至一个Shard

Forward Motion

用于联邦查询场景。外部数据源或执行引擎与Hologres执行引擎进行数据传输。

结合explain SQL语句执行结果您可以注意如下事项:

如果Motion算子耗时较高,则您可以重新设计分布列。

如果统计信息错误,导致生成Gather Motion或Broadcast Motion,则您可以通过analyze <tablename>命令将其修改为更高效的Redistribute Motion分布方式。

您可以设置如下参数,禁止生成Motion算子,再对比查询耗时,示例语句如下。

-- 禁止生成Broadcast Motion。set optimizer_enable_motion_broadcast = off;

-- 禁止生成Redistribute Motion。set optimizer_enable_motion_redistribute = off;

Broadcast Motion只有在Shard数较少,且广播表的数量较少的场景下有优势。所以如果是小表Broadcast的场景,建议您将表的Shard数量减少(尽量保持Shard Count与Worker数量成比例关系),从而提高查询效率。Shard Count详情请参见Shard Count

关闭Dictionary Encoding

对于字符类型(包括Text/Char/Varchar)的相关查询,Dictionary Encoding或Decoding会减少比较字符串的耗时,但是会带来大量的Decode或Encode开销。

Hologres默认对所有的字符类型列建立Dictionary Encoding,您可以设置dictionary_encoding_columns为空,或关闭部分列的自动Dictionary Encoding功能。注意,修改Dictionary Encoding设置,会引起数据文件重新编码存储,会在一段时间内消耗一部分CPU和内存资源,建议在业务低峰期执行变更。

Decode算子的耗时较高时,请关闭Decode。关闭Dictionary Encoding功能可以改善性能。

当表的字符类型字段较多时,按需选择,可以不用将所有的字符类型都加入dictionary_encoding_columns。示例语句如下:begin;create table tbl (a int not null, b text not null, c int not null, d int);call set_table_property('tbl', 'dictionary_encoding_columns', '');commit;

常见的性能调优手段

可以通过优化相应的SQL来提高查询效率。

采用Fixed Plan优化SQL写入和更新

Fixed Plan是Hologres针对高吞吐数据写入、更新、删除场景的特定优化,通过简化的执行路径,实现数倍性能和吞吐的提升,配置方式和使用方法请参考Fixed Plan加速SQL执行

PQE算子改写

Hologres底层有原生引擎HQE(Hologres Query Engine,向量引擎)和PQE(Postgres Query Engine,分布式Postgres引擎)等多个执行引擎,如果SQL语句中包含HQE不支持的算子,则系统会将该算子发送至PQE执行。此时查询的性能未能足够优化,需要修改相关查询语句。

通过执行计划(explain SQL)查询,若执行计划中出现External SQL(Postgres)则说明这部分的SQL是在PQE中执行的。

具体示例如下:HQE不支持not in,则会将not in操作转到外部查询引擎PQE执行。建议将not in重写为not exists。优化前的SQL语句如下。 explain select * from tmp where a not in (select a from tmp1);

External算子代表该部分SQL语句是在外部引擎Postgres中执行的。

优化后的SQL语句如下,不再使用外部查询引擎。

 explain select * from tmp where not exists (select a from tmp1 where a = tmp.a);

 

通过改写函数,将算子运行在HQE引擎中,以下为函数改写建议。同时Hologres每个版本都在不断迭代PQE函数,以将更多函数下推至HQE。如果是HQE已经支持的函数,则可以通过升级版本来解决,详情请参见函数功能发布记录

 

Hologres原生引擎(HQE)不支持的函数

建议改写的函数

样例

备注

not in

not exists

 

select * from tmp where not exists (select a from tmp1 where a = tmp.a);

不涉及。

regexp_split_to_table(string text, pattern text)

unnest(string_to_array

 

select name,unnest(string_to_array(age,',')) from demo;

regexp_split_to_table支持正则表达式。

Hologres V2.0.4版本起HQE支持regexp_split_to_table,需要使用如下命令开启GUCset hg_experimental_enable_hqe_table_function = on;

substring

extract(hour from to_timestamp(c1, 'YYYYMMDD HH24:MI:SS'))

 

select cast(substring(c1, 13, 2) as int) AS hour from t2;

改写为:

 

select extract(hour from to_timestamp(c1, 'YYYYMMDD HH24:MI:SS')) from t2;

Hologres部分V0.10版本及更早版本不支持substringV1.3版本及以上版本中,HQE已支持substring函数的非正则表达式入参。

regexp_replace

replace

 

select regexp_replace(c1::text,'-','0') from t2;

改写为:

 

select replace(c1::text,'-','') from t2;

replace不支持正则表达式。

at time zone 'utc'

删除at time zone 'utc'

 

select date_trunc('day',to_timestamp(c1, 'YYYYMMDD HH24:MI:SS')  at time zone 'utc') from t2

改写为:

 

select date_trunc('day',to_timestamp(c1, 'YYYYMMDD HH24:MI:SS') ) from t2;

不涉及。

cast(text as timestamp)

to_timestamp

 

select cast(c1 as timestamp) from t2;

改写为:

 

select to_timestamp(c1, 'yyyyMMdd hh24:mi:ss') from t2;

Hologres V2.0版本起HQE支持。

timestamp::text

to_char

 

select c1::text from t2;

改写为:

 

select to_char(c1, 'yyyyMMdd hh24:mi:ss') from t2;

Hologres V2.0版本起HQE支持。

避免模糊查询

模糊查询(Like操作)不会建立索引。

结果缓存对查询的影响

Hologres会默认对相同的查询或子查询结果进行缓存,重复执行会命中缓存结果。您可以使用如下命令关闭缓存对性能测试的影响:

 

set hg_experimental_enable_result_cache = off;

OOM的优化手段

当实例计算内存不足时通常会出现OOM,常见的报错如下。产生OOM的原因有多种,比如计算复杂、并发量高等,可以根据不同的原因进行针对性优化,从而减少OOM。详情请参见OOM常见问题排查指南

 

Total memory used by all existing queries exceeded memory limitation.

memory usage for existing queries=(2031xxxx,184yy)(2021yyyy,85yy)(1021121xxxx,6yy)(2021xxx,18yy)(202xxxx,14yy); Used/Limit: xy1/xy2 quota/sum_quota: zz/100

Order By Limit场景优化

Hologres V1.3之前版本,对Order By Limit场景不支持Merge Sort算子,生成执行计划时,在最后输出时还会做一次排序,导致性能相对较差。从1.3版本开始,引擎通过对Order By Limit场景优化,支持Merge Sort算子,实现多路归并排序,无需再进行额外的排序,提升了查询性能。

优化示例如下。

建表DDL

begin;create table test_use_sort_1

(

          uuid           text not null,

          gpackagename   text not null,

          recv_timestamp text not null

);call set_table_property('test_use_sort_1', 'orientation', 'column');call set_table_property('test_use_sort_1', 'distribution_key', 'uuid');call set_table_property('test_use_sort_1', 'clustering_key', 'uuid:asc,gpackagename:asc,recv_timestamp:desc');commit;

--插入数据insert into test_use_sort_1 select i::text, i::text, '20210814' from generate_series(1, 10000) as s(i);

--更新统计信息analyze test_use_sort_1;查询命令set hg_experimental_enable_reserve_gather_exchange_order =on set hg_experimental_enable_reserve_gather_motion_order =onselect uuid from test_use_sort_1 order by uuid limit 5;

执行计划对比Hologres V1.3之前版本(V1.1)的执行计划如下。

Hologres V1.3版本的执行计划如下。

从执行计划对比中可以看出,Hologres V1.3版本在最后输出会少一个排序,直接多路归并,提升了查询性能。

Count Distinct优化

改写为APPROX_COUNT_DISTINCT

Count Distinct是精确去重,需要把相同key的记录shuffle到同一个节点去重,比较耗费资源。Hologres实现了扩展函数APPROX_COUNT_DISTINCT,采用HyperLogLog基数估计的方式进行非精确的COUNT DISTINCT计算,提升查询性能。误差率平均可以控制在0.1%-1%以内,可以根据业务情况适当改写,详情请参见APPROX_COUNT_DISTINCT

使用UNIQ函数

Hologres从 V1.3版本开始,支持UNIQ精确去重函数,在GROUP BY KEY的KEY基数较高时,比Count Distinct性能更好,更节省内存。当使用Count Distinct出现OOM时,可以使用UNIQ做替换,详情请参见UNIQ

设置合适的Distribution Key

当有多个Count Distinct且是key是同一个并且数据离散均匀分布,建议将Count Distinct的key设置成Distribution Key,这样相同的数据可以分布相同的Shard,避免数据Shuffle。

Count Distinct优化

Hologres从V2.1版本开始,针对Count Distinct场景做了非常多的性能优化(包括单个Count Distinct、多个Count Distinct、数据倾斜、SQL没有Group By字段等场景),无需手动改写成UNIQ,即可实现更好的性能。如果想要提升Count Distinct性能,建议您将Hologres实例升级至V2.1及以上版本。

Group By优化

Group By Key会导致数据在计算时按照分组列的Key重新分布数据,如果Group By耗时较高,您可以将Group By的列设置为分布列。

 -- 数据如果按照a列的值进行分布,将减少数据运行时重分布,充分利用shard的并行计算能力。select a, count(1) from t1 group by a;

数据倾斜处理

数据在多个Shard上分布不均匀会导致查询速度较慢,您可以通过如下语句判断数据分布是否存在倾斜。详情请参见查看Worker倾斜关系-- hg_shard_id是每个表的内置隐藏列,描述对应行数据所在shardselect hg_shard_id, count(1) from t1 group by hg_shard_id;如果数据存在显著倾斜,则需要更改distribution_key,选择数据分布均匀离散的列作为分布列。说明更改distribution_key需要重新创建表并导入数据。如果数据本身存在倾斜(与distribution_key无关时),建议从业务角度对数据进行优化,避免倾斜。

With表达式优化(Beta

Hologres兼容PostgreSQL ,支持CTE(Common Table Expression),常用在with递归查询,其实现原理同PostgreSQL,都是基于Inlining展开的,所以当有多次使用CTE时会造成重复计算。在HologresV1.3版本中,可以通过如下GUC参数支持CTE Reuse(复用),这样CTE只需计算一次而被多次引用,用以节省计算资源,提升查询性能。若您的Hologres实例版本低于 V1.3,请升级实例。set optimizer_cte_inlining=off;

说明该功能当前还处于Beta阶段,默认没有开启(默认会将CTE全部Inline展开,重复计算),可手动设置GUC后开启使用。CTE Reuse开启后,依赖Shuffle阶段的Spill功能,因为下游用户消费CTE的进度是不同步的,所以数据量大的时候会影响性能。

示例create table cte_reuse_test_t

(

    a integer not null,

    b text,

    primary key (a)

);

insert into cte_reuse_test_t values(1, 'a'),(2, 'b'), (3, 'c'), (4, 'b'), (5, 'c'), (6, ''), (7, null);

set optimizer_cte_inlining=off;

explain with c as (select b, max(a) as a from cte_reuse_test_t group by b)select a1.a,a2.a,a1.b, a2.bfrom c a1, c a2where a1.b = a2.border by a1.b

limit 100;                                  

执行计划对比Hologres V1.3之前版本(V1.1)的执行计划如下。

Hologres V1.3版本的执行计划如下。

从执行计划的对比中可以看出Hologres V1.3之前版本会有多个AGG计算(HashAggregate),Hologres V1.3版本只需计算一次就被结果复用,提升了性能。

单阶段Agg优化为多阶段Agg

如果Agg算子耗时过高,您可以检查是否没有做Local Shard级别的预聚合。通过在单个Shard内先进行本地的Agg操作,可以减少最终聚合操作的数据量,提升性能。具体如下:三阶段聚合:数据先进行文件级别的聚合计算,再聚合单个Shard内的数据,最后汇总所有Shard的结果。两阶段聚合:数据先在单个Shard内进行聚合计算,再汇总所有Shard的结果。

您可以强制Hologres进行多阶段聚合操作,语句如下。set optimizer_force_multistage_agg = on;

建表属性优化

选择存储类型

Hologres支持行存储、列存储和行列共存多种存储模式,您可以根据业务场景选择合适的存储类型,如下表所示。

 

类型

适用场景

缺点

行存储

按主键进行高QPS的点查询场景。

一次能读取所有列,并且对UPDATEDELETEINSERT操作的性能较好。

大范围的查询、全表扫描及聚合等操作性能较差。

列存储

适用于多列按范围查询、单表聚合及多表连接等数据分析场景。

UPDATEDELETE操作及无索引场景下的点查询性能慢于行存储。

行列共存

同时具备以上行列两种使用场景。

存储开销更高。

选择数据类型

Hologres支持多种数据类型,您可以根据业务场景以及需求选择合适的数据类型,原则如下:尽量选用存储空间小的类型。优先使用INT类型,而不是BIGINT类型。优先使用精确确定的DECIMAL/NUMERIC类型,明确数值精度(PRECISION,SCALE),且精度尽量小,减少使用FLOAT/DOUBLE PRECISION等非精确类型,避免统计汇总中的误差。GROUP BY的列不建议使用FLOAT/DOUBLE等非精确类型。优先使用TEXT,适用范围更广,当使用VARCHAR(N)和CHAR(N),N的取值尽量小。日期类型使用TIMESTAMPTZ、DATE,避免使用TEXT。关联条件使用一致的数据类型。进行多表关联时,不同列尽量使用相同的数据类型。避免Hologres将不同类型的列进行隐式类型转换,造成额外的开销。UNION或GROUP BY等操作避免使用FLOAT/DOUBLE等非精确类型。

UNIONGROUP BY等操作暂不支持DOUBLE PRECISIONFLOAT数据类型,需要使用DECIMAL类型。

选择主键主键(Primary Key)主要用于保证数据的唯一性,适用于主键重复的导入数据场景。您可以在导入数据时设置option选择去重方式,如下所示:ignore:忽略新数据。update:新数据覆盖旧数据。合理的设置主键能帮助优化器在某些场景下生成更好的执行计划。例如,查询为group by pk,a,b,c的场景。但是在列存储场景,主键的设置对于写入数据的性能会有较大的影响。通常,不设置主键的写入性能是设置主键的3倍。

选择分区表

Hologres当前仅支持创建一级分区表。合理的设置分区会加速查询性能,不合理的设置(比如分区过多)会造成小文件过多,查询性能显著下降。说明对于按天增量导入的数据,建议按天建成分区表,数据单独存储,只访问当天数据。

设置分区适用的场景如下:删除整个子表的分区,不影响其他分区数据。DROP/TRUNCATE语句的性能高于DELETE语句。对于分区列在谓词条件中的查询,可以直接通过分区列索引到对应分区,并且可以直接查询子分区,操作更为灵活。对于周期性实时导入的数据,适用于创建分区表。例如,每天都会导入新的数据,可以将日期作为分区列,每天导入数据至一个子分区。示例语句如下。begin;create table insert_partition(c1 bigint not null, c2 boolean, c3 float not null, c4 text, c5 timestamptz not null) partition by list(c4);call set_table_property('insert_partition', 'orientation', 'column');commit;create table insert_partition_child1 partition of insert_partition for values in('20190707');create table insert_partition_child2 partition of insert_partition for values in('20190708');create table insert_partition_child3 partition of insert_partition for values in('20190709');

select * from insert_partition where c4 >= '20190708';select * from insert_partition_child3;

选择索引

Hologres支持设置多种索引,不同索引的作用不同。您可以根据业务场景选择合适的索引,提升查询性能,因此写入数据前,请根据业务场景提前设计好表结构。索引类型如下表所示。

 

 

类型

名称

描述

使用建议

示例查询语句

clustering_key

聚簇列

文件内聚簇索引,数据在文件内按该索引排序。

对于部分范围查询,Hologres可以直接通过聚簇索引的数据有序属性进行过滤。

将范围查询或Filter查询列作为聚簇索引列。索引过滤具备左匹配原则,建议设置不超过2列。

select sum(a) from tb1 where a > 100 and a < 200;

bitmap_columns

位图列

文件内位图索引,数据在文件内按该索引列建立位图。

对于等值查询,Hologres可以按照数值对每一行的数据做编码,通过位操作快速索引到对应行,时间复杂度为O(1)

将等值查询列作为Bitmap列。

select * from tb1 where a =100;

segment_key(也称为event_time_column

分段列

文件索引,数据按Append Only方式写入文件,随后文件间按该索引键合并小文件。

Segment_key标识了文件的边界范围,您可以通过Segment Key快速索引到目标文件。

Segment_key是为时间戳、日期等有序,范围类数据场景设计的,因此与数据的写入时间有强相关性。

您需要先通过Segment_key进行快速过滤,再通过BitmapCluster索引进行文件内范围或等值查询。具备最左匹配原则,一般只有1列。

建议将第一个非空的时间戳字段设置为Segment_key

select sum(a) from tb1 where ts > '2020-01-01' and a < '2020-03-02';

clustering_key和segment_key都需要满足传统数据库(例如MySQL)的最左前缀匹配原则,即按照Index书写的最左列排序进行索引。如果最左列为有序的场景,则按照左边第二列进行排序。示例如下。

call set_table_property('tmp', 'clustering_key', 'a,b,c');select * from tmp where a > 1 ;  --可以使用Cluster索引。select * from tmp where a > 1 and c > 2 ;   --只有a可以使用Cluster索引。select * from tmp where a > 1 and b > 2 ;  --a,b均可以使用Cluster索引。select * from tmp where a > 1 and b > 2 and c > 3 ; --a,b,c均可以使用Cluster索引。select * from tmp where b > 1 and c > 2 ;   --b,c均不能使用Cluster索引。

Bitmap Index支持多个列的and或or查询,示例如下。call set_table_property('tmp', 'bitmap_columns', 'a,b,c');select * from tmp where a = 1 and b = 2 ;  -- 可以使用Bitmap索引。select * from tmp where a = 1 or b = 2 ; -- 可以使用Bitmap索引。说明bitmap_columns可以在创建表后添加,clustering_key和segment_key则在创建表时已经指定,后续无法再添加。

查看是否使用Index创建tmp表并指定索引字段,语句如下begin;create table tmp(a int not null, b int not null, c int not null);call set_table_property('tmp', 'clustering_key', 'a');call set_table_property('tmp', 'segment_key', 'b');call set_table_property('tmp', 'bitmap_columns', 'a,b,c');commit;

查看是否使用Cluster Index,语句如下explain select * from tmp where a > 1;

 

查看是否使用Bitmap Index,语句如下explain select * from tmp where c = 1;

 

查看是否使用Segment Key,语句如下explain select * from tmp where b > 1;

 

11.场景化建表调优指南

Hologres支持设置多种表属性,不同的表属性有不同的特性。本文为您介绍如何根据业务查询场景设置合适的表属性,从而使查询在执行过程中扫描数据量更少、访问文件数更少、产生更少的I/O次数,进而使得查询更快、查询QPS更高。

确定表的存储格式

Hologres支持行存、列存、行列共存三种存储格式,具体原理及使用建议请参见表存储格式:列存、行存、行列共存

首先请参考下图所示流程确定您表的存储格式。如果您的业务场景尚未完全明确,请优先选择行列共存,以兼顾更多可能出现的场景。

确定表的查询属性

在确定表的存储格式后,您需要根据查询场景确定表的属性。下文均为针对单场景设置的表属性示例。示例中均使用64 CU规格的Hologres实例对TPC-H 100GB中的Lineitem和Orders两张表进行效果验证,表字段定义及数据导入方法请参见OLAP查询场景TPC-H表介绍:TPC-H数据集模拟的是零售场景。其中:Orders表是订单表(根据o_orderkey字段可以确定一个订单)。Lineitem表是订单明细表(根据o_orderkey和l_linenumber字段可以确定一个订单中的某个商品)。本文的TPC-H的实现基于TPC-H的基准测试,并不能与已发布的TPC-H基准测试结果相比较,本文中的测试并不符合TPC-H基准测试的所有要求。如果您的表需要兼顾多个场景,如有不同的查询过滤条件或有不同的JOIN字段,则需要综合考虑这些场景的查询频率与性能需求,从而设置最优的表属性。

场景1:超高QPS点查

场景:如果您的查询场景为万级及以上QPS的点查,以TPC-H Orders表为例,通过o_orderkey字段可以唯一确定一行数据。典型SQL样例如下:SELECT * FROM orders WHERE o_orderkey = ?;

设置建议

将点查时的过滤字段设置为主键(Primary Key),Hologres支持通过Fixed Plan加速执行基于主键的点查,实现执行效率的成倍提升,原理请参见Fixed Plan加速SQL执行

效果验证:您可以将Orders表定义为行存表或行列共存表,分别对将o_orderkey字段设为主键和不设主键两种情况进行效果验证,建表语句请参见场景1 DDL,验证方法请参见Key/Value点查场景。验证结果如下:有主键:并发数500,平均QPS约为10.4万,平均latency约为4ms。无主键:并发数500,平均QPS约为1.6万,平均latency约为30ms。

场景2:高QPS的小数据量前缀扫描

场景

如果您的查询场景满足以下条件:表由多个字段组成复合主键(Primary Key)。要求高QPS (万级QPS)查询。查询条件为根据主键字段组合中的某个字段进行等值过滤,查询结果集一般比较小(几条或者几十条)。以TPC-H Lineitem表为例,Lineitem是订单明细表(根据l_orderkey和l_linenumber字段可以确定一个订单中的某个商品),现需要高QPS的根据某个订单号(l_orderkey)获取此订单下的所有商品,SQL样式如下:SELECT * FROM lineitem WHERE l_orderkey = ?;

设置建议

将等值过滤条件的字段设置为主键(Primary Key)的最左字段,即Lineitem表的主键是(l_orderkey, l_linenumber),而不是(l_linenumber, l_orderkey)。将等值过滤条件的字段设置为Distribution Key,保证需要扫描的数据都存储在同一个Shard,减少访问的Shard数,以提高QPS。即Lineitem表的distribution_key为l_orderkey。将等值过滤条件的字段设置为Clustering Key(行存表不需要,列存表和行列共存表需要),保证需要扫描的数据在文件中连续,减少I/O次数。即Lineitem表的clustering_key设置为l_orderkey。通过如上的表属性设置,将查询转化成了单Shard的前缀扫描(PrefixScan)。Hologres支持通过Fixed Plan加速执行PrefixScan场景(需要开启hg_experimental_enable_fixed_dispatcher_for_scanGUC ),详情请参见Fixed Plan加速SQL执行

效果验证:您可以将Lineitem表定义为行存表或行列共存表,分别对按上述组合设置表属性和不按上述组合设置表属性两种情况进行效果验证,建表语句请参见场景2 DDL,验证方法请参见Key/Value点查场景。验证结果如下:按上述组合设置表属性:并发数500,平均QPS约为3.7万,平均latency约为13ms。不按上述组合设置:并发数1,平均QPS约为60,平均latency约为16ms。

场景3:有时间过滤条件的查询

场景

如果您的查询场景带有典型的时间过滤条件,以TPC-H Lineitem表为例,需要通过l_shipdate字段进行时间过滤(如查询语句Q1),适当修改时间过滤条件,SQL样式如下:-- 原始Query SELECT l_returnflag,

    l_linestatus,

    sum(l_quantity) AS sum_qty,

    sum(l_extendedprice) AS sum_base_price,

    sum(l_extendedprice * (1 - l_discount)) AS sum_disc_price,

    sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge,

    avg(l_quantity) AS avg_qty,

    avg(l_extendedprice) AS avg_price,

    avg(l_discount) AS avg_disc,

    count(*) AS count_orderFROM

    lineitemWHERE

    l_shipdate <= date '1998-12-01' - interval '120' dayGROUP BY

    l_returnflag,

    l_linestatusORDER BY

    l_returnflag,

    l_linestatus;

-- 修改后的QuerySELECT ... FROM lineitemWHERE

    l_year = '1992' AND  -- 仅分区表需要增加该时间过滤条件

    l_shipdate <= date '1992-12-01'  -- 适当缩小时间范围以更好地验证效果

...;

设置建议:使用分区表,根据时间过滤条件进行分区。本场景针对Lineitem表增加l_year列并将其设为分区键,即为按年分区。您需要结合实际数据量等因素综合考虑,决定是否使用分区表,或只设置event_time_column。分区表使用限制与注意事项请参见CREATE PARTITION TABLE。将时间过滤字段设置为event_time_column,保证Shard内的各文件按event_time_column值有序排列,减少扫描文件数。即Lineitem表的event_time_column为l_shipdate。event_time_column原理及使用请参见Event Time Column(Segment Key)

效果验证:您可以将Lineitem表定义为列存表,分别对按上述建议设置分区和event_time_column、不设置分区并将其他字段设为event_time_column两种情况进行效果验证,建表语句请参见场景3 DDL,验证方法请参见OLAP查询场景。验证结果如下:按上述建议设置分区和event_time_column:扫描分区数为1,扫描文件数为80。不设置分区并将其他字段设为event_time_column:未经过分区过滤,扫描文件数为320。说明:可以通过执行EXPLAIN ANALYZE命令查看SQL的扫描分区数(Partitions selected)和扫描文件数(dop)。

场景4:有非时间的单值过滤条件的查询

场景

如果您的查询场景带有非时间类的单值过滤条件,以TPC-H Lineitem表为例,需要通过非时间字段l_shipmode进行单值过滤(如按查询语句Q1进行聚合计算),SQL样式如下:SELECT

    ...FROM

    lineitemWHERE

    l_shipmode IN ('FOB', 'AIR');

设置建议:将单值字段设置为Clustering Key,保证相同值的数据在文件中连续,减少I/O次数。即Lineitem表的l_shipmode为Clustering Key。将单值字段设置为Bitmap,加速定位到符合条件的数据所在位置。即Lineitem表的l_shipmode为Bitmap_columns。效果验证:您可以将Lineitem表定义为列存表,分别对按上述建议设置表属性、不将l_shipmode设为Clustering Key和Bitmap_columns两种情况进行效果验证,建表语句请参见场景4 DDL,验证方法请参见OLAP查询场景。验证结果如下:按上述建议设置表属性:读取数据行数1.7亿行,查询时长0.71s。不将l_shipmode设为Clustering Key和Bitmap_columns:读取数据行数6.0亿行(全表扫描),查询时长2.41s。说明:可以通过慢Query日志查看读取数据行数(read_rows),详情请参见Query日志查看与分析。可以通过执行计划验证是否通过Bitmap过滤,执行计划中有Bitmap Filter关键字,说明查询进行了Bitmap过滤。

场景5:有按某字段聚合的查询

场景

如果您的查询场景为按某字段聚合,以TPC-H Lineitem表为例,针对l_suppkey字段进行分组聚合查询,SQL样式如下:SELECT

    l_suppkey,

    sum(l_extendedprice * (1 - l_discount))FROM

    lineitemGROUP BY

    l_suppkey;

设置建议:将聚合字段设置为Distribution Key,避免跨Shard的大量数据Shuffle。效果验证:您可以将Lineitem表定义为列存表,分别对将聚合字段l_suppkey设为Distribution Key、将其他字段设为Distribution Key两种情况进行效果验证,建表语句请参见场景5 DDL,验证方法请参见OLAP查询场景。验证结果如下:设置合适的Distribution Key:数据Shuffle量为 0.21 GB,执行时长2.30s。设置不合适的Distribution Key:数据Shuffle量为 8.16 GB,执行时长3.68s。说明可以通过慢Query日志查看Shuffle数据量(shuffle_bytes),详情请参见Query日志查看与分析

场景6:多表JOIN查询

场景

如果您的查询场景为多表JOIN查询,以TPC-H Lineitem表和Orders表为例,按查询语句Q4进行JOIN查询,SQL样例如下:SELECT

    o_orderpriority,

    count(*) AS order_countFROM

    ordersWHERE

    o_orderdate >= date '1996-07-01'

    AND o_orderdate < date '1996-07-01' + interval '3' month

    AND EXISTS (

        SELECT

            *

        FROM

            lineitem

        WHERE

            l_orderkey = o_orderkey -- JOIN查询

            AND l_commitdate < l_receiptdate)GROUP BY

    o_orderpriorityORDER BY

    o_orderpriority;

设置建议:建议将JOIN字段设置为Distribution Key,实现Local Join,避免跨Shard的大量数据Shuffle。效果验证:您可以将Lineitem表和Orders表定义为列存表,分别对将JOIN字段l_orderkey与o_orderkey设为各自的Distribution Key、设置其他字段为Distribution Key(比如将l_linenumber字段设为Lineitem表的Distribution Key、将Orders表的Distribution Key设为空)两种情况进行效果验证,建表语句请参见场景6 DDL,验证方法请参见OLAP查询场景。验证结果如下:两个表均设置合适的Distribution Key:数据Shuffle量为0.45 GB,执行时长2.19s。两个表均设置不合适的Distribution Key:数据Shuffle量为6.31 GB,执行时长5.55s。说明可以通过慢Query日志查看Shuffle数据量(shuffle_bytes),详情请参见Query日志查看与分析

确定表所属的Table Group

如果您的实例规格较大(大于256 Core),并且业务场景较丰富,可以考虑规划多个Table Group,并在建表时指定表所属的Table Group。详情请参见Table Group设置最佳实践

建表语句

场景1 DDL:-- 有主键表DDL如下。无主键表只需删去O_ORDERKEY的PRIMARY KEY定义。DROP TABLE IF EXISTS orders;BEGIN;CREATE TABLE orders(

    O_ORDERKEY       BIGINT         NOT NULL PRIMARY KEY

    ,O_CUSTKEY       INT            NOT NULL

    ,O_ORDERSTATUS   TEXT           NOT NULL

    ,O_TOTALPRICE    DECIMAL(15,2)  NOT NULL

    ,O_ORDERDATE     TIMESTAMPTZ    NOT NULL

    ,O_ORDERPRIORITY TEXT           NOT NULL

    ,O_CLERK         TEXT           NOT NULL

    ,O_SHIPPRIORITY  INT            NOT NULL

    ,O_COMMENT       TEXT           NOT NULL

);CALL SET_TABLE_PROPERTY('orders', 'orientation', 'row');CALL SET_TABLE_PROPERTY('orders', 'clustering_key', 'o_orderkey');CALL SET_TABLE_PROPERTY('orders', 'distribution_key', 'o_orderkey');COMMIT;

场景2 DDL:-- 创建满足上文表属性组合的Lineitem表。DROP TABLE IF EXISTS lineitem;BEGIN;CREATE TABLE lineitem

(

    L_ORDERKEY      BIGINT      NOT NULL,

    L_PARTKEY       INT         NOT NULL,

    L_SUPPKEY       INT         NOT NULL,

    L_LINENUMBER    INT         NOT NULL,

    L_QUANTITY      DECIMAL(15,2) NOT NULL,

    L_EXTENDEDPRICE DECIMAL(15,2) NOT NULL,

    L_DISCOUNT      DECIMAL(15,2) NOT NULL,

    L_TAX           DECIMAL(15,2) NOT NULL,

    L_RETURNFLAG    TEXT        NOT NULL,

    L_LINESTATUS    TEXT        NOT NULL,

    L_SHIPDATE      TIMESTAMPTZ NOT NULL,

    L_COMMITDATE    TIMESTAMPTZ NOT NULL,

    L_RECEIPTDATE   TIMESTAMPTZ NOT NULL,

    L_SHIPINSTRUCT  TEXT        NOT NULL,

    L_SHIPMODE      TEXT        NOT NULL,

    L_COMMENT       TEXT        NOT NULL,

    PRIMARY KEY (L_ORDERKEY,L_LINENUMBER)

);CALL set_table_property('lineitem', 'orientation', 'row');-- CALL set_table_property('lineitem', 'clustering_key', 'L_ORDERKEY,L_SHIPDATE');CALL set_table_property('lineitem', 'distribution_key', 'L_ORDERKEY');COMMIT;

场景3 DDL:-- 创建Lineitem分区表。非分区表同场景2。DROP TABLE IF EXISTS lineitem;BEGIN;CREATE TABLE lineitem

(

    L_ORDERKEY      BIGINT      NOT NULL,

    L_PARTKEY       INT         NOT NULL,

    L_SUPPKEY       INT         NOT NULL,

    L_LINENUMBER    INT         NOT NULL,

    L_QUANTITY      DECIMAL(15,2) NOT NULL,

    L_EXTENDEDPRICE DECIMAL(15,2) NOT NULL,

    L_DISCOUNT      DECIMAL(15,2) NOT NULL,

    L_TAX           DECIMAL(15,2) NOT NULL,

    L_RETURNFLAG    TEXT        NOT NULL,

    L_LINESTATUS    TEXT        NOT NULL,

    L_SHIPDATE      TIMESTAMPTZ NOT NULL,

    L_COMMITDATE    TIMESTAMPTZ NOT NULL,

    L_RECEIPTDATE   TIMESTAMPTZ NOT NULL,

    L_SHIPINSTRUCT  TEXT        NOT NULL,

    L_SHIPMODE      TEXT        NOT NULL,

    L_COMMENT       TEXT        NOT NULL,

    L_YEAR          TEXT        NOT NULL,

    PRIMARY KEY (L_ORDERKEY,L_LINENUMBER,L_YEAR)

)PARTITION BY LIST (L_YEAR);CALL set_table_property('lineitem', 'clustering_key', 'L_ORDERKEY,L_SHIPDATE');CALL set_table_property('lineitem', 'segment_key', 'L_SHIPDATE');CALL set_table_property('lineitem', 'distribution_key', 'L_ORDERKEY');COMMIT;

场景4 DDL:-- 创建设置不恰当表属性的Lineitem表。对比场景只需将clustering_key和bitmap_columns改为恰当值。DROP TABLE IF EXISTS lineitem;BEGIN;CREATE TABLE lineitem

(

    L_ORDERKEY      BIGINT      NOT NULL,

    L_PARTKEY       INT         NOT NULL,

    L_SUPPKEY       INT         NOT NULL,

    L_LINENUMBER    INT         NOT NULL,

    L_QUANTITY      DECIMAL(15,2) NOT NULL,

    L_EXTENDEDPRICE DECIMAL(15,2) NOT NULL,

    L_DISCOUNT      DECIMAL(15,2) NOT NULL,

    L_TAX           DECIMAL(15,2) NOT NULL,

    L_RETURNFLAG    TEXT        NOT NULL,

    L_LINESTATUS    TEXT        NOT NULL,

    L_SHIPDATE      TIMESTAMPTZ NOT NULL,

    L_COMMITDATE    TIMESTAMPTZ NOT NULL,

    L_RECEIPTDATE   TIMESTAMPTZ NOT NULL,

    L_SHIPINSTRUCT  TEXT        NOT NULL,

    L_SHIPMODE      TEXT        NOT NULL,

    L_COMMENT       TEXT        NOT NULL,

    PRIMARY KEY (L_ORDERKEY,L_LINENUMBER)

);CALL set_table_property('lineitem', 'segment_key', 'L_SHIPDATE');CALL set_table_property('lineitem', 'distribution_key', 'L_ORDERKEY');CALL set_table_property('lineitem', 'bitmap_columns', 'l_orderkey,l_partkey,l_suppkey,l_linenumber,l_returnflag,l_linestatus,l_shipinstruct,l_comment');COMMIT;

场景5 DDL:-- 将Group By字段设为distribution_key的Lineitem表。DROP TABLE IF EXISTS lineitem;BEGIN;CREATE TABLE lineitem

(

    L_ORDERKEY      BIGINT      NOT NULL,

    L_PARTKEY       INT         NOT NULL,

    L_SUPPKEY       INT         NOT NULL,

    L_LINENUMBER    INT         NOT NULL,

    L_QUANTITY      DECIMAL(15,2) NOT NULL,

    L_EXTENDEDPRICE DECIMAL(15,2) NOT NULL,

    L_DISCOUNT      DECIMAL(15,2) NOT NULL,

    L_TAX           DECIMAL(15,2) NOT NULL,

    L_RETURNFLAG    TEXT        NOT NULL,

    L_LINESTATUS    TEXT        NOT NULL,

    L_SHIPDATE      TIMESTAMPTZ NOT NULL,

    L_COMMITDATE    TIMESTAMPTZ NOT NULL,

    L_RECEIPTDATE   TIMESTAMPTZ NOT NULL,

    L_SHIPINSTRUCT  TEXT        NOT NULL,

    L_SHIPMODE      TEXT        NOT NULL,

    L_COMMENT       TEXT        NOT NULL,

    PRIMARY KEY (L_ORDERKEY,L_LINENUMBER,L_SUPPKEY)

);CALL set_table_property('lineitem', 'segment_key', 'L_COMMITDATE');CALL set_table_property('lineitem', 'clustering_key', 'L_ORDERKEY,L_SHIPDATE');CALL set_table_property('lineitem', 'distribution_key', 'L_SUPPKEY');COMMIT;

场景6 DDL:DROP TABLE IF EXISTS LINEITEM;

BEGIN;CREATE TABLE LINEITEM

(

    L_ORDERKEY      BIGINT      NOT NULL,

    L_PARTKEY       INT         NOT NULL,

    L_SUPPKEY       INT         NOT NULL,

    L_LINENUMBER    INT         NOT NULL,

    L_QUANTITY      DECIMAL(15,2) NOT NULL,

    L_EXTENDEDPRICE DECIMAL(15,2) NOT NULL,

    L_DISCOUNT      DECIMAL(15,2) NOT NULL,

    L_TAX           DECIMAL(15,2) NOT NULL,

    L_RETURNFLAG    TEXT        NOT NULL,

    L_LINESTATUS    TEXT        NOT NULL,

    L_SHIPDATE      TIMESTAMPTZ NOT NULL,

    L_COMMITDATE    TIMESTAMPTZ NOT NULL,

    L_RECEIPTDATE   TIMESTAMPTZ NOT NULL,

    L_SHIPINSTRUCT  TEXT        NOT NULL,

    L_SHIPMODE      TEXT        NOT NULL,

    L_COMMENT       TEXT        NOT NULL,

    PRIMARY KEY (L_ORDERKEY,L_LINENUMBER)

);CALL set_table_property('LINEITEM', 'clustering_key', 'L_SHIPDATE,L_ORDERKEY');CALL set_table_property('LINEITEM', 'segment_key', 'L_SHIPDATE');CALL set_table_property('LINEITEM', 'distribution_key', 'L_ORDERKEY');CALL set_table_property('LINEITEM', 'bitmap_columns', 'L_ORDERKEY,L_PARTKEY,L_SUPPKEY,L_LINENUMBER,L_RETURNFLAG,L_LINESTATUS,L_SHIPINSTRUCT,L_SHIPMODE,L_COMMENT');CALL set_table_property('LINEITEM', 'dictionary_encoding_columns', 'L_RETURNFLAG,L_LINESTATUS,L_SHIPINSTRUCT,L_SHIPMODE,L_COMMENT');COMMIT;

DROP TABLE IF EXISTS ORDERS;

BEGIN;CREATE TABLE ORDERS

(

    O_ORDERKEY      BIGINT      NOT NULL PRIMARY KEY,

    O_CUSTKEY       INT         NOT NULL,

    O_ORDERSTATUS   TEXT        NOT NULL,

    O_TOTALPRICE    DECIMAL(15,2) NOT NULL,

    O_ORDERDATE     timestamptz NOT NULL,

    O_ORDERPRIORITY TEXT        NOT NULL,

    O_CLERK         TEXT        NOT NULL,

    O_SHIPPRIORITY  INT         NOT NULL,

    O_COMMENT       TEXT        NOT NULL

);CALL set_table_property('ORDERS', 'segment_key', 'O_ORDERDATE');CALL set_table_property('ORDERS', 'distribution_key', 'O_ORDERKEY');CALL set_table_property('ORDERS', 'bitmap_columns', 'O_ORDERKEY,O_CUSTKEY,O_ORDERSTATUS,O_ORDERPRIORITY,O_CLERK,O_SHIPPRIORITY,O_COMMENT');CALL set_table_property('ORDERS', 'dictionary_encoding_columns', 'O_ORDERSTATUS,O_ORDERPRIORITY,O_CLERK,O_COMMENT');COMMIT;

十二.写入或更新调优指南

背景信息

Hologres是一站式实时数据仓库引擎,支持海量数据高性能实时写入与实时更新,满足大数据场景上对数据高性能低延迟的需求,Hologres的写入技术原理详情请参见写入技术揭秘。在写入或更新场景上,Hologres提供多种数据写入和更新的方式,详情请参见数据同步概述

基本原理

在了解写入或更新的调优手段前,先了解基本的原理,以帮助业务在使用过程中,对不同写入模式的写入性能有着更加合理的预估。

不同表存储格式的写入或更新性能。全列写入或更新时,性能排序如下。行存 > 列存 > 行列共存。部分列写入或更新时,性能排序如下。行存 > 行列共存 > 列存。不同写入模式的性能。

写入模式类型如下。

写入模式

说明

Insert

以追加(Append-Only)的方式写入,结果表无主键(PK)。

InsertOrIgnore

写入时忽略更新,结果表有主键,实时写入时如果主键重复,丢弃后到的数据。

InsertOrReplace

写入覆盖,结果表有主键,实时写入时如果主键重复,按照主键更新。如果写入的一行数据不包含所有列,缺失的列的数据补Null。

InsertOrUpdate

写入更新,结果表有主键,实时写入时如果主键重复,按照主键更新。分为整行更新和部分列更新,部分列更新指如果写入的一行数据不包含所有列,缺失的列不更新。

 

列存表不同写入模式的性能排序如下。

结果表无主键性能最高。

结果表有主键时:

InsertOrIgnore > InsertOrReplace >= InsertOrUpdate(整行)> InsertOrUpdate(部分列)。

行存表不同写入模式的性能排序如下。

InsertOrReplcae = InsertOrUpdate(整行)>= InsertOrUpdate(部分列) >= InsertOrIgnore。

开启Binlog的表写入或更新性能排序如下。行存 > 行列共存 > 列存。

写入瓶颈判断:在表数据写入或更新时,如果写入性能慢,可通过查看管理控制台的CPU使用率监控指标,初步判断性能瓶颈:

CPU使用率很低:说明Hologres资源没有完全用上,性能瓶颈不在Hologres侧,可自行排查是否存在上游数据读取较慢等问题。

CPU使用率较高(长期100%):说明已经达到了Hologres的资源瓶颈,可以通过如下方法处理。通过基本调优手段排查是否因基础设置不到位导致资源负载较高,影响写入性能,详情请参见基本调优手段。在基本调优手段已经检查完毕后,可以通过写入渠道(常见的如Flink、数据集成)以及Hologres的高级调优手段,更深层次的判断是否存在写入瓶颈,并做相应的处理,详情请参见Flink写入调优数据集成调优高级调优手段。查询影响写入,二者共同执行会导致资源使用率较高,通过慢Query日志排查同一时间查询的CPU消耗。若是查询影响写入,可以考虑为实例配置读写分离高可用部署,详情请参见多实例读写分离高可用部署(共享存储)。所有调优手段已操作完毕,但写入性能仍然不满足预期,可适当扩容Hologres实例。

基本调优手段

一般情况下Hologres就能达到非常高的写入性能,如果在数据写入过程中觉得性能不符合预期,可以通过以下方法进行常规调优。

避免使用公网,减少网络开销:Hologres提供VPC、经典、公网等网络类型,适用场景请参见网络配置。推荐在进行数据写入时,尤其是使用JDBC、PSQL等业务应用连接Hologres时,尽量使用VPC网络连接而不是公网连接。因为公网有流量限制,相比VPC网络会更加不稳定。

尽量使用Fixed Plan写入:一个SQL在Hologres中的执行流程如下图所示,详细原理请参见执行引擎

 

SQL为普通OLAP写入,那么就会走左侧的链路,会经过优化器(QO)、执行引擎(QE)等一系列组件,数据写入或更新时会整个表拿锁即表锁,如果是并发执行INSERT、UPDATE或DELETE命令,那么SQL间会相互等锁,导致延迟较高。

SQL为点查点写,那么就会走右侧的执行链路,此链路统称为Fixed Plan。Fixed Plan的Query特征足够简单,没有QO等组件的开销,因此在写入或更新时是行锁,这样就能极大的提高Query的并发能力及性能。

因此在优化写入或更新性能时,优先考虑让Query尽量走Fixed Plan。Query走Fixed Plan走Fixed Plan的SQL需要符合一定的特征,常见未走Fixed Plan的情形如下。

使用insert on conflict语法进行多行插入更新时,示例如下。

 

INSERT INTO test_upsert(pk1, pk2, col1, col2)

    VALUES (1, 2, 5, 6), (2, 3, 7, 8)ON CONFLICT (pk1, pk2)

    DO UPDATE SET

 col1 = excluded.col1, col2 = excluded.col2;使用insert on conflict语法进行局部更新时,结果表的列和插入数据的列没有一一对应。结果表中有SERIAL类型的列。结果表设置了Default属性。基于主键的update或delete,如:update table set col1 = ?, col2 = ? where pk1 = ? and pk2 = ?;。

使用了Fixed Plan不支持的数据类型。如果SQL没有走Fixed Plan,那么在管理控制台监控指标中实时导入RPS指标则会显示插入类型为INSERT,示例如下。没有走Fixed Plan的SQL,其执行引擎类型为HQE或PQE,大多数情况的写入为HQE。因此当发现写入或更新较慢时,可以通过如下示例语句查看慢Query日志,排查Query的执行引擎类型(engine_type)。--示例查看过去3小时未走Fixed Plan的insert/update/deleteSELECT

 *FROM

    hologres.hg_query_logWHERE

    query_start >= now() - interval '3 h'

    AND command_tag IN ('INSERT', 'UPDATE', 'DELETE')

    AND ARRAY['HQE'] && engine_typeORDER BY

    query_start DESCLIMIT 500;尽量将执行引擎类型为HQE的Query改写为符合Fixed Plan特征的SDK SQL,从而提高性能。重点关注如下GUC参数,建议DB级别开启GUC参数,更多关于Fixed Plan的使用请参见Fixed Plan加速SQL执行

场景

GUC设置

说明

支持使用insert on conflict语法多行记录的Fixed Plan写入。

 

alter database <databasename> set hg_experimental_enable_fixed_dispatcher_for_multi_values =on;

建议DB级别开启。

支持含有SERIAL类型列的Fixed Plan写入。

 

alter database <databasename> set hg_experimental_enable_fixed_dispatcher_autofill_series =on;

不建议为表设置SERIAL类型,对写入性能有一定的牺牲。Hologres从 V1.3.25版本开始此GUC参数默认为on。

支持Default属性的列的Fixed Plan写入。

Hologres从 V1.3版本开始,使用insert on conflict语法写入数据时含有设置了Default属性的字段,则会默认走Fixed Plan。

不建议为表设置Default属性,对写入性能有一定的牺牲。Hologres V1.1版本不支持含有设置了Default属性的字段走Fixed Plan,从V1.3版本开始支持。

基于主键的UPDATE。

 

alter database <databasename> set hg_experimental_enable_fixed_dispatcher_for_update =on;

Hologres从 V1.3.25版本开始此GUC参数值默认为on。

基于主键的DELETE。

alter database <databasename> set hg_experimental_enable_fixed_dispatcher_for_delete =on;

Hologres从 V1.3.25版本开始此GUC参数值默认为on。

如果SQL走了Fixed Plan,如下图所示监控指标实时导入RPS的类型为SDK。并且在慢Query日志中SQL的engine_type也为SDK。

走了Fixed Plan后写入仍然比较慢。

SQL已经走了Fixed Plan仍然耗时较长,可能原因如下:通常情况是这个表既有Fixed Plan的SDK写入或更新,又有HQE或PQE的写入或更新,HQE是表锁,会导致SDK的写入因为等锁而耗时较长。可以根据如下SQL查询当前表是否有HQE的操作,根据业务情况优化为SDK的SQL。--查询表在过去3小时未走Fixed Plan的insert/update/deleteSELECT

    *FROM

    hologres.hg_query_logWHERE

    query_start >= now() - interval '3 h'

    AND command_tag IN ('INSERT', 'UPDATE', 'DELETE')

    AND ARRAY['HQE'] && engine_type

    AND table_write = '<table_name>'ORDER BY  query_start DESCLIMIT 500;如果表都是SDK写入,但仍然慢,观察CPU使用率监控指标,若是持续较高,可能已经达到实例资源瓶颈,可适当进行扩容。

开启Binlog会降低写入吞吐:Hologres Binlog记录了数据变更记录(INSERT、UPDATE、DELETE),会完整的记录每行数据的变更情况。为某张表打开Binlog,以UPDATE语句为例,示例SQL如下:update tbl set body =new_body where id='1';由于Binlog记录的是整行所有字段的数据,因此在生成Binlog的过程中,需要通过过滤字段(示例中的id字段)去点查目标表的整行数据。如果是列存表的话,这种点查SQL相比行存表会消耗更多的资源,因此开启Binlog的表在写入性能上行存表 > 列存表。同一张表避免并发实时和离线写入:离线写入如MaxCompute写入Hologres时是表锁,实时写入大多数是Fixed Plan写入为行锁(例如Flink实时写入或者DataWorks数据集成实时写入),如果对同一个表并发执行离线写入和实时写入,那么离线写入就会拿表锁,实时写入会因为等锁而导致写入性能慢。所以建议同一张表避免并发进行实时和离线写入。

Holo Client或JDBC写入调优

Holo Client、JDBC等客户端在写入数据时,提高写入性能的调优手段如下。

攒批写入数据:在通过客户端写入时,攒批写入相比单条写入能够提供更高的吞吐,从而提升写入性能。

使用Holo Client会自动攒批,建议使用Holo Client默认配置参数,详情请参见通过Holo Client读写数据。使用JDBC时可以在JDBC连接串配置WriteBatchedInserts=true,如下所示则可实现攒批的功能,更多JDBC详情请参见JDBCjdbc:postgresql://{ENDPOINT}:{PORT}/{DBNAME}?ApplicationName={APPLICATION_NAME}&reWriteBatchedInserts=true未攒批的SQL改造成可攒批的SQL示例如下。 --未攒批的两个sqlinsert into data_t values (1, 2, 3);insert into data_t values (2, 3, 4);--攒批后的sqlinsert into data_t values (1, 2, 3), (4, 5, 6);--攒批的另一种写法insert into data_t select unnest(ARRAY[1, 4]::int[]), unnest(ARRAY[2, 5]::int[]), unnest(ARRAY[3, 6]::int[]);

使用Prepared Statement模式写入数据:Hologres兼容PostgreSQL生态,并基于Postgres的extended协议,支持了Prepared Statement模式,会缓存服务器的SQL编译结果,从而降低了FE、QO等组件的开销,提高写入的性能。相关技术原理请参见如何支持超高QPS在线服务(点查)场景JDBC、Holo Client使用Prepared Statement模式写入数据请参见JDBC

Flink写入调优

各类型表的注意事项如下。

Binlog源表:Flink消费Hologres Binlog支持的数据类型有限,对不支持的数据类型(如SMALLINT)即使不消费此字段,仍然可能导致作业无法上线。从Flink引擎VVR-6.0.3-Flink-1.15 版本开始,支持通过JDBC模式消费Hologres Binlog,此模式下支持更多数据类型,详情请参见Flink全托管。开启Binlog的Hologres表建议使用行存表。列存表开启Binlog会使用较多的资源,影响写入性能。

维表:维表必须使用行存表或行列共存表,列存表对于点查场景性能开销较大。创建行存表时必须设置主键,并且将主键配置为Clustering Key时性能较好。维表的主键必须是Flink Join ON的字段,Flink Join ON的字段也必须是表的完整主键,两者必须完全匹配。

结果表:宽表Merge或局部更新功能对应的Hologres表必须有主键,且每个结果表都必须声明和写入主键字段,必须使用InsertOrUpdate的写入模式。每个结果表的ignoredelete属性都必须设置为true,防止回撤消息产生Delete请求。列存表的宽表Merge场景在高RPS的情况下,CPU使用率会偏高,建议关闭表中字段的Dictionary Encoding。结果表有主键场景,建议设置segment_key,可以在写入和更新时快速定位到数据所在的底层文件。推荐使用时间戳、日期等字段作为segment_key,并且在写入时使对应字段的数据与写入时间有强相关性。

Flink参数配置建议:Hologres Connector各参数的默认值是大多数情况下的最佳配置。如果出现以下情况,可以酌情修改参数。

Binlog消费延迟比较高:默认读取Binlog批量大小(binlogBatchReadSize)为100,如果单行数据的byte size并不大,可以增加此参数,可以优化消费延迟。

维表点查性能较差:设置async参数为true开启异步模式。此模式可以并发地处理多个请求和响应,从而连续的请求之间不需要阻塞等待,提高查询的吞吐。但在异步模式下,无法保证请求的绝对顺序。有关异步请求的原理请参见维表JOIN与异步优化。维表数据量较大且更新不频繁时,推荐使用维表缓存优化查询性能。相应参数设置为cache = 'LRU',同时默认的cacheSize较保守,为10000行,推荐根据实际情况调大一些。

连接数不足:connector默认使用JDBC方式实现,如果Flink作业较多,可能会导致Hologres的连接数不足,此时可以使用connectionPoolName参数实现同一个TaskManager中,连接池相同的表可以共享连接。

作业开发推荐:Flink SQL相对DataStream来说,可维护性高且可移植性强,因此推荐使用Flink SQL来实现作业。如果业务需要使用DataStream,更推荐使用Hologres DataStream Connector,详情请参见Hologres DataStream Connector。如果需要开发自定义Datastream作业,则推荐使用Holo Client而不是JDBC,即推荐使用的作业开发方式排序为:Flink SQL > Flink DataStream(connector) > Flink DataStream(holo-client) > Flink DataStream(JDBC)。

写入慢的原因排查:很多情况下,写入慢也可能是Flink作业中其他步骤的问题。您可以拆分Flink作业的节点,并观察Flink作业的反压情况,是否在读数据源或一些复杂的计算节点已经反压,数据进入到Hologres结果表的速率已经很慢,此时优先排查Flink侧是否有可以优化的地方。如果Hologres实例的CPU使用率很高(如长时间达到100%),写入延迟也比较高,则可以考虑是Hologres侧的问题。其他常见异常信息和排查方法请参见Blink和Flink常见问题及诊断

数据集成调优

 

并发配置与连接的关系:数据集成中非脚本模式作业的连接数为每个并发三个连接,脚本模式作业可通过maxConnectionCount参数配置任务的总连接数,或者insertThreadCount参数配置单并发的连接数。一般情况下,无需修改并发和连接数就能达到很好的性能,可根据实际业务情况适当修改。

独享资源组:数据集成大部分作业都需要使用独享资源组,因此独享资源组的规格也决定着任务的性能上限。 为了保证性能,推荐作业一个并发对应独享资源组1 Core。 如果资源组规格过小,但任务并发高可能会存在JVM内存不足等问题。同样的如果独享资源组的带宽打满也会影响写入任务的性能上限,如果发生此现象,建议对任务进行拆解,把大任务拆成小任务并分配到不同的资源组上。关于数据集成独享资源组的规格指标请参见性能指标。写入慢时如何排查是数据集成或上游慢还是Hologres侧的问题?数据集成写Hologres时,如果数据集成读端的等待时间比写端的等待时间大,通常情况是读端慢导致。如果Hologres实例的CPU使用率很高(如长时间达到100%),写入延迟也比较高,则可以考虑是Hologres侧的问题。

高级调优手段

基本调优手段已经覆盖提升写入性能的基本方法,若是使用正确就能达到很好的写入性能。但是在实际情况中,还有一些其他因素影响性能,比如索引的设置、数据的分布等,高级调优将会介绍在基本调优手段的基础上,如何进一步的排查并提升写入性能,适用于对Hologres原理有进一步了解的业务。

数据倾斜导致写入慢:如果数据倾斜或Distribution Key设置的不合理,就会导致Hologres实例的计算资源出现倾斜,导致资源无法高效使用从而影响写入性能,排查数据倾斜和对应问题解决方法请参见查看Worker倾斜关系

Segment Key设置不合理导致写入慢:写入列存表时,设置了不合理的Segment Key可能会极大的影响写入性能,表已有数据量越多性能下降越明显。这是因为Segment Key用于底层文件分段,在写入或更新时Hologres会根据主键反查对应的旧数据,列存表的反查操作需要通过Segment Key快速定位到数据所在的底层文件。如果这张列存表没有配置Segment Key或者Segment Key配置了不合理的字段或者Segment Key对应的字段在写入时没有与时间有强相关性(比如基本乱序),那反查时需要扫描的文件将会非常之多,不仅会有大量的IO操作,而且也可大量占用CPU,影响写入性能和整个实例的负载。此时管控台监控页面的IO吞吐指标往往表现为即使主要是写入作业,其Read指标也非常高。因此推荐使用时间戳、日期等字段作为Segment Key,并且在写入时使对应字段的数据与写入时间有强相关性。

Clustering Key设置不合理导致写入慢:有主键(PK)的情况下,在写入或更新时,Hologres会根据主键反查对应的旧数据。对于行存表来说,当Clustering Key与PK不一致时,反查就会需要反查两次,即分别按照PK索引和Clustering Key索引,这种行为会增加写入的延时,所以对于行存表推荐Clustering Key和PK保持一致。对于列存表,Clustering Key的设置主要会影响查询性能,不会影响写入性能,可以暂不考虑。

十三.优化MaxCompute外部表的查询性能

本文为您介绍在Hologres中如何优化查询MaxCompute外部表数据的性能。Hologres与MaxCompute在底层资源无缝打通,您可以通过以下方式加速查询MaxCompute的数据:

新建外部表直接加速查询:Hologres中新建外部表,即可直接加速查询外部表数据。无需数据导入导出、无冗余存储。该方式适用于单次查询的数据量小于200 GB的表(与查询字段的大小无关)。导入数据至Hologres进行加速查询:当需要大量分析计算外部表数据并建立与内部表的连接时,您可以在Hologres中新建内部表并导入外部表数据。根据业务需求,为内部表指定合适的Distribute Key索引属性,加快查询速度。导入外部表数据相比新建外部表方式查询速度更快。该方式适用于单次查询的数据量大于等于200 GB的表,以及使用复杂查询、包含索引查询、更新数据或插入数据的场景。导入MaxCompute外部表数据至Hologres的操作请参见使用SQL导入MaxCompute的数据至Hologres。您还可以根据实际业务需求,通过优化查询语句、修改MaxCompute数据源表、合理配置资源和参数,优化查询外部表数据的性能。

优化查询语句:使用如下方式优化查询语句,避免查询外部表数据时扫描全表:查询数据时使用select a from xx语句查询指定内容,不推荐使用select * from xx。增加过滤分区的条件或减少扫描的分区数,实现减少扫描的数据量。

修改MaxCompute数据源表

通过修改MaxCompute数据源表优化查询数据的性能,方式如下:转换MaxCompute表为Hash Clustering表:Hash Clustering表可以优化Bucket Pruning、Aggregation以及存储。创建MaxCompute表时,如果使用Clustered By指定了Hash Key,则MaxCompute对指定列进行Hash运算,并分散Hash值至各个Bucket中。请选择重复键值少的列作为Hash Key。如果没有指定Hash Key,则使用如下语句指定。ALTER TABLE table_name [CLUSTERED BY (col_name [, col_name, ...]) [SORTED BY (col_name [ASC | DESC] [, col_name [ASC | DESC] ...])] INTO number_of_buckets BUCKETS]:ALTER TABLE语句适用于指定存量表的Hash Key。新增聚集属性后,新的分区数据写入MaxCompute时直接执行Hash Clustering计算。同时,您可以使用INSERT OVERWRITE语句对原有的源表数据执行Hash Clustering计算。Hash Clustering表不支持INSERT INTO语句,您需要使用INSERT OVERWRITE语句添加数据。Hash Clustering表不支持通过Tunnel方式上传数据至Range Cluster表。合并小文件:MaxCompute中的小文件数量较多时,会降低查询表数据的速度。您可以在Hologres中执行如下语句,查看Query命中的文件数量:explain analyze <query>;查询结果中的file_count表示MaxCompute中的文件数。如果当前小文件数量较多,影响查询速度,您可以在MaxCompute中对小文件进行合并,具体操作请参见合并小文件

合理配置参数:查询外部表时,Hologres会设置一些默认的参数来提高读取数据的并发度,从而提高查询效率。如果您具有有特殊需求,可以按照业务场景合理配置如下参数(如下参数是经过内部调校和实验的最佳规格,一般情况下,不太建议更改),hg_foreign_table_executor_max_dop建议不要设置太低的值,例如设置为1。尤其是在实例既有写入又有查询的时候,不建议直接设置为1,会导致负载集中在某些Worker上引发实例OOM。--调整每次query命中的分区数大小,默认512,最大为1024,不建议调整太大,会影响查询性能set hg_foreign_table_max_partition_limit = 128;--调整每次读取MaxCompute表batch的大小,默认8192。set hg_experimental_query_batch_size = 4096;--设置MaxCompute表访问切分split的数目,可以调节并发数目,默认64MB,当表很大时需要调大,避免过多的split影响性能。该参数在Hologres 1.1中生效。set hg_foreign_table_split_size = 128; --设置访问外表时的最大并发度,默认为实例的Core数,最大为128,不建议设置大,避免外表query(特别是数据导入场景)影响其它query,导致系统繁忙导致报错。该参数在Hologres 1.1中生效。set hg_foreign_table_executor_max_dop = 32;--设置访问外表时执行DML语句的最大并发度,默认值为32,针对数据导入导出场景专门优化的参数,避免导入操作占用过多系统资源,该参数在Hologres 1.1中生效。set hg_foreign_table_executor_dml_max_dop = 16;

采用全新外部表查询引擎

Hologres V0.10版本开始,将会采用全新的MaxCompute查询引擎,相比低于V0.10版本的实例,查询性能约有 30% ~ 100%的提升。

使用限制:该功能仅Hologres V0.10及以上版本支持,请在Hologres管理控制台的实例详情页查看当前实例版本,如果您的实例是V0.10以下版本,请您使用自助升级或加入Hologres钉钉交流群反馈,详情请参见如何获取更多的在线支持?。从Hologres V1.1版本开始,查询MaxCompute数据时,默认使用全新外表查询引擎,该功能仅适用于独享实例,不适用于共享实例。当前仅对MaxCompute ORC类型的表有加速效果,暂不支持对Cfile等文件进行加速,请确保MaxCompute与Hologres的数据类型映射正确,否则加速效果不明显。

使用方式:Hologres实例升级到V0.10版本之后,您可以使用如下开关参数开启全新外表查询引擎。

--session级别

set hg_experimental_enable_access_odps_orc_via_holo = on;

--DB级别

alter database <databasename> set hg_experimental_enable_access_odps_orc_via_holo = on;

Hologres在V1.1及之后版本中,新外表查询引擎默认启用,可以通过以下参数配置。

--session级别

set hg_enable_access_odps_orc_via_holo = on;

--DB级别

alter database <databasename> set hg_enable_access_odps_orc_via_holo = on;

 

 

 

 

 

 

 

 

posted @ 2024-03-15 10:14  数据阮小白  阅读(669)  评论(0编辑  收藏  举报