本文展示了PolarDB MySQL版的Tair缓存节点的性能,并与普通只读节点进行性能对比,以及与Redis读写分离版的只读节点进行性能对比。
测试环境
PolarDB MySQL版集群
测试环境信息 | 说明 |
产品版本 | 企业版 |
系列 | 集群版 |
数据库引擎版本 | MySQL 8.0.2 |
节点 | 1个主节点RW,1个普通只读节点,1个Tair缓存节点 |
规格 | 每个节点规格都为2核16GB |
Redis读写分离版实例
测试环境信息 | 说明 |
部署模式 | 云原生 |
系列 | 标准版 |
架构类型 | 不启用集群 |
分片规格 | 16 GB |
读写分离 | 开启 |
测试客户端
PolarDB MySQL版测试客户端
测试环境信息 | 说明 |
部署压测工具的设备 | 云服务器ECS |
规格 | 2 vCPU 16 GB |
操作系统 | 4.19.91-27.6.al7.x86_64 |
网络 | VPC网络,与PolarDB MySQL版的PolarProxy延迟约0.1ms |
Redis读写分离版测试客户端
测试环境信息 | 说明 |
部署压测工具的设备 | 云服务器ECS |
规格 | 32 vCPU 128 GiB |
操作系统 | 4.19.91-27.6.al7.x86_64 |
网络 | 与Redis读写分离版实例同属一个VPC,延迟约0.1ms |
测试内容
创建数据库表到Tair的映射
mysql -h XXX -P XXX -u XXX -pXXX -e "create database if not exists XXX"
# Create mapping for string type
mysql -h XXX -P XXX -u XXX -pXXX -e "insert into mysql.tair_string_containers values ('sbtest', 'XXX', 'sbtest1', 'id', 'myfield', 'primary')"
# Create mapping for hash type
mysql -h XXX -P XXX -u XXX -pXXX -e "insert into mysql.tair_hash_containers values ('sbtest', 'XXX', 'sbtest1', 'id', 'myfield', 'primary')"
# flush tair after creating the mapping to make the mapping work
mysql -h XXX -P XXX -u XXX -pXXX -e "flush tair"
测试场景
以下场景如无特别说明,PolarDB MySQL版的SCC(全局一致性(高性能模式))功能都为开启状态。
场景一:三种节点读查询性能对比
场景描述:对比相同数据的情况下,Redis、PolarDB MySQL版的普通只读节点、PolarDB MySQL版的Tair缓存节点三者的读查询性能。
测试数据概况:测试数据长度分别为16、128、256字节,数据量均为1,000,000条。
场景二:不同缓存命中率下性能对比
场景描述:对比不同缓存命中率(0%、50%、75%、100%)下,PolarDB MySQL版的Tair缓存节点的读查询性能。
测试数据概况:命中率通过控制数据量来实现。
无数据,但查询key的范围在[1, 1000000],此时即命中率为0%。
数据key的范围在[1, 500000],查询key的范围在[1, 1000000],使用随机读的方式进行测试,此时即命中率为50%。
数据key的范围在[1, 750000],查询key的范围在[1, 1000000],使用随机读的方式进行测试,此时即命中率为75%。
数据key的范围在[1, 1000000],查询key的范围在[1, 1000000],使用随机读的方式进行测试,此时即命中率为100%。
场景三:开启和关闭SCC下性能对比
场景描述:对比PolarDB MySQL版开启和关闭SCC(全局一致性(高性能模式))功能的情况下,Tair缓存节点的读查询性能。
测试数据概况:给主节点施加UPDATE压力,同时给Tair缓存节点施加GET压力,分别对比开启和关闭SCC功能下的数据一致性。
测试工具
memtier_benchmark
由于PolarDB MySQL版中表经过映射后,在Tair缓存节点中的key的格式为mapping_name@col_value
,因此本文使用开源Redis的memtier_benchmark工具对Redis和PolarDB MySQL版Tair缓存节点进行压测。memtier_benchmark工具支持自定义key的前缀。
安装
docker pull redislabs/memtier_benchmark:latest
使用
# test GET command
docker run --rm redislabs/memtier_benchmark:latest -h XXX -a XXX:XXX --distinct-client-seed --key-maximum=XXX --key-minimum=1 -n 100000 --ratio=0:1 --threads=24 --clients=4 --key-prefix='sbtest@' --data-size=XXX --print-percentiles 95,99 --hide-histogram
# test HGET command
docker run --rm redislabs/memtier_benchmark:latest -h XXX -a XXX:XXX --distinct-client-seed --key-maximum=XXX --key-minimum=1 -n 100000 --threads=24 --clients=4 --key-prefix='sbtest@' --data-size=XXX --command "hget __key__ myfield" --print-percentiles 95,99 --hide-histogram
参数说明:
参数 | 说明 |
-h | 指定服务器的 IP 地址或主机名 |
-a | 实例用户名和密码 |
--distinct-client-seed | 每个client使用不同的随机种子 |
--key-maximum | Key ID的最小值,默认为0 |
--key-minimum | Key ID的最大值,默认10000000 |
-n | 每个客户端发出的请求数量 |
--ratio | Set:Get 比例, 默认1:10 |
--threads | 设置线程的数量 |
--clients | 设置并发客户端的数量 |
--key-prefix | Key前缀,默认“memtier-” |
--data-size | value的长度,单位Byte |
--print-percentiles | 打印分位点 |
--hide-histogram | 不显示响应时间的柱状图 |
Sysbench
使用Sysbench工具对PolarDB MySQL版的主节点和普通只读节点进行压测。
安装
yum -y install make automake libtool pkgconfig libaio-devel
yum -y install mariadb-devel openssl-devel
./autogen.sh
./configure
make -j
make install
使用
# for read
sysbench --db-driver=mysql --mysql-host=XXX --mysql-port=XXX --mysql-user=XXX --mysql-password=XXX --mysql-db=XXX --table_size=XXX --tables=1 --events=0 --threads=96 --time=XXX --point_selects=1 my_oltp_read_only --value-length=XXX run
# for update
sysbench --db-driver=mysql --mysql-host=XXX --mysql-port=XXX --mysql-user=XXXX --mysql-password=XXX --mysql-db=XXX --table_size=XXX --tables=1 --events=0 --threads=96 --rate=XXX update --non_index_updates=1 --value-length=XXX --time=XXX run
参数说明:
参数 | 说明 |
--db-driver | 指定数据库驱动,本场景下为 |
--mysql-host | PolarDB MySQL版的连接地址 |
--mysql-port | PolarDB MySQL版的端口号 |
--mysql-user | 连接PolarDB MySQL版的用户名 |
--mysql-password | 连接PolarDB MySQL版的用户密码 |
--mysql-db | 用于测试的数据库名 |
--table_size | 设置每个表的行数 |
--tables | 设置要创建和测试的表的数量 |
--events | 设置测试期间要执行的总事件数(0表示事件数无限制,测试会根据 |
--threads | 设置并发线程的数量 |
--time | 设置测试的持续时间(秒) |
--point_selects | 每个查询中执行的点查找(单行查询)操作的数量 |
--value-length | 值的长度 |
--non_index_updates | 每个查询中执行的更新非索引的操作的数量 |
--rate | 每秒的transaction数量 |
为了达到测试目的,本测试中对Sysbench自带的脚本进行了一定的调整:
修改了Sysbench自带的oltp_read_only脚本,以满足自定义数据的长度。具体如下:
require("oltp_common")
local new_options = {
value_length = {"Value length", 16},
}
for k, v in pairs(new_options) do
sysbench.cmdline.options[k] = v
end
sysbench.cmdline.commands.prepare = {
function ()
local drv = sysbench.sql.driver()
local con = drv:connect()
for i = sysbench.tid % sysbench.opt.threads + 1, sysbench.opt.tables,
sysbench.opt.threads do
create_table(drv, con, i)
end
end,
sysbench.cmdline.PARALLEL_COMMAND
}
function create_table(drv, con, table_num)
engine_def = "/*! ENGINE = " .. sysbench.opt.mysql_storage_engine .. " */"
query = string.format([[
CREATE TABLE sbtest%d(
id INTEGER NOT NULL AUTO_INCREMENT,
myfield TEXT NOT NULL,
PRIMARY KEY (`id`)
) %s %s]], table_num, sysbench.opt.value_length, engine_def, sysbench.opt.create_table_options)
con:query(query)
if (sysbench.opt.table_size > 0) then
print(string.format("Inserting %d records into 'sbtest%d'",
sysbench.opt.table_size, table_num))
end
value_template = string.rep("#", sysbench.opt.value_length)
query = "INSERT INTO sbtest" .. table_num .. "(myfield) VALUES"
con:bulk_insert_init(query)
for i = 1, sysbench.opt.table_size do
value = sysbench.rand.string(value_template)
query = string.format("('%s')", value)
con:bulk_insert_next(query)
end
con:bulk_insert_done()
end
function prepare_statements()
prepare_for_each_table("point_selects")
end
local t = sysbench.sql.type
local stmt_defs = {
point_selects = {
"SELECT myfield FROM sbtest%u WHERE id=?",
t.INT},
}
function prepare_for_each_table(key)
for t = 1, sysbench.opt.tables do
sql = string.format(stmt_defs[key][1], t)
stmt[t][key] = con:prepare(sql)
local nparam = #stmt_defs[key] - 1
if nparam > 0 then
param[t][key] = {}
end
for p = 1, nparam do
local btype = stmt_defs[key][p+1]
local len
if type(btype) == "table" then
len = btype[2]
btype = btype[1]
end
if btype == sysbench.sql.type.VARCHAR or
btype == sysbench.sql.type.CHAR then
param[t][key][p] = stmt[t][key]:bind_create(btype, len)
else
param[t][key][p] = stmt[t][key]:bind_create(btype)
end
end
if nparam > 0 then
stmt[t][key]:bind_param(unpack(param[t][key]))
end
end
end
function event()
execute_point_selects()
check_reconnect()
end
在SCC开启和关闭的测试对比场景下,本测试修改了Sysbench自带的oltp_update_non_index脚本,从而实现向主节点施加UPDATE压力:
require("oltp_common")
local new_options = {
value_length = {"Value length", 16},
}
for k, v in pairs(new_options) do
sysbench.cmdline.options[k] = v
end
function prepare_statements()
prepare_for_each_table("non_index_updates")
end
local t = sysbench.sql.type
local stmt_defs = {
non_index_updates = {
"UPDATE sbtest%u SET myfield=? WHERE id=?",
{t.VARCHAR, 256}, t.INT},
}
function prepare_for_each_table(key)
for t = 1, sysbench.opt.tables do
sql = string.format(stmt_defs[key][1], t)
stmt[t][key] = con:prepare(sql)
local nparam = #stmt_defs[key] - 1
if nparam > 0 then
param[t][key] = {}
end
for p = 1, nparam do
local btype = stmt_defs[key][p+1]
local len
if type(btype) == "table" then
len = btype[2]
btype = btype[1]
end
if btype == sysbench.sql.type.VARCHAR or
btype == sysbench.sql.type.CHAR then
param[t][key][p] = stmt[t][key]:bind_create(btype, len)
else
param[t][key][p] = stmt[t][key]:bind_create(btype)
end
end
if nparam > 0 then
stmt[t][key]:bind_param(unpack(param[t][key]))
end
end
end
local function get_id()
return sysbench.rand.default(1, sysbench.opt.table_size)
end
function execute_non_index_updates()
local tnum = 1
value_template = string.rep("#", sysbench.opt.value_length)
for i = 1, sysbench.opt.non_index_updates do
param[tnum].non_index_updates[1]:set_rand_str(value_template)
param[tnum].non_index_updates[2]:set(get_id())
stmt[tnum].non_index_updates:execute()
end
end
function event()
execute_non_index_updates()
check_reconnect()
end
测试指标
测试指标 | 说明 |
QPS |
|
Average Latency | 操作的平均延迟分布,单位为毫秒(ms)。 |
95th Percentile Latency | 95%操作的最大延迟时间,单位为毫秒(ms)。 例如:该指标的值为0.5毫秒,表示95%的请求可以在0.5毫秒内被处理。 |
99th Percentile Latency | 99%操作的最大延迟时间,单位为毫秒(ms)。 例如:该指标的值为0.5毫秒,表示99%的请求可以在0.5毫秒内被处理。 说明 由于Sysbench的输出中只能指定一个分位点,本测试中指定为95%,因此 PolarDB MySQL版普通只读节点的测试结果中不包含该指标。 |
测试结果
场景一:三种节点读查询性能对比
String数据结构
value长度(字节) | 节点类型 | QPS(次/秒) | Average Latency(毫秒) | 95th Percentile Latency(毫秒) | 99th Percentile Latency(毫秒) |
16 | Redis读写分离版 只读节点 | 302444.97 | 0.313 | 0.451 | 0.575 |
PolarDB MySQL版 Tair缓存节点 (开启SCC) | 201673.35 | 0.513 | 0.767 | 1.231 | |
PolarDB MySQL版 Tair缓存节点 (关闭SCC) | 251194.09 | 0.419 | 0.623 | 2.039 | |
PolarDB MySQL版 普通只读节点 | 67261.98 | 1.43 | 0.94 | - | |
128 | Redis读写分离版 只读节点 | 293564.77 | 0.336 | 0.03 | 0.599 |
PolarDB MySQL版 Tair缓存节点 (开启SCC) | 183765.10 | 0.530 | 0.831 | 1.799 | |
PolarDB MySQL版 Tair缓存节点 (关闭SCC) | 242174.42 | 0.403 | 0.583 | 1.647 | |
PolarDB MySQL版 普通只读节点 | 66282.00 | 1.45 | 0.97 | - | |
256 | Redis读写分离版 只读节点 | 290125.14 | 0.340 | 0.487 | 0.567 |
PolarDB MySQL版 Tair缓存节点 (开启SCC) | 174216.94 | 0.516 | 0.919 | 2.479 | |
PolarDB MySQL版 Tair缓存节点 (关闭SCC) | 239536.55 | 0.406 | 0.575 | 0.823 | |
PolarDB MySQL版 普通只读节点 | 66575.22 | 1.44 | 0.99 | - |
Hash数据结构
Hash数据结构场景中,数据库表的定义与String数据结构场景相同,因此PolarDB普通只读节点的读性能与上表相同,此处没有重复展示。
value长度(字节) | 节点类型 | QPS(次/秒) | Average Latency(毫秒) | 95th Percentile Latency(毫秒) | 99th Percentile Latency(毫秒) |
16 | Redis读写分离版 只读节点 | 294997.32 | 0.322 | 0.471 | 0.559 |
PolarDB MySQL版 Tair缓存节点 (开启SCC) | 186980.47 | 0.528 | 0.839 | 1.407 | |
PolarDB MySQL版 Tair缓存节点 (关闭SCC) | 241858.95 | 0.406 | 0.583 | 0.935 | |
128 | Redis读写分离版 只读节点 | 278540.78 | 0.347 | 0.503 | 0.591 |
PolarDB MySQL版 Tair缓存节点 (开启SCC) | 174051.46 | 0.441 | 0.871 | 1.759 | |
PolarDB MySQL版 Tair缓存节点 (关闭SCC) | 242724.15 | 0.402 | 0.575 | 0.887 | |
256 | Redis读写分离版 只读节点 | 257195.14 | 0.386 | 0.551 | 0.647 |
PolarDB MySQL版 Tair缓存节点 (开启SCC) | 170178.62 | 0.573 | 0.935 | 2.895 | |
PolarDB MySQL版 Tair缓存节点 (关闭SCC) | 242568.36 | 0.402 | 0.583 | 1.031 |
场景二:不同缓存命中率下性能对比
命中率(%) | QPS(次/秒) | Average Latency(毫秒) | 95th Percentile Latency(毫秒) | 99th Percentile Latency(毫秒) |
0 | 149090.39 | 0.653 | 0.719 | 1.775 |
50 | 165684.15 | 0.602 | 0.679 | 1.135 |
75 | 188725.06 | 0.505 | 0.623 | 1.287 |
100 | 215733.45 | 0.481 | 0.703 | 2.271 |
场景三:开启和关闭SCC下性能对比
无论是否开启SCC,tair节点都会回放redo log更新数据,这会占据部分CPU资源。
Update/s 表示在RW节点执行随机update的速率(次/秒)
该场景下缓存命中率均为100%。
value长度(字节) | SCC状态 | UPDATE(次/秒) | QPS(次/秒) | Average Latency(毫秒) | 95th Percentile Latency(毫秒) | 99th Percentile Latency(毫秒) |
16 | 开启 | 1000 | 165789.91 | 0.544 | 0.967 | 1.567 |
2000 | 156138.20 | 0.627 | 1.143 | 2.287 | ||
4000 | 111491.45 | 0.797 | 1.391 | 2.495 | ||
8000 | 105940.97 | 0.959 | 1.607 | 2.623 | ||
关闭 | 1000 | 240805.05 | 0.405 | 0.575 | 0.887 | |
2000 | 239148.58 | 0.412 | 0.583 | 1.095 | ||
4000 | 227816.33 | 0.420 | 0.575 | 0.951 | ||
8000 | 205837.92 | 0.474 | 0.791 | 1.791 | ||
128 | 开启 | 1000 | 160965.60 | 0.589 | 1.111 | 2.463 |
2000 | 160991.79 | 0.642 | 1.175 | 2.335 | ||
4000 | 131926.87 | 0.840 | 1.751 | 2.751 | ||
8000 | 97805 | 0.977 | 1.655 | 2.623 | ||
关闭 | 1000 | 243004.86 | 0.406 | 0.567 | 0.879 | |
2000 | 230458.07 | 0.411 | 0.583 | 0.967 | ||
4000 | 234980.81 | 0.424 | 0.583 | 1.159 | ||
8000 | 190149.23 | 0.500 | 0.759 | 1.959 | ||
256 | 开启 | 1000 | 169201.09 | 0.587 | 1.063 | 2.399 |
2000 | 142985.23 | 0.629 | 1.111 | 2.127 | ||
4000 | 112945.09 | 0.817 | 1.383 | 2.383 | ||
8000 | 94641.14 | 0.989 | 1.591 | 2.591 | ||
关闭 | 1000 | 234171.71 | 0.419 | 0.607 | 1.255 | |
2000 | 232775.40 | 0.418 | 0.583 | 1.055 | ||
4000 | 215226.14 | 0.447 | 0.599 | 1.007 | ||
8000 | 189603.56 | 0.520 | 0.775 | 1.823 |
测试结论
在主节点无写流量的情况下,三种节点的读查询QPS性能排序为:
Redis > PolarDB MySQL版Tair缓存节点(关闭SCC) > PolarDB MySQL版Tair缓存节点(开启SCC) >> PolarDB MySQL版普通只读节点
在开启SCC的情况下,命中率越高,QPS越高。当命中率为100%时,QPS比命中率为0%时约高44.7%。
在开启SCC的情况下,主节点的写压力越大,Tair缓存节点的QPS降低越明显。这是因为一方面Tair缓存节点为保证强一致性,需要等待Redo Log回放到相应点位以获取最新的值;另一方面随着写压力的增大,Tair缓存节点回放Redo Log会占用更多的CPU资源。
在关闭SCC的情况下,主节点的写压力越大,Tair缓存节点的QPS越低。但是相比于开启SCC的情况,关闭SCC时Tair缓存节点受的影响要小很多。