VoltDB实时投票应用性能测试

Wesley13
• 阅读 652

voter是votedb开源包中的一个性能测试程序,代码位于源码包examples/voter/目录下。该程序模拟短时间内大量用户发起投票的场景,测试每秒处理的投票请求(三次读一次写的事务)的能力。官方发布的两个基于该程序的性能测试报告。测试环境部署在12台Amazon E2云主机服务上,服务端版本是voltdb 2.2:
686K TPS with Spring Framework Web App and VoltDB
695k TPS with Node.js and VoltDB

本文将深入到voter程序内部,分析该测试的流程,以及给出自己的性能测试结果。 (本文代码部分的格式正在修改,先请将就看文本的)

1、表结构(ddl.sql)

contestants表存储候选人编号和名字,该表的数据量较小,并且不会改变。

CREATE TABLE contestants 
( 
  contestant_number integer     NOT NULL 
, contestant_name   varchar(50) NOT NULL 
, CONSTRAINT PK_contestants PRIMARY KEY 
  ( 
    contestant_number 
  ) 
);

vote表存储每一次投票的信息,包括投票电话、州名和所投的候选人编号。同时该表的会按电话号码做多节点分区。

CREATE TABLE votes 
( 
  phone_number       bigint     NOT NULL 
, state              varchar(2) NOT NULL 
, contestant_number  integer    NOT NULL 
); 
PARTITION TABLE votes ON COLUMN phone_number;

area_code_state表存储州编号和州名的映射关系。

CREATE TABLE area_code_state 
( 
  area_code smallint   NOT NULL 
, state     varchar(2) NOT NULL 
, CONSTRAINT PK_area_code_state PRIMARY KEY 
  ( 
    area_code 
  ) 
);

根据voter表创建视图v_votes_by_phone_number,表示每个电话号码已经投票的次数(应用限制最大投票次数)
是的,voltdb支持Create View。根据官方文档的解释,VoltDB存储的是物理视图,即每次插入、更新、删除数据都会修改物理视图中所关联记录的数据。

CREATE VIEW v_votes_by_phone_number 
( 
  phone_number 
, num_votes 
) 
AS 
   SELECT phone_number 
        , COUNT(*) 
     FROM votes 
GROUP BY phone_number 
;

根据contestants表创建视图v_votes_by_contestant_number_state,表示每个候选人来自不同州的投票数。

CREATE VIEW v_votes_by_contestant_number_state 
( 
  contestant_number 
, state 
, num_votes 
) 
AS 
   SELECT contestant_number 
        , state 
        , COUNT(*) 
     FROM votes 
GROUP BY contestant_number 
        , state 
;

2、存储过程

voter中定义了5个存储过程,其中主要的Vote存储过程包含4个statement,三次Select和一次Insert
通过候选人编号,查询候选人

public final SQLStmt checkContestantStmt = new SQLStmt( 
        "SELECT contestant_number FROM contestants WHERE contestant_number = ?;");

通过电话号码,查询该号码已投的票数

public final SQLStmt checkVoterStmt = new SQLStmt( 
        "SELECT num_votes FROM v_votes_by_phone_number WHERE phone_number = ?;");

通过州编号,查询州名

public final SQLStmt checkStateStmt = new SQLStmt( 
        "SELECT state FROM area_code_state WHERE area_code = ?;");

将投票的电话号码、来源州、投票对象记录到votes表,同时更新两个视图

public final SQLStmt insertVoteStmt = new SQLStmt( 
        "INSERT INTO votes (phone_number, state, contestant_number) VALUES (?, ?, ?);");

存储过程,输入投票电话、候选人编号,以及每个电话最大投票数(常量)

public long run(long phoneNumber, int contestantNumber, long maxVotesPerPhoneNumber) { 
    voltQueueSQL(checkContestantStmt, EXPECT_ZERO_OR_ONE_ROW, contestantNumber); 
    voltQueueSQL(checkVoterStmt, EXPECT_ZERO_OR_ONE_ROW, phoneNumber); 
     //根据电话号码计算所在州区号 
    voltQueueSQL(checkStateStmt, EXPECT_ZERO_OR_ONE_ROW, (short)(phoneNumber / 10000000l)); 
    VoltTable validation[] = voltExecuteSQL(); 
     //验证输入的候选人编号是否合法 
    if (validation[0].getRowCount() == 0) { 
        return ERR_INVALID_CONTESTANT; 
    } 
     //验证该电话投票数是否到达上限 
    if ((validation[1].getRowCount() == 1) && 
            (validation[1].asScalarLong() >= maxVotesPerPhoneNumber)) { 
        return ERR_VOTER_OVER_VOTE_LIMIT; 
    } 
     //如果计算的州区号没有记录,投票仍然有效,州名记录为XX 
    final String state = (validation[2].getRowCount() > 0) ? validation[2].fetchRow(0).getString(0) : "XX"; 
    //完成投票 
    voltQueueSQL(insertVoteStmt, EXPECT_SCALAR_MATCH(1), phoneNumber, state, contestantNumber); 
    voltExecuteSQL(true); 
    return VOTE_SUCCESSFUL; 
}

另外4个存储过程没有列入性能统计,不再具体介绍,含义如下:

  • Initialize.java 初始化区号表和候选人表 
  • GetStateHeatmap 获取按分区得票数最高的候选人 
  • ContestantWinningStates.java 查询每个候选人早获胜的N个州 
  • Results.java 获得最终投票结果

3、单节点性能测试

测试环境是两台双路E5420服务器,服务端版本采用2013年1月刚发布的VoltDB 3.0开源版:

  • CPU: 2 * E5420 (qual 2.5G)
  • RAM: 8GB
  • LAN: 1000Mbps
  • 1 client,1 server

性能测试的配置参数包括:

  • 服务端处理线程数,分别取1、2(默认值)、4、8、16
  • 客户端连接模式,分别是sync同步、jdbc同步,async异步三种。

考察的性能指标包括:

  • 每秒执行的事务数:tps
  • 平均请求延时:latency_avg
  • 95%最大请求延时:latency_95
  • 99%最大请求延时:latency_99

以下是三种请求模式和不同服务端线程数情况下的性能曲线图,其中8线程异步模式下性能达到11.8万TPS。而两种同步模式下的性能最高值则是服务端4线程,分别是6.3万和5.9万。
(需要再次提醒,所统计的TPS性能是指每秒执行的事务数,包括三次读操作,其中两次是轻量级表的读,一次是对大表视图的读,以及一次带主键并发互斥的写操作。)
VoltDB实时投票应用性能测试

以下两张图分别是平均和95%延时统计。可以看出sync和jdbc两种同步模式下延时一直维持很低(小于5ms)。而async异步模式下,当服务端线程数小于等于8时,也保持低于20ms的水平,但当服务端线程数为16,平均和95%最大延时都出现了大幅的增加,分别达到了131ms和350ms。

VoltDB实时投票应用性能测试 VoltDB实时投票应用性能测试

为了进一步分析上面不同线程数下“延时突变”现象,我有进行了进一步的测试——对线程数进行微调。从4到11每个线程数都测试async异步模式的延时,最终得到如下数据。当线程数低于6时,延时随线程数缓慢上升。而当线程数在7到10,延时则突然下降到小于10ms区间。当线程数增加到11以上后,又发生了延时数据的突变,产生了大幅增长。

VoltDB实时投票应用性能测试

仅从已测试数据来看,我们推测当服务端配置线程数接近服务器可用内核数时,VoltDB达到最优吞吐率和较理想的延时波动。当然由于本次测试的CPU不支持Intel超线程技术,不清楚这个关于内核数的推断应该适用于物理内核数、还是逻辑内核数。
从VoltDB公布的技术架构来看,以上推断也是可以成立的。VoltDB的服务端采用CPU级别的Share-noting设计,为每个线程分配了固定的主键区间,事务操作串行化,这样可以减少数据在CPU缓存和kenel内存区的加载频率,从而提高处理性能。当所服务端线程数与内核数相匹配,便恰好符合该设计。

最后总结以下,VoltDB的处理能力不一定超出Redis等KV数据库,但考虑其SQL和事务能力能力,而KV数据库如果需要应用控制事务性能往往大打折扣。因此,我们可以认为VoltDB是一个性能很强大的内存关系数据库,适合使用在需要实时高性能和事务支持的在线游戏、交易等业务。
(好吧,它是AGPL的,我知道,但总算有个开源数据库供我们学习之)

下期预告

  • VoltDB的集群如何部署?
  • 为什么我把voter服务端部署到两个节点之后,性能反而比单服务器下降了呢?
  • 如何让VoltDB客户端支持路由?
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Android蓝牙连接汽车OBD设备
//设备连接public class BluetoothConnect implements Runnable {    private static final UUID CONNECT_UUID  UUID.fromString("0000110100001000800000805F9B34FB");
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这