BoltDB 测试报告

117 阅读11分钟

压测对象介绍

boltdb 是一款基于 go 开发的 k/v 数据库。其设计源于 LMDB(Lightning Memory-Mapped Database),采用 B+ 树组织数据结构,并将数据持久化到单文件中。boltdb 支持事务

压测背景

本文是针对 boltdb 的一次压测记录,目的是为了测试其在不同场景下随机读写的 cpu内存消耗情况。

压测计划

压测环境

  • 机器:MacBook Pro (13-inch, M1, 2020)
  • 芯片:Apple M1
  • 内存:8 GB
  • disk:diskutil list
/dev/disk0 (internal):
#:TYPENAMESIZEIDENTIFIER
0:GUID_partition_scheme251.0 GBdisk0
1:Apple_APFS_ISC524.3 MBdisk0s1
2:Apple_APFSContainer disk3245.1 GBdisk0s2
3:Apple_APFS_Recovery5.4 GBdisk0s3
/dev/disk3 (synthesized):
#:TYPENAMESIZEIDENTIFIER
0:APFS Container Scheme-+245.1 GBdisk3
Physical Store disk0s2
1:APFS VolumeMacintosh HD15.3 GBdisk3s1
2:APFS Snapshotcom.apple.os.update-...15.3 GBdisk3s1s1
3:APFS VolumePreboot184.9 MBdisk3s2
4:APFS VolumeRecovery1.0 GBdisk3s3
5:APFS VolumeData54.8 GBdisk3s5
6:APFS VolumeVM6.4 GBdisk3s6

基础参数设置

bbolt.Options{
	NoSync  :   true,						// 设置异步写入
	Timeout :   1 * time.Second,	  // 设置文件锁超时时间
}


变量定义

压测计划将有以下几个变量:

  1. 读写请求 value 大小;
  2. mmap 初始大小;
  3. bucket 数量;
  4. 是否有回滚行为;
  5. 是否有数据竞争;
  6. 只读,只写,混合读写。

压测计划采取控制变量的方法,由行为出发,结合预期的变量得到测试结果。

eg:

行为编号变量组编号cpuheap单次耗时单批次耗时

变量组

行为

编号读写请求频率(w次/s)
00
15
210
320
450

变量组

编号具体变量编号具体变量
0-0-0mmap: 0 ,value: 16 * 4 字节 ,bucket: 10-4-0mmap: 0 ,value: 256 * 4 字节 ,bucket: 1
0-0-1mmap: 0 ,value: 16 * 4 字节 ,bucket: 20-4-1mmap: 0 ,value: 256 * 4 字节 ,bucket: 2
0-0-2mmap: 0 ,value: 16 * 4 字节 ,bucket: 50-4-2mmap: 0 ,value: 256 * 4 字节 ,bucket: 5
0-0-3mmap: 0 ,value: 16 * 4 字节 ,bucket: 100-4-3mmap: 0 ,value: 256 * 4 字节 ,bucket: 10
0-1-0mmap: 0 ,value: 32 * 4 字节 ,bucket: 10-5-0mmap: 0 ,value: 512 * 4 字节 ,bucket: 1
0-1-1mmap: 0 ,value: 32 * 4 字节 ,bucket: 20-5-1mmap: 0 , value: 512 * 4 字节 ,bucket: 2
0-1-2mmap: 0 ,value: 32 * 4 字节 ,bucket: 50-5-2mmap: 0 ,value: 512 * 4 字节 ,bucket: 5
0-1-3mmap: 0 ,value: 32 * 4 字节 ,bucket: 100-5-3mmap: 0 ,value: 512 * 4 字节 ,bucket: 10
0-2-0mmap: 0 ,value: 64 * 4 字节 ,bucket: 10-6-0mmap: 0 ,value: 1024 * 4 字节 ,bucket: 1
0-2-1mmap: 0 ,value: 64 * 4 字节 ,bucket: 20-6-1mmap: 0 ,value: 1024 * 4 字节 ,bucket: 2
0-2-2mmap: 0 ,value: 64 * 4 字节 ,bucket: 50-6-2mmap: 0 ,value: 1024 * 4 字节 ,bucket: 5
0-2-3mmap: 0 ,value: 64 * 4 字节 ,bucket: 100-6-3mmap: 0 ,value: 1024 * 4 字节 ,bucket: 10
0-3-0mmap: 0 ,value: 128 * 4 字节 ,bucket: 10-7-0mmap: 0 ,value: 2048 * 4 字节 ,bucket: 1
0-3-1mmap: 0 ,value: 128 * 4 字节 ,bucket: 20-7-1mmap: 0 ,value: 2048 * 4 字节 ,bucket: 2
0-3-2mmap: 0 ,value: 128 * 4 字节 ,bucket: 50-7-2mmap: 0 ,value: 2048 * 4 字节 ,bucket: 5
0-3-3mmap: 0 ,value: 128 * 4 字节 ,bucket: 100-7-3mmap: 0 ,value: 2048 * 4 字节 ,bucket: 10
1-0-0mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 11-4-0mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 1
1-0-1mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 21-4-1mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 2
1-0-2mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 51-4-2mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 5
1-0-3mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 101-4-3mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 10
1-1-0mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 11-5-0mmap: 1024 * 1024 * 10 ,value: 512 * 4 字节 ,bucket: 1
1-1-1mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 21-5-1mmap: 1024 * 1024 * 10 , value: 512 * 4 字节 ,bucket: 2
1-1-2mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 51-5-2mmap: 1024 * 1024 * 10 ,value: 512 * 4 字节 ,bucket: 5
1-1-3mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 101-5-3mmap: 1024 * 1024 * 10 ,value: 512 * 4 字节 ,bucket: 10
1-2-0mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 11-6-0mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 1
1-2-1mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 21-6-1mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 2
1-2-2mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 51-6-2mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 5
1-2-3mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 101-6-3mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 10
1-3-0mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 11-7-0mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 1
1-3-1mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 21-7-1mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 2
1-3-2mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 51-7-2mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 5
1-3-3mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 101-7-3mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 10
2-0-0mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 12-4-0mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 1
2-0-1mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 22-4-1mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 2
2-0-2mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 52-4-2mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 5
2-0-3mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 102-4-3mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 10
2-1-0mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 12-5-0mmap: 1024 * 1024 * 50 ,value: 512 * 4 字节 ,bucket: 1
2-1-1mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 22-5-1mmap: 1024 * 1024 * 50 , value: 512 * 4 字节 ,bucket: 2
2-1-2mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 52-5-2mmap: 1024 * 1024 * 50 ,value: 512 * 4 字节 ,bucket: 5
2-1-3mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 102-5-3mmap: 1024 * 1024 * 50 ,value: 512 * 4 字节 ,bucket: 10
2-2-0mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 12-6-0mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 1
2-2-1mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 22-6-1mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 2
2-2-2mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 52-6-2mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 5
2-2-3mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 102-6-3mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 10
2-3-0mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 12-7-0mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 1
2-3-1mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 22-7-1mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 2
2-3-2mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 52-7-2mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 5
2-3-3mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 102-7-3mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 10

压测结果

根据比较敏感的变量进行测试我们分为:value 长度对内存影响的相关压测,bucket 数量影响请求时间的压测

以下是 value 长度对内存和请求时间的影响测试,其中 heap 这项由 bblot 的 commit() 和 put() 两项 * 内存使用使用情况确定

value 长度影响测试

行为编号变量组编号cpuheap单批次耗时
00-0-02.4%13MB * (51.43%+5.71%)11ms
10-0-04.6%31MB * (58.12%+4.27%)33ms
20-0-09.0%52MB * (59.49%+5.06%)70ms
30-0-018.0%95MB * (59.64%+6.58%)135ms
40-0-042.0%230MB * (61.87%+6.66%)350ms
00-1-02.4%15MB * (40.54%+5.41%)11ms
10-1-04.6%44MB * (63.85%+7.69%)40ms
20-1-09.0%76MB * (62.03%+6.81%)70ms
30-1-018.0%144MB * (55.53%+12.42%)140ms
40-1-043.0%338MB * (62.38%+11.33%)355ms
00-1-02.4%21MB * (57.63%+10.25%)11ms
10-2-04.6%70MB * (62.86%+12.43%)41ms
20-2-09.0%141MB * (60.49%+21.81%)85ms
30-2-018.0%270MB * (61.63%+20.77%)159ms
40-2-043.0%670MB * (62.38%+21.23%)430ms
00-3-02.8%33MB * (69.49%+13.56%)15ms
10-3-06.3%130MB * (61.88%+25.57%)52ms
20-3-010.3%258MB * (55.81%+31.23%)98ms
30-3-020.0%499MB * (53.08%+34.94%)200ms
40-3-070.0%1261MB * (51.92%+39.75%)1140ms
00-4-03.2%60MB * (54.09%+24.05%)18ms
10-4-09.3%269MB * (44.49%+41.34%)80ms
20-4-019.%538MB * (42.02%+45.38%)148ms
30-4-035.0%1023MB * (44.11%+47.93%)400ms
40-4-0147.1%2686M+ * ( * % + * %)* ms
00-5-04.0%129MB * (39.77%+46.59%)30ms
10-5-014.0%583MB * (30.11%+60.22%)130ms
20-5-037.5%1272MB * (24.57%+64.14%)310ms
30-5-0170%** ms
40-5-0*** ms

value 长度影响结论

根据测试数据我们发现在同行为的情况下,value 越大,对内存的影响是比较大的,具体我们可以根据两幅火焰图来看一下: value 324 byte 转存失败,建议直接上传图片文件 value 5124 byte 转存失败,建议直接上传图片文件

首先 put 操作中的 seek 函数的确是消耗内存比较多的行为,主要原因是在读写操作里要首先找到历史数据,其次加载 page 页数据,这一系列操作产生的大量映射关系需要消耗内存,其次 value 越大,事务 commit 行为将大量耗费内存,这里翻阅代码大概了解 首先事务将数据写进脏页,这里分配了新的 page 数组,其次进行 page 排序,最后调用 writeAt 写入进行系统调用写数据,这部分中我们改变 value 长度就变得很敏感,在不同请求次数的条件下,如果不进行及时的 commit,脏页元素会成倍上涨,占用内存资源!

bucket 数量影响测试

有了之前的测试数据,我们从影响比较大的测试数据入手,这里我们选取行为 3 变量组 0-2-0

行为编号变量组编号cpuheap单批次耗时
30-2-018.0%270MB * (61.63%+20.77%)159ms
30-2-317.9%277MB * (60.13%+22.25%)170ms

转存失败,建议直接上传图片文件 可以基本得到结论,分桶数对请求时间和内存没有明显改善,根本原因是,bucket 文件在统一文件中,都是对一个文件的随机读写,所以时间上得不到较大优化

结论

1、目前初步结论 bbolt 在小 value 写入场景较好,对于存索引一类的数据还算比较合适; 2、可以采取分库策略进一步降低写入开销,并可以将文件散列在不同盘中; 3、需要关注事务的提交时机,否则分库也不是银弹。