为自己的数据库添加YCSB测试 C++语言
- 操作系统:Linux/Mac
- 编程语言:C++11
代码说明:
- Github上的权威YCSB测试代码(Java版):https://github.com/brianfrankcooper/YCSB
- 原C++代码框架:https://github.com/basicthinker/YCSB-C
- 为自实现的数据库
HWDB
添加YCSB测试的代码样例:https://github.com/zzh-wisdom/YCSB-HWDB
第二个是一位大佬根据第一个翻译过来的C++版本的代码。然后第三个代码都是实验室学长实现的,我只是在学习学习~,这里附上学长的代码链接:
https://github.com/a993096281/YCSB-HWDB
代码中还包含对RocksDB
的YCSB测试的实现,还是很有参考意义的。
代码框架简要说明:
- core // YSCB测试底层逻辑的实现
- db // 自定义数据库的实现
- lib // 一些独立功能的实现
- test_sh // 测试脚本
- workloads // 工作负载属性设置文件
- ycsbc.cc // YCSB的主逻辑
下面是为自己实现的数据库添加YCSB测试的过程,以及部分代码的解释。
1. 为需要测试的数据库,实现db.h
中的接口函数
在文件core/db.h
文件中定义了一个抽象类DB
。
namespace ycsbc {
class DB {
public:
typedef std::pair<std::string, std::string> KVPair;
static const int kOK = 0;
static const int kErrorNoData = 1;
static const int kErrorConflict = 2;
virtual void Init() { }
virtual void Close() { }
virtual int Read(const std::string &table, const std::string &key,
const std::vector<std::string> *fields,
std::vector<KVPair> &result) = 0;
virtual int Scan(const std::string &table, const std::string &key, const std::string &max_key,
int record_count, const std::vector<std::string> *fields,
std::vector<std::vector<KVPair>> &result) = 0;
virtual int Update(const std::string &table, const std::string &key,
std::vector<KVPair> &values) = 0;
virtual int Insert(const std::string &table, const std::string &key,
std::vector<KVPair> &values) = 0;
virtual int Delete(const std::string &table, const std::string &key) = 0;
virtual bool HaveBalancedDistribution() { return true; };
virtual void PrintStats() {};
virtual ~DB() { }
};
} // ycsbc
中间的五个函数为未实现的虚函数。
为自己的数据库定一个类,并继承抽象类DB
时,必须要实现这五个虚函数。另外几个已经实现的虚函数也可以根据需要进行重写。
文件db/basic_db.h
中给出了一个最简单的实现样例,可以参考。
在为自己的数据库实现接口时,推荐使用链接库的方式,这样可以将自己数据库的代码与YSCB测试的代码分离。编译时,通过链接库的方式将自己数据库的代码与YCSB编译到一起。
- 链接库放置在:
/usr/local/lib/
路径下。 - 头文件放置在:
/usr/local/include/
路径下
使用时包含相应的头文件即可。
注意的是,编译时需要加上链接库参数,如-lrocksdb
。
2. 实现数据库的创建函数
文件db/db_factory.cc
文件中,只实现了一个方法CreateDB
。
该函数的功能是根据传入的参数创建对应数据库的类(前一节实现的),然后返回值为它的父类DB
。
这个函数可以看成是YCSB代码与自己实现的数据库代码之间连接的桥梁。
3. 主要逻辑实现ycsbc.cc
该文件是测试主逻辑实现的地方,可以直接复用已有的代码,不做任何修改。这里主要说一下该文件实现的逻辑。
1) 参数解释
首先使用ParseCommandLine
函数解释从命令行中传入的参数,参数存放到类utils::Properties
中。
int main( const int argc, const char *argv[]) {
utils::Properties props;
Init(props);
string file_name = ParseCommandLine(argc, argv, props);
...
}
命令行参数主要包括以下几个:
- -threads n:使用n个线程执行(默认值:1)
- -db dbname: 指定要使用的数据库名称(默认值:basic)
- -P propertyfile:从给定文件加载参数属性(有一定的格式,可以使用#进行行注释,使用=号连接属性名和值)。 可以指定多个文件,并将按照指定的顺序进行处理
- -host ip:指定ip地址
- -port port:指定port端口
更多的参数可以参考函数ParseCommandLine
的实现,也可以添加一些参数,辅助测试,比如对于一些嵌入式数据库,往往需要指定数据库数据存放的路径。
3) Workload
参数加载
函数ParseCommandLine
还会从-P
参数传入的文件中读取YCSB测试工作负载的参数。这部分是代码框架本来就实现的。
文件core/core_workload.h
包含YCSB工作负载属性设置的逻辑。包含:
-
-table tablename:YCSB测试所操作的表的名字,默认为“usertable”
-
-recordcount n:记录(key-value)个数n
-
-keylength n:key的长度n
-
-fieldlength n:字段(相当于列)取值的长度n
-
-fieldcount n:字段的个数n
-
-field_len_dist:字段长度分布的方式,选项包括:“uniform”, “zipfian” (有利于简短记录), 和 “constant”,默认为“constant”
-
-operationcount n:操作的个数n
-
-readallfields:用于决定是读取记录的一个字段(false)还是读取所有字段(true)的属性名称。
-
-readproportion f:读操作的比例f,如0.5
-
-updateproportion f:更新操作的比例
-
-scanproportion f:范围查找操作的比例
-
-insertproportion f:插入操作的比例
-
-requestdistribution op:key请求的分配方式,选项包括:“uniform”, “zipfian” 和 “latest”,默认为“uniform”
-
-maxscanlength n:范围查找的最大长度(记录数),默认是1000
-
-scanlengthdistribution:范围查找长度的分配方式,选项包括:“uniform”, “zipfian” (有利于简短记录)。默认为“uniform”
上面只是罗列一些主要的参数选项,更详细的参数选项,参考文件
core/core_workload.h
和core/core_workload.cc
文件,里面有相应的注释说明。
从函数ParseCommandLine
的实现逻辑,可以得到负载属性设置文件的书写格数如下:
- 一行只能设置一个属性
- 属性使用等号赋值,前面是属性名,后面是值。例如:
keylength=16
- 可以使用
#
符号对行注释
下面是负载属性设置文件的一个例子:
# Yahoo! Cloud System Benchmark
# Workload A: Update heavy workload
# Application example: Session store recording recent actions
#
# Read/update ratio: 50/50
# Default data size: 1 KB records (10 fields, 100 bytes each, plus key)
# Request distribution: zipfian
keylength=16
fieldcount=1
fieldlength=16
# 共3.2GB
recordcount=100000000
operationcount=10000000
workload=com.yahoo.ycsb.workloads.CoreWorkload
readallfields=true
readproportion=0.5
updateproportion=0.5
scanproportion=0
insertproportion=0
requestdistribution=zipfian
负载属性设置文件均放置在目录./wordloads
下,且文件均以.spec
结尾(实际上,任何后缀名的文本文件都可以)。
4) 测试主逻辑
实际测试的运行由DelegateClient
函数实现。声明如下:
int DelegateClient(ycsbc::DB *db, ycsbc::CoreWorkload *wl, const int num_ops, bool is_loading)
其中
ycsbc::DB *db
通过之前所说的db/db_factory.cc
文件的CreateDB
方法创建。ycsbc::CoreWorkload *wl
通过存放参数的类utils::Properties
来初始化:wl.Init(props)
,是有关负载属性设置的类。- 参数
num_ops
表示操作的总次数 is_loading
指定当前是否是数据加载操作。
DelegateClient
函数根据参数情况,执行num_ops
次数的数据加载操作,或者num_ops
次数的测试操作,具体如何测试由参数wl
决定。
更具体的实现逻辑在目录./core
下,想了解具体如何实现的可以进一步细看。
通过创建多线程执行DelegateClient
函数,可以模拟多并发的过程,该函数会在全局变量数组temp_cnt
和temp_time
中保存不同类型操作的次数和所花的时间。
4. 测试运行
编译程序后,将生成可执行文件ycsbc
。
下面是在我的代码下(mac系统),为RocksDB
实现的YCSB测试的执行示例:
(如何在Mac系统安装RocksDB,可以参考我写的文章:RocksDB使用入门 Mac)
- 执行命令:
./ycsbc -db rocksdb -dbpath /tmp/rocksdb-test -threads 4 -P "workloads/workloada.spec" -load true -run true
- 执行结果:
zzh@zzhdeMBP YCSB-HWDB % ./ycsbc -db rocksdb -dbpath /tmp/rocksdb-test -threads 4 -P "workloads/workloada.spec" -load true -run true
---- dbname:rocksdb dbpath:/tmp/rocksdb-test ----
dbname:rocksdb
dboption:0
dbpath:/tmp/rocksdb-test
dbstatistics:false
dbwaitforbalance:false
fieldcount:1
fieldlength:16
insertproportion:0
keylength:16
load:true
morerun:
operationcount:100000
readallfields:true
readproportion:0.5
recordcount:100000
requestdistribution:zipfian
run:true
scanproportion:0
threadcount:4
updateproportion:0.5
workload:com.yahoo.ycsb.workloads.CoreWorkload
----------------------------------------
********** load result **********
loading records:100000 use time:0.397 s IOPS:251946.29 iops (3.97 us/op)
*********************************
********** run result **********
all opeartion records:100000 use time:0.260 s IOPS:384455.69 iops (2.60 us/op)
read ops : 50182 use time: 0.199 s IOPS:252622.78 iops (3.96 us/op)
update ops: 49818 use time: 0.831 s IOPS:59946.65 iops (16.68 us/op)
********************************
zzh@zzhdeMBP YCSB-HWDB %