优化写入和更新性能

当您Hologres的表数据写入或更新的性能无法达到业务预期时,可根据本文提供的写入瓶颈判断方法分析其具体原因(上游数据读取较慢,或达到了Hologres的资源瓶颈等),从而选择合适的调优手段,帮助业务实现更高的数据写入和更新性能。

背景信息

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

基本原理

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

  • 不同表存储格式的写入或更新性能。

    • 全列写入或更新时,性能排序如下。

      行存 > 列存 > 行列共存

    • 部分列写入或更新时,性能排序如下。

      行存 > 行列共存 > 列存

  • 不同写入模式的性能。

    写入模式类型如下。

    写入模式

    说明

    Insert

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

    InsertOrIgnore

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

    InsertOrReplace

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

    InsertOrUpdate

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

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

      • 结果表无主键性能最高。

      • 结果表有主键时:

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

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

      InsertOrReplace = 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执行流程

    • SQL为普通OLAP写入,那么就会走左侧的链路,会经过优化器(QO)、执行引擎(QE)等一系列组件,数据写入或更新时会整个表拿锁即表锁,如果是并发执行INSERTUPDATEDELETE命令,那么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属性。

      • 基于主键的updatedelete,如:update table set col1 = ?, col2 = ? where pk1 = ? and pk2 = ?;

      • 使用了Fixed Plan不支持的数据类型。

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

      --示例查看过去3小时未走Fixed Plan的insert/update/delete
      SELECT
          *
      FROM
          hologres.hg_query_log
      WHERE
          query_start >= now() - interval '3 h'
          AND command_tag IN ('INSERT', 'UPDATE', 'DELETE')
          AND ARRAY['HQE'] && engine_type
      ORDER BY
          query_start DESC
      LIMIT 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也为SDKsql走了fixed plan

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

      SQL已经走了Fixed Plan仍然耗时较长,可能原因如下。

      • 通常情况是该表既有Fixed Plan的SDK写入或更新,又有HQE的写入或更新,HQE是表锁,会导致SDK的写入因为等锁而耗时较长。可以通过以下SQL查询当前表是否有HQE的操作,并根据业务情况优化为SDK的SQL。可以通过HoloWeb Query洞察快速识别出某个Fixed Plan的Query是否有HQE的锁,详情请参见Query洞察

        --查询表在过去3小时未走Fixed Plan的insert/update/delete
        SELECT
            *
        FROM
            hologres.hg_query_log
        WHERE
            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 DESC
        LIMIT 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详情请参见JDBC

      jdbc:postgresql://{ENDPOINT}:{PORT}/{DBNAME}?ApplicationName={APPLICATION_NAME}&reWriteBatchedInserts=true

    未攒批的SQL改造成可攒批的SQL示例如下。

    --未攒批的两个sql
    insert into data_t values (1, 2, 3);
    insert into data_t values (2, 3, 4);
    
    --攒批后的sql
    insert 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的设置主要会影响查询性能,不会影响写入性能,可以暂不考虑。