目前,在系统设计中引入了越来越多的NoSQL产品,例如Redis/ MongoDB/ HBase等,其中性能指标往往会成为权衡不同NoSQL产品的关键因素。对这些产品在性能表现和产品选择上的争论,Ivan碰到不止一次。虽然通过对系统架构原理方面的分析可以大致判断出其在不同读写场景下的表现,但一是对受众有较高的要求,也来的不那么直接。这时候,没有什么比一次性能测试更有说服力。有什么好的性能测试工具呢?这就是今天的主角YCSB。YCSB是Yahoo开源的一套分布式性能测试工具,方便易用,拓展性强。Ivan最近研究HBase二级索引时用它来做性能测试,感觉还是非常顺手的。虽然网上已经有很多YCSB的介绍文章,但用来指导实际操作还是有些不便。Ivan会用两三篇文章来介绍一下YCSB的实际使用。本文是官方文章的译文,选择这篇文章是因为其与具体操作的关系比较紧密,感兴趣的同学可以了解一下。
运行workload有六个步骤
这些步骤描述都假定你运行一个单客户端。这可以满足中小规模集群(10台左右)的测试需要。对于更大规模的集群,你必须在不同的服务器上运行多个客户端来生成足够的负载。类似地,在某些场景下,多客户机加载数据库可能更快。多客户端并行运行的更多细节,可以查看Running a Workload in Parallel
第一步是安装你希望测试的数据库。可能是单机或者集群,依赖于你要测试的配置。 你必须create 或 set up tables/keyspaces/storage buckkets用于存储记录。这些细节对于每个数据库都不同,依赖于希望运行的负载情况。在YCSB客户端运行前,数据表必须被创建,因为客户端自身是不会请求创建数据库表的。这是因为对于某些系统创建表一个手工操作,而对于其他系统,表必须在集群启动前被创建。 workload所依赖的table必须被创建。对于核心负载,YCSB客户端将假定存在一个名为'usertable'的table,且具有灵活的schema:运行时可以根据需要增加列。'usertable'可以被映射为适当的存储容器。例如,在MySQL中,你可以create table,在Cassandra你可以在配置中定义keyspace。数据库接口层(Step 2描述)会收到读写usertable的请求,将其转换为你所指定的实际存储的请求。这意味着你必须提供数据库接口层帮助它理解下层存储的结构。例如,在Cassandra中,你必须定义在keyspace中定义列族column families。这样,必须创建一个列族并命名(例如,你可以使用values)。然后,数据库访问层需要理解指向values列族,或者将字符串“values”设置为一个属性,或者在数据库接口层中硬编码。
数据库接口层是一个可执行的java类,实现read、write、update、delete和scan调用,它由YCSB客户端生成,调用你的数据库API。这个类是com.yahoo.ycsb包下抽象类DB的子类。在运行YCSB客户端时,你要通过命令行指定类名,客户端会动态加载你的接口类。命令行中指定的任何属性或指定的参数文件,将会传递给数据库接口层实例,用于配置该层(例如,告诉它你要测试的数据库主机名hostname)
YCSB客户端自带一个简单的虚拟接口层,com.yahoo.ycsb.BasicDB。这层会把执行的操作通过System.out打印。这可以用于确认客户端在正常运行,用于debug 你的workload。
如何使用YCSB客户端的细节可以查看 Using the Database Libraries。更多实现数据库接口层的细节,可以查看 Adding a Database
你可以使用YCSB命令,直接运行数据库命令。客户端使用DB接口层发送命令给数据库。你可以使用客户端确定DB层运行正常,你的数据库正确安装,DB层可以连接到数据库等等。它为大量的数据库提供了命令行接口,可以用于检验数据库的数据。运行命令行:
$ ./bin/ycsb shell basic help Commands: read key [field1 field2 ...] - Read a record scan key recordcount [field1 field2 ...] - Scan starting at key insert key name1=value1 [name2=value2 ...] - Insert a new record update key name1=value1 [name2=value2 ...] - Update a record delete key - Delete a record table [tablename] - Get or [set] the name of the table quit - Quit
工作负载定义了在loading阶段将被加载进数据库的数据,在transaction阶段在数据集上执行的操作。 典型的工作负载包括以下内容:
因为数据集的参数属性必须在两个阶段被获得,在loading阶段用于构造和插入适当类型的记录,在transaction阶段用于指定正确的记录id和field,所以参数文件在两个阶段都会使用。workload java class使用这些属性插入记录(loading phase)或操作那些记录(transaction phase)。选择哪个阶段要看你运行YCSB命令行时指定的参数。
在运行YCSB客户端的命令行时,你可以指定java class和参数文件。客户端将动态加载你的workload class,从参数文件解析参数(和任何命令行的附加参数)并执行workload。在loading和transaction两个阶段,需要同样的属性和workload逻辑应用。例如,如果loading阶段创建10个field的记录,而后在transaction阶段必须知道有10个field可以被查询和修改。
YCSB自带的CoreWorkload 是标准workload包可以直接使用。CoreWorkload定义了简单的read/insert/update/scan操作组合。在参数文件中定义了每种操作的相应频率,以及其他workload属性。这样,修改参数文件可以执行不同的workload。更多CoreWorkload的细节,可以查看Core Workloads
如果CoreWorkload不能满足你的需求,你可以基于com.yahoo.ycsb.Workload定义自己的workload子类。细节可以查看 Implementing New Workloads
虽然workload class和参数文件定义了具体的workload,还有一些在运行特定测试时,你可能希望指定附加的设置。这些设置可以通过YCSB客户端命令行实现。设置包括
Workload有两个执行阶段:loading阶段(定义待插入的数据)和transaction阶段(定义数据集上的操作)。为了加载数据,你要运行YCSB客户端并告诉它执行loading阶段。 例如,考虑workload A的benchmark(更多标准workloads细节在Core Workloads)。加载标准数据集
$ ./bin/ycsb load basic -P workloads/workloada
这个命令的关注点
加载HBase数据集:
$ ./bin/ycsb load hbase -P workloads/workloada -p columnfamily=family
这个命令的关注点
如果你使用BasicDB,你将看到数据库的insert语句。如果是你用一个实际的DB接口层,记录会被加载到数据库中。
标准workload参数文件创建很小的数据库,例如,workload仅创建1000条记录。这用于调试你的安装。然而,运行一个实际的benchmark你需要创建一个更大的数据库。例如,想象你需要加载100百万记录。然后,你需要修改workload文件中默认的“recordcount”属性。有两个办法实现。
指定一个新的属性文件,包含recordcount的新值。在命令行中,如果这个文件在workload文件后被指定,它会覆盖workload的任何属性。例如创建"large.dat"文件,仅有一行内容
recordcount=100000000
然后,client执行以下内容
$ ./bin/ycsb load basic -P workloads/workloada -P large.dat
Client会加载所有的属性文件,但使用最后加载的一个文件large.dat中的recordcount值,
通过命令行指定recordcount属性的新值。在命令行指定的任何属性都会覆盖配置文件中的属性。如下执行
$ ./bin/ycsb load basic -P workloads/workloada -p recordcount=100000000
一般来说,好的实践是在新的参数文件中存储任何重要的参数,代替通过命令行指定它们。这使得你的benchmark结果可以被复现。不用必须重建你使用的命令行,你重用参数文件即可。注意,当它开始执行时,YCSB Client会打印处他的命令行,所以如果你将Client的输出存储到一个数据文件,你可以很容易重新执行命令行。 因为一个大数据库加载需要很长时间,你可能希望1.需要Client输出状态,2.直接将输出写入数据文件。这样,你可以执行以下命令加载数据库。
$ ./bin/ycsb load basic -P workloads/workloada -P large.dat -s > load.dat
-s 参数将要求Client向stderr输出状态报告。这样命令行的输出可能是这样
$ ./bin/ycsb load basic -P workloads/workloada -P large.dat -s > load.dat Loading workload... (might take a few minutes in some cases for large data sets) Starting test. 0 sec: 0 operations 10 sec: 61731 operations; 6170.6317473010795 operations/sec 20 sec: 129054 operations; 6450.76477056883 operations/sec ...
这个状态输出会帮助你看到加载操作执行得多快(这样你可以估计加载的完成时间),确认load正在执行。当load完成时,Client会报告load的性能统计数据。这些统计与transaction阶段一样,所以看后续介绍
一旦数据被加载,你就可以执行workload。告诉Client执行transaction操作。执行workload,可以使用以下命令
$ ./bin/ycsb run basic -P workloads/workloada -P large.dat -s > transactions.dat
主要差别是我们使用run参数时,告诉Client执行transaction阶段而不是loading阶段。如果你使用BasicDB,检查结果文件 transactions.dat,你会看到一个read和update混合的请求,与统计数据一致。
典型情况下,你会希望使用 -threads 和 -target 参数控制负荷量。例如,你可能希望10个线程每秒总数100个操作。平均操作延时不高于100ms,每个线程能够携带每秒10此操作。一般来说,你需要足够的线程因为没有线程尝试每秒更多的操作,否则你达到的吞吐量将小于指定的目标吞吐量。 这个例子,我们可以执行
$ ./bin/ycsb run basic -P workloads/workloada -P large.dat -s -threads 10 -target 100 > transactions.dat
注意这个例子,我们使用 -threads 10 命令参数指定10个线程, -target 100 命令参数指定每秒100次操作。否则,两个值可以设置在你的参数文件中,使用threadcount 和 target 属性代替。例如
threadcount=10 target=100
run的结尾,Client会向stdout报告性能统计数据。上面的例子,统计数据会写入transaction.dat文件。默认包括每个操作类型延时的average,min,max,95th,99th。每次操作返回代码的统计,每类操作的直方图。返回值被你的DB接口层定义,允许你看到workload过程中的任何错误。上述例子中,我们可以得到输出:
[OVERALL],RunTime(ms), 10110 [OVERALL],Throughput(ops/sec), 98.91196834817013 [UPDATE], Operations, 491 [UPDATE], AverageLatency(ms), 0.054989816700611 [UPDATE], MinLatency(ms), 0 [UPDATE], MaxLatency(ms), 1 [UPDATE], 95thPercentileLatency(ms), 1 [UPDATE], 99thPercentileLatency(ms), 1 [UPDATE], Return=0, 491 [UPDATE], 0, 464 [UPDATE], 1, 27 [UPDATE], 2, 0 [UPDATE], 3, 0 [UPDATE], 4, 0 ...
这个输出指标
读操作有与之接近的统计数值
延时信息的直方图通常是有用的,时序图的形式有时更有用。请求一个时序,需要在Client命令行或在属性文件指定"measureenttype=timeseries"属性。默认情况下,Client会每间隔1000ms,报告一次平均延时。你可以对报告指定不同的间隔粒度,使用 timeseries.granularity属性,例如。
$ ./bin/ycsb run basic -P workloads/workloada -P large.dat -s -threads 10 -target 100 -p \measurementtype=timeseries -p timeseries.granularity=2000 > transactions.dat
将会报告一个时序,间隔2000ms读一次,结果将是。
[OVERALL],RunTime(ms), 10077 [OVERALL],Throughput(ops/sec), 9923.58836955443 [UPDATE], Operations, 50396 [UPDATE], AverageLatency(ms), 0.04339630129375347 [UPDATE], MinLatency(ms), 0 [UPDATE], MaxLatency(ms), 338 [UPDATE], Return=0, 50396 [UPDATE], 0, 0.10264765784114054 [UPDATE], 2000, 0.026989343690867442 [UPDATE], 4000, 0.0352882703777336 [UPDATE], 6000, 0.004238958990536277 [UPDATE], 8000, 0.052813085033008175 [UPDATE], 10000, 0.0 [READ], Operations, 49604 [READ], AverageLatency(ms), 0.038242883638416256 [READ], MinLatency(ms), 0 [READ], MaxLatency(ms), 230 [READ], Return=0, 49604 [READ], 0, 0.08997245741099663 [READ], 2000, 0.02207505518763797 [READ], 4000, 0.03188493260913297 [READ], 6000, 0.004869141813755326 [READ], 8000, 0.04355329949238579 [READ], 10000, 0.005405405405405406
这个输出分开显示了update和read操作的时间序列,每2000ms的数据。数据报告的时点是仅包括前一个2000ms的均值。(这个例子,我们做了100,000次操作,目标是每秒10,000次操作)。一个关于延时度量的关注点:Client度量,特定操作对数据库的端到端的执行延时。那样,它在调用DB接口层class适当方法前会启动启动一个时钟,方法返回时会停止时钟。延时包括:执行包括接口层,到数据库服务器的网络延迟,数据库的执行时间。不包括用于控制吞吐量的延迟。就是说,如果你指定目标是每秒10次操作(单线程)Client会在每100ms仅执行1次操作。如果操作耗费了12ms,Client会在下一次操作前额外等待88ms。然而,报告延时不会包括这个等待时间,报告会显示延迟是12ms而不是100.
英文原文:YCSB