性能对比

本文展示了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

--mysql-host

PolarDB MySQL版的连接地址

--mysql-port

PolarDB MySQL版的端口号

--mysql-user

连接PolarDB MySQL版的用户名

--mysql-password

连接PolarDB MySQL版的用户密码

--mysql-db

用于测试的数据库名

--table_size

设置每个表的行数

--tables

设置要创建和测试的表的数量

--events

设置测试期间要执行的总事件数(0表示事件数无限制,测试会根据 --time参数运行指定的时间)

--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

  • 对于Redis和PolarDB MySQL版Tair缓存节点,QPS表示每秒执行的查询数(含GET、HGET,视测试场景而定)。

  • 对于PolarDB MySQL版的主节点和普通只读节点,QPS表示每秒执行的SELECT查询数。由于设置了每个transaction只包含1个查询,因此QPS=TPS。

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

测试结论

  1. 在主节点无写流量的情况下,三种节点的读查询QPS性能排序为:

    Redis > PolarDB MySQL版Tair缓存节点(关闭SCC) > PolarDB MySQL版Tair缓存节点(开启SCC) >> PolarDB MySQL版普通只读节点

  2. 在开启SCC的情况下,命中率越高,QPS越高。当命中率为100%时,QPS比命中率为0%时约高44.7%。

  3. 在开启SCC的情况下,主节点的写压力越大,Tair缓存节点的QPS降低越明显。这是因为一方面Tair缓存节点为保证强一致性,需要等待Redo Log回放到相应点位以获取最新的值;另一方面随着写压力的增大,Tair缓存节点回放Redo Log会占用更多的CPU资源。

  4. 在关闭SCC的情况下,主节点的写压力越大,Tair缓存节点的QPS越低。但是相比于开启SCC的情况,关闭SCC时Tair缓存节点受的影响要小很多。