本文为您介绍如何通过阿里云性能测试PTS对Hologres的性能进行压测,帮助您快速验证Hologres的性能。
Hologres简介
Hologres是兼容PostgreSQL协议的实时交互式分析引擎。支持海量数据实时写入、实时更新、实时分析,既支持PB级数据多维分析(OLAP)与即席分析(Ad Hoc),又支持高并发低延迟的在线数据服务(Serving)。
Hologres支持连接多种开发工具,您可以使用HoloWeb登录Hologres实例,创建数据库并在SQL编辑器中执行如下语句,完成内部表、外部表的创建和写入测试数据。关于HoloWeb的简介与使用方法,请参见HoloWeb简介。
Hologres支持多种数据源的数据同步,包括MaxCompute数据同步、OSS数据湖同步、通过Copy命令导入本地文件、通过DataWorks同步MySQL等数据库数据,详情参见数据同步概述。
创建Hologres内部表的同时,需要为其设置合适的索引,以获得最优的查询性能。关于索引的创建,详情请参见CREATE TABLE。
前提条件
已购买并开通Hologres实例,开通方法请参见购买Hologres。
已开通PTS服务。
本示例使用RAM用户
pts-test
来做测试,请确保您已创建RAM用户并授予AliyunHologresReadOnlyAccess
权限。且已在HoloWeb授权该用户访问目标DB。
方案概览
本实践通过性能测试PTS使用TPC-H(商业智能计算测试)对Hologres在OLAP查询场景、Key/Value点查场景和数据更新场景的性能进行测试。使用PTS进行性能压测,您无需准备测试所需的基础环境,但您仍需针对不同的测试场景准备相应数据及测试语句。
OLAP查询场景:需要创建列存表,从数据源中同步数据,设计OLAP查询测试语句。
Key/Value点查场景:需要创建行存表,从数据源中同步数据,设计Key/Value点查测试语句。
数据更新场景:需要创建表,写入原始数据,并准备需要进行更新的数据。
本文的TPC-H的实现基于TPC-H的基准测试,并不能与已发布的TPC-H基准测试结果相比较,本文中的测试并不符合TPC-H基准测试的所有要求。
本文以MaxCompute数据源为例,将MaxCompute公共空间
MAXCOMPUTE_PUBLIC_DATA
中的TPC-H 100 GB数据写入Hologres,作为本文的测试数据。
OLAP查询场景
主要使用列存表,使用TPC-H测试中的22条查询语句进行测试。
1. 配置Hologres测试数据
创建外部表
SQL语句如下所示:
DROP FOREIGN TABLE IF EXISTS odps_customer_100g; DROP FOREIGN TABLE IF EXISTS odps_lineitem_100g; DROP FOREIGN TABLE IF EXISTS odps_nation_100g; DROP FOREIGN TABLE IF EXISTS odps_orders_100g; DROP FOREIGN TABLE IF EXISTS odps_part_100g; DROP FOREIGN TABLE IF EXISTS odps_partsupp_100g; DROP FOREIGN TABLE IF EXISTS odps_region_100g; DROP FOREIGN TABLE IF EXISTS odps_supplier_100g; IMPORT FOREIGN SCHEMA "MAXCOMPUTE_PUBLIC_DATA#default" LIMIT to ( odps_customer_100g, odps_lineitem_100g, odps_nation_100g, odps_orders_100g, odps_part_100g, odps_partsupp_100g, odps_region_100g, odps_supplier_100g ) FROM SERVER odps_server INTO public OPTIONS(if_table_exist 'error',if_unsupported_type 'error');
创建内部表
OLAP场景测试需要使用列存表(默认即为列存),还需要创建合适的索引以达到更优的查询性能。更多关于表属性的信息请参见CREATE TABLE。
SQL语句如下所示:
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; DROP TABLE IF EXISTS PARTSUPP; BEGIN; CREATE TABLE PARTSUPP ( PS_PARTKEY INT NOT NULL, PS_SUPPKEY INT NOT NULL, PS_AVAILQTY INT NOT NULL, PS_SUPPLYCOST DECIMAL(15,2) NOT NULL, PS_COMMENT TEXT NOT NULL, PRIMARY KEY(PS_PARTKEY,PS_SUPPKEY) ); CALL set_table_property('PARTSUPP', 'distribution_key', 'PS_PARTKEY'); CALL set_table_property('PARTSUPP', 'bitmap_columns', 'PS_PARTKEY,PS_SUPPKEY,PS_AVAILQTY,PS_COMMENT'); CALL set_table_property('PARTSUPP', 'dictionary_encoding_columns', 'PS_COMMENT'); COMMIT; DROP TABLE IF EXISTS PART; BEGIN; CREATE TABLE PART ( P_PARTKEY INT NOT NULL PRIMARY KEY, P_NAME TEXT NOT NULL, P_MFGR TEXT NOT NULL, P_BRAND TEXT NOT NULL, P_TYPE TEXT NOT NULL, P_SIZE INT NOT NULL, P_CONTAINER TEXT NOT NULL, P_RETAILPRICE DECIMAL(15,2) NOT NULL, P_COMMENT TEXT NOT NULL ); CALL set_table_property('PART', 'distribution_key', 'P_PARTKEY'); CALL set_table_property('PART', 'bitmap_columns', 'P_PARTKEY,P_SIZE,P_NAME,P_MFGR,P_BRAND,P_TYPE,P_CONTAINER,P_COMMENT'); CALL set_table_property('PART', 'dictionary_encoding_columns', 'P_NAME,P_MFGR,P_BRAND,P_TYPE,P_CONTAINER,P_COMMENT'); COMMIT; DROP TABLE IF EXISTS CUSTOMER; BEGIN; CREATE TABLE CUSTOMER ( C_CUSTKEY INT NOT NULL PRIMARY KEY, C_NAME TEXT NOT NULL, C_ADDRESS TEXT NOT NULL, C_NATIONKEY INT NOT NULL, C_PHONE TEXT NOT NULL, C_ACCTBAL DECIMAL(15,2) NOT NULL, C_MKTSEGMENT TEXT NOT NULL, C_COMMENT TEXT NOT NULL ); CALL set_table_property('CUSTOMER', 'distribution_key', 'C_CUSTKEY'); CALL set_table_property('CUSTOMER', 'bitmap_columns', 'C_CUSTKEY,C_NATIONKEY,C_NAME,C_ADDRESS,C_PHONE,C_MKTSEGMENT,C_COMMENT'); CALL set_table_property('CUSTOMER', 'dictionary_encoding_columns', 'C_NAME,C_ADDRESS,C_PHONE,C_MKTSEGMENT,C_COMMENT'); COMMIT; DROP TABLE IF EXISTS SUPPLIER; BEGIN; CREATE TABLE SUPPLIER ( S_SUPPKEY INT NOT NULL PRIMARY KEY, S_NAME TEXT NOT NULL, S_ADDRESS TEXT NOT NULL, S_NATIONKEY INT NOT NULL, S_PHONE TEXT NOT NULL, S_ACCTBAL DECIMAL(15,2) NOT NULL, S_COMMENT TEXT NOT NULL ); CALL set_table_property('SUPPLIER', 'distribution_key', 'S_SUPPKEY'); CALL set_table_property('SUPPLIER', 'bitmap_columns', 'S_SUPPKEY,S_NAME,S_ADDRESS,S_NATIONKEY,S_PHONE,S_COMMENT'); CALL set_table_property('SUPPLIER', 'dictionary_encoding_columns', 'S_NAME,S_ADDRESS,S_PHONE,S_COMMENT'); COMMIT; DROP TABLE IF EXISTS NATION; BEGIN; CREATE TABLE NATION( N_NATIONKEY INT NOT NULL PRIMARY KEY, N_NAME text NOT NULL, N_REGIONKEY INT NOT NULL, N_COMMENT text NOT NULL ); CALL set_table_property('NATION', 'distribution_key', 'N_NATIONKEY'); CALL set_table_property('NATION', 'bitmap_columns', 'N_NATIONKEY,N_NAME,N_REGIONKEY,N_COMMENT'); CALL set_table_property('NATION', 'dictionary_encoding_columns', 'N_NAME,N_COMMENT'); COMMIT; DROP TABLE IF EXISTS REGION; BEGIN; CREATE TABLE REGION ( R_REGIONKEY INT NOT NULL PRIMARY KEY, R_NAME TEXT NOT NULL, R_COMMENT TEXT ); CALL set_table_property('REGION', 'distribution_key', 'R_REGIONKEY'); CALL set_table_property('REGION', 'bitmap_columns', 'R_REGIONKEY,R_NAME,R_COMMENT'); CALL set_table_property('REGION', 'dictionary_encoding_columns', 'R_NAME,R_COMMENT'); COMMIT;
测试数据写入并收集统计信息
说明Hologres从V2.1.17版本起支持Serverless Computing能力,针对大数据量离线导入、大型ETL作业、外表大数据量查询等场景,使用Serverless Computing执行该类任务可以直接使用额外的Serverless资源,避免使用实例自身资源,无需为实例预留额外的计算资源,显著提升实例稳定性、减少OOM概率,且仅需为任务单独付费。Serverless Computing详情请参见Serverless Computing概述,Serverless Computing使用方法请参见Serverless Computing使用指南。
SQL语句如下所示:
-- (可选)推荐使用Serverless Computing执行大数据量离线导入和ETL作业 SET hg_computing_resource = 'serverless'; INSERT INTO public.customer SELECT * FROM public.odps_customer_100g ; INSERT INTO public.lineitem SELECT * FROM public.odps_lineitem_100g ; INSERT INTO public.nation SELECT * FROM public.odps_nation_100g ; INSERT INTO public.orders SELECT * FROM public.odps_orders_100g ; INSERT INTO public.part SELECT * FROM public.odps_part_100g ; INSERT INTO public.partsupp SELECT * FROM public.odps_partsupp_100g ; INSERT INTO public.region SELECT * FROM public.odps_region_100g ; INSERT INTO public.supplier SELECT * FROM public.odps_supplier_100g ; -- 清理写入文件 vacuum nation; vacuum region; vacuum supplier; vacuum customer; vacuum part; vacuum partsupp; vacuum orders; vacuum lineitem; -- 收集表的统计信息 analyze nation; analyze region; analyze lineitem; analyze orders; analyze customer; analyze part; analyze partsupp; analyze supplier; -- 针对非主键的JOIN KEY收集统计信息 analyze lineitem (l_orderkey,l_partkey,l_suppkey); analyze orders (o_custkey); analyze partsupp(ps_partkey,ps_suppkey); -- 重置配置,保证非必要的SQL不会使用serverless资源。 RESET hg_computing_resource;
2. 配置PTS压测场景
登录PTS控制台,选择性能测试 > 创建场景,然后单击PTS压测。
删除默认HTTP节点。
添加一个JDBC节点并配置,如下所示:
说明在实际测试过程中,一个业务会话内的压测节点顺序执行,不同业务会话间并行执行。在本文的测试场景中,只需保留一个业务会话,通过配置后续的并发数来模拟并行场景。
基本请求信息页签重要配置项说明:
数据库类型:选择PostgreSQL。
数据库URL:格式为:
{ENDPOINT}:{PORT}/{DBNAME}
,您可从实时数仓Hologres > 实例列表 > 实例详情页面获取。用户名:输入RAM用户
pts-test
的AccessKey ID。密码:输入RAM用户
pts-test
的AccessKey Secret。SQL:待执行的SQL语句,本文使用TPC-H 22条查询语句,SQL语句详情请参见TPC-H 22条查询语句。
连接池配置页签配置项说明:
初始化连接数:初始化时建立物理连接的个数,本示例设置为
1
。获取连接最大等待时间:这里保持默认即可。
最大连接数:最大活跃连接数量,本示例设置为
15
,以匹配单并发与多并发多种测试场景。最小连接数:池中最小空闲连接数量,本示例设置为
1
。
根据步骤c,配置其他查询语句,针对此OLAP查询场景测试,TPC-H包含22条查询语句。您可采用API复制功能,在同一条业务会话下创建22个JDBC节点,并依次添加TPC-H 22条查询语句。在压测过程中,PTS会依次循环执行这22条查询语句。
施压配置
本示例选择阿里云VPC内网压测,其他施压配置参数需要根据测试目的的不同进行调整。使用TPC-H数据进行OLAP场景测试,主要关注的是单条Query的执行时长,参数配置如下:
参数
说明
最大虚拟用户数
本示例设置为1,以确保每条query执行时有充足的资源。
递增模式
选择手动调速,由于此处不涉及并发数的变化,因此只需保证并发数始终为1即可。
压测总时长
此处涉及22条查询语句,因此测试时长选择60分钟。
指定IP数
指发起压测流量的IP地址数量,即施压机数量。此处只需设置1个施压IP。
3. 调试并启动压测
调试场景可验证配置是否合理,避免压测失败,建议您先调试场景。
单击保存去压测,在温馨提示页面,选择立即执行并勾选确认本次压测已获得准许并遵守当地法律,然后单击启动压测。
4. 压测报告查询与分析
压测完成后,在压测报告页签单击查看。
在报告详情页面的概览页签,可以查看压测场景运行成功与否。
压测运行成功,可以查看成功率、平均RT、TPS/虚拟用户、异常数、总请求数等信息。其中,平均RT对应平均查询延迟,TPS指包含连接时间的每秒事务处理量。在本文的性能测试中,TPS与QPS大小一致。
压测运行失败,请求数等指标均为0,可以通过选择查看采样日志 > 点击查看详情 > Error信息查看报错信息,进行问题排查。
在报告详情页面单击明细,在压测报告明细页签查看每个压测节点(对应每个Query)的相关指标。
如本文的OLAP查询场景,针对单个业务会话中包含多个压测节点的场景,查看单个压测节点的指标。
在不配置虚拟用户数时,OLAP查询场景关注的指标为Query的查询时长,即对应下图的平均RT,本文测试得到的TPC-H 22条Query执行时长之和约为
25 s
。在配置虚拟用户数后的OLAP场景测试结果如下,此时需要同时关注查询时间及QPS两项指标。可以看出,随着虚拟用户数的逐步提升,测试的QPS结果保持在5附近。您也可以前往明细页,选择对应时间段来查看每个虚拟用户数下的性能结果。
更多关于压测报告的内容查看与分析,详情请参见查看JDBC压测报告。
Key/Value点查场景
主要使用行存表,针对ORDERS使用行存表后,进行主键过滤的点查。
1. 配置Hologres准备测试数据
Key/Value点查场景继续使用OLAP场景创建的数据库,使用TPC-H数据集中的ORDERS
表进行测试。您在登录Hologres实例后,可以执行如下语句建表并直接从OLAP场景的ORDERS
表中写入数据。
创建内部表
由于点查场景需要使用行存表,所以不能使用OLAP查询场景中创建的内部表,需要重新创建一张内部表。Key/Value点查场景测试需要设置主键并使用行存表,还需要设置合适的索引以达到更优的查询性能。更多关于表属性的信息请参见CREATE TABLE。
SQL语句如下所示:
DROP TABLE IF EXISTS public.orders_row; BEGIN; CREATE TABLE public.orders_row( O_ORDERKEY INT 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('public.orders_row', 'orientation', 'row'); CALL SET_TABLE_PROPERTY('public.orders_row', 'clustering_key', 'o_orderkey'); CALL SET_TABLE_PROPERTY('public.orders_row', 'distribution_key', 'o_orderkey'); COMMIT;
写入数据
-- (可选)推荐使用Serverless Computing执行大数据量离线导入和ETL作业 SET hg_computing_resource = 'serverless'; INSERT INTO public.orders_row SELECT * FROM public.orders; -- 重置配置,保证非必要的SQL不会使用serverless资源。 RESET hg_computing_resource;
2. 配置PTS压测场景
登录PTS控制台,选择性能测试 > 创建场景,然后单击PTS压测。
删除默认HTTP节点。
添加一个JDBC节点并配置,如下所示:
说明在实际测试过程中,一个业务会话内的压测节点顺序执行,不同业务会话间并行执行。在本文的测试场景中,只需保留一个业务会话,通过配置后续的并发数来模拟并行场景。
基本请求信息页签重要配置项说明:
数据库类型:选择PostgreSQL。
数据库URL:格式为:
{ENDPOINT}:{PORT}/{DBNAME}
,您可从实时数仓Hologres > 实例列表 > 实例详情页面获取。用户名:输入RAM用户
pts-test
的AccessKey ID。密码:输入RAM用户
pts-test
的AccessKey Secret。SQL:Key/Value点查场景的SQL与OLAP场景不同,查询方式可以分为单值点查与多值点查,样例SQL如下,详情请参见测试方案介绍。
单值点查
SQL语句如下所示:
SELECT O_ORDERKEY, O_CUSTKEY, O_ORDERSTATUS, O_TOTALPRICE, O_ORDERDATE, O_ORDERPRIORITY, O_CLERK, O_SHIPPRIORITY, O_COMMENT FROM public.orders_row WHERE o_orderkey = ?;
多值点查,此处以9个值为例。
SQL语句如下所示:
SELECT O_ORDERKEY, O_CUSTKEY, O_ORDERSTATUS, O_TOTALPRICE, O_ORDERDATE, O_ORDERPRIORITY, O_CLERK, O_SHIPPRIORITY, O_COMMENT FROM public.orders_row WHERE o_orderkey IN (?, ?, ?, ?, ?, ?, ?, ?, ?);
占位符页签配置项说明:
Type:本示例占位符Type使用
bigint
。由于Key/Value点查场景需要随机生成待查询的主键值,在PTS中可以通过配置占位符实现。在SQL中使用?
作为占位符,并在占位符中依次配置?
代表的值。重要SQL中
?
的数量与占位符的数量需要相等,且二者按顺序一一对应。即本实践中需要为多值点查的SQL示例配置九行占位符。Value:PTS支持若干系统函数,本示例使用
random
函数,即Value值为:${sys.random(1,99999999)}
。
连接池配置页签配置项说明:
初始化连接数:初始化时建立物理连接的个数,Key/Value点查场景涉及并发,这里先将压测节点的连接数配置为
20
,对应后文施压配置中的每一台施压机都会产生20个连接。获取连接最大等待时间:这里保持默认即可。
最大连接数:最大活跃连接数量,本示例设置为
20
。最小连接数:池中最小空闲连接数量,本示例设置为
20
。
施压配置
本示例选择阿里云VPC内网压测,其他施压配置参数需要根据测试目的的不同进行调整。
参数
说明
压力模式
本示例选择并发模式。
最大并发
由于本文Key/Value点查场景针对Hologres 64CU规格实例的测试并发数为
500
,本示例设为500
。因此,上文的连接池配置中,每台机器在压测节点上产生的连接数配置为20
,以达到需要测试的500
并发数。递增模式
由于本文的测试场景不涉及并发数的变化,因此该部分只需保证并发数始终为
500
即可,即本示例选择手动调速。压测总时长
结合测试场景需要进行填写。本场景仅涉及
1
条测试语句,因此测试时长选择5
分钟。指定IP数
指发起压测流量的IP地址数量,即施压机数量。本场景预估达到的QPS为
10
万,单台施压机可以提供的QPS上限为4000
,因此本示例选择25
台施压机。说明PTS在并发模式下,单个施压机可以提供的QPS上限为4000,因此在实际测试场景中,需要结合可能达到的QPS值来确定施压机数量,而后确定连接池配置。
3. 调试并启动压测
调试场景可验证配置是否合理,避免压测失败,建议您先调试场景。
单击保存去压测,在温馨提示页面,选择立即执行并勾选确认本次压测已获得准许并遵守当地法律,然后单击启动压测。
4. 压测结果查询与分析
对于单值点查,本实践压测得到的QPS平均值超过
100000
。对于多值点查,以30个值的批量点查为例,本实践得到的测试结果如下,其中QPS平均值为
34819
。最终的QPS性能为图示平均QPS值乘以批量点查数量30
,即34819 * 30 =1044570
。
数据更新场景
主要用于测试OLAP引擎在有主键的情况下数据更新的性能。
本文的数据更新场景使用与Key/Value点查场景相同的数据表。
在配置PTS压测场景内容中,仅JDBC压测节点信息中的SQL语句与 Key/Value点查场景单值点查不同(此处忽略多值点查情况),其余配置项均可保持一致。
数据更新场景的SQL语句如下所示:
INSERT INTO public.orders_row (o_orderkey, o_custkey, o_orderstatus, o_totalprice, o_orderdate, o_orderpriority, o_clerk, o_shippriority, o_comment) VALUES (?, 1, 'demo', 1.1, '2021-01-01', 'demo', 'demo', 1, 'demo') ON CONFLICT (o_orderkey) DO UPDATE SET (o_orderkey, o_custkey, o_orderstatus, o_totalprice, o_orderdate, o_orderpriority, o_clerk, o_shippriority, o_comment) = ROW (excluded.*);
启动压测和报告分析请参考Key/Value点查场景。
常见报错
在调试过程中,可能会遇到如下错误,报错原因可能是数据库URL错误、用户名密码错误等,导致连接失败。