压测对象介绍
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): | ||||
|---|---|---|---|---|
| #: | TYPE | NAME | SIZE | IDENTIFIER |
| 0: | GUID_partition_scheme | 251.0 GB | disk0 | |
| 1: | Apple_APFS_ISC | 524.3 MB | disk0s1 | |
| 2: | Apple_APFS | Container disk3 | 245.1 GB | disk0s2 |
| 3: | Apple_APFS_Recovery | 5.4 GB | disk0s3 |
| /dev/disk3 (synthesized): | ||||
|---|---|---|---|---|
| #: | TYPE | NAME | SIZE | IDENTIFIER |
| 0: | APFS Container Scheme | - | +245.1 GB | disk3 |
| Physical Store disk0s2 | ||||
| 1: | APFS Volume | Macintosh HD | 15.3 GB | disk3s1 |
| 2: | APFS Snapshot | com.apple.os.update-... | 15.3 GB | disk3s1s1 |
| 3: | APFS Volume | Preboot | 184.9 MB | disk3s2 |
| 4: | APFS Volume | Recovery | 1.0 GB | disk3s3 |
| 5: | APFS Volume | Data | 54.8 GB | disk3s5 |
| 6: | APFS Volume | VM | 6.4 GB | disk3s6 |
基础参数设置
bbolt.Options{
NoSync : true, // 设置异步写入
Timeout : 1 * time.Second, // 设置文件锁超时时间
}
变量定义
压测计划将有以下几个变量:
- 读写请求 value 大小;
- mmap 初始大小;
- bucket 数量;
- 是否有回滚行为;
- 是否有数据竞争;
- 只读,只写,混合读写。
压测计划采取控制变量的方法,由行为出发,结合预期的变量得到测试结果。
eg:
| 行为编号 | 变量组编号 | cpu | heap | 单次耗时 | 单批次耗时 |
|---|
变量组
行为
| 编号 | 读写请求频率(w次/s) |
|---|---|
| 0 | 0 |
| 1 | 5 |
| 2 | 10 |
| 3 | 20 |
| 4 | 50 |
变量组
| 编号 | 具体变量 | 编号 | 具体变量 |
|---|---|---|---|
| 0-0-0 | mmap: 0 ,value: 16 * 4 字节 ,bucket: 1 | 0-4-0 | mmap: 0 ,value: 256 * 4 字节 ,bucket: 1 |
| 0-0-1 | mmap: 0 ,value: 16 * 4 字节 ,bucket: 2 | 0-4-1 | mmap: 0 ,value: 256 * 4 字节 ,bucket: 2 |
| 0-0-2 | mmap: 0 ,value: 16 * 4 字节 ,bucket: 5 | 0-4-2 | mmap: 0 ,value: 256 * 4 字节 ,bucket: 5 |
| 0-0-3 | mmap: 0 ,value: 16 * 4 字节 ,bucket: 10 | 0-4-3 | mmap: 0 ,value: 256 * 4 字节 ,bucket: 10 |
| 0-1-0 | mmap: 0 ,value: 32 * 4 字节 ,bucket: 1 | 0-5-0 | mmap: 0 ,value: 512 * 4 字节 ,bucket: 1 |
| 0-1-1 | mmap: 0 ,value: 32 * 4 字节 ,bucket: 2 | 0-5-1 | mmap: 0 , value: 512 * 4 字节 ,bucket: 2 |
| 0-1-2 | mmap: 0 ,value: 32 * 4 字节 ,bucket: 5 | 0-5-2 | mmap: 0 ,value: 512 * 4 字节 ,bucket: 5 |
| 0-1-3 | mmap: 0 ,value: 32 * 4 字节 ,bucket: 10 | 0-5-3 | mmap: 0 ,value: 512 * 4 字节 ,bucket: 10 |
| 0-2-0 | mmap: 0 ,value: 64 * 4 字节 ,bucket: 1 | 0-6-0 | mmap: 0 ,value: 1024 * 4 字节 ,bucket: 1 |
| 0-2-1 | mmap: 0 ,value: 64 * 4 字节 ,bucket: 2 | 0-6-1 | mmap: 0 ,value: 1024 * 4 字节 ,bucket: 2 |
| 0-2-2 | mmap: 0 ,value: 64 * 4 字节 ,bucket: 5 | 0-6-2 | mmap: 0 ,value: 1024 * 4 字节 ,bucket: 5 |
| 0-2-3 | mmap: 0 ,value: 64 * 4 字节 ,bucket: 10 | 0-6-3 | mmap: 0 ,value: 1024 * 4 字节 ,bucket: 10 |
| 0-3-0 | mmap: 0 ,value: 128 * 4 字节 ,bucket: 1 | 0-7-0 | mmap: 0 ,value: 2048 * 4 字节 ,bucket: 1 |
| 0-3-1 | mmap: 0 ,value: 128 * 4 字节 ,bucket: 2 | 0-7-1 | mmap: 0 ,value: 2048 * 4 字节 ,bucket: 2 |
| 0-3-2 | mmap: 0 ,value: 128 * 4 字节 ,bucket: 5 | 0-7-2 | mmap: 0 ,value: 2048 * 4 字节 ,bucket: 5 |
| 0-3-3 | mmap: 0 ,value: 128 * 4 字节 ,bucket: 10 | 0-7-3 | mmap: 0 ,value: 2048 * 4 字节 ,bucket: 10 |
| 1-0-0 | mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 1 | 1-4-0 | mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 1 |
| 1-0-1 | mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 2 | 1-4-1 | mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 2 |
| 1-0-2 | mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 5 | 1-4-2 | mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 5 |
| 1-0-3 | mmap: 1024 * 1024 * 10 ,value: 16 * 4 字节 ,bucket: 10 | 1-4-3 | mmap: 1024 * 1024 * 10 ,value: 256 * 4 字节 ,bucket: 10 |
| 1-1-0 | mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 1 | 1-5-0 | mmap: 1024 * 1024 * 10 ,value: 512 * 4 字节 ,bucket: 1 |
| 1-1-1 | mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 2 | 1-5-1 | mmap: 1024 * 1024 * 10 , value: 512 * 4 字节 ,bucket: 2 |
| 1-1-2 | mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 5 | 1-5-2 | mmap: 1024 * 1024 * 10 ,value: 512 * 4 字节 ,bucket: 5 |
| 1-1-3 | mmap: 1024 * 1024 * 10 ,value: 32 * 4 字节 ,bucket: 10 | 1-5-3 | mmap: 1024 * 1024 * 10 ,value: 512 * 4 字节 ,bucket: 10 |
| 1-2-0 | mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 1 | 1-6-0 | mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 1 |
| 1-2-1 | mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 2 | 1-6-1 | mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 2 |
| 1-2-2 | mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 5 | 1-6-2 | mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 5 |
| 1-2-3 | mmap: 1024 * 1024 * 10 ,value: 64 * 4 字节 ,bucket: 10 | 1-6-3 | mmap: 1024 * 1024 * 10 ,value: 1024 * 4 字节 ,bucket: 10 |
| 1-3-0 | mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 1 | 1-7-0 | mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 1 |
| 1-3-1 | mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 2 | 1-7-1 | mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 2 |
| 1-3-2 | mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 5 | 1-7-2 | mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 5 |
| 1-3-3 | mmap: 1024 * 1024 * 10 ,value: 128 * 4 字节 ,bucket: 10 | 1-7-3 | mmap: 1024 * 1024 * 10 ,value: 2048 * 4 字节 ,bucket: 10 |
| 2-0-0 | mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 1 | 2-4-0 | mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 1 |
| 2-0-1 | mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 2 | 2-4-1 | mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 2 |
| 2-0-2 | mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 5 | 2-4-2 | mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 5 |
| 2-0-3 | mmap: 1024 * 1024 * 50 ,value: 16 * 4 字节 ,bucket: 10 | 2-4-3 | mmap: 1024 * 1024 * 50 ,value: 256 * 4 字节 ,bucket: 10 |
| 2-1-0 | mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 1 | 2-5-0 | mmap: 1024 * 1024 * 50 ,value: 512 * 4 字节 ,bucket: 1 |
| 2-1-1 | mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 2 | 2-5-1 | mmap: 1024 * 1024 * 50 , value: 512 * 4 字节 ,bucket: 2 |
| 2-1-2 | mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 5 | 2-5-2 | mmap: 1024 * 1024 * 50 ,value: 512 * 4 字节 ,bucket: 5 |
| 2-1-3 | mmap: 1024 * 1024 * 50 ,value: 32 * 4 字节 ,bucket: 10 | 2-5-3 | mmap: 1024 * 1024 * 50 ,value: 512 * 4 字节 ,bucket: 10 |
| 2-2-0 | mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 1 | 2-6-0 | mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 1 |
| 2-2-1 | mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 2 | 2-6-1 | mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 2 |
| 2-2-2 | mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 5 | 2-6-2 | mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 5 |
| 2-2-3 | mmap: 1024 * 1024 * 50 ,value: 64 * 4 字节 ,bucket: 10 | 2-6-3 | mmap: 1024 * 1024 * 50 ,value: 1024 * 4 字节 ,bucket: 10 |
| 2-3-0 | mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 1 | 2-7-0 | mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 1 |
| 2-3-1 | mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 2 | 2-7-1 | mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 2 |
| 2-3-2 | mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 5 | 2-7-2 | mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 5 |
| 2-3-3 | mmap: 1024 * 1024 * 50 ,value: 128 * 4 字节 ,bucket: 10 | 2-7-3 | mmap: 1024 * 1024 * 50 ,value: 2048 * 4 字节 ,bucket: 10 |
压测结果
根据比较敏感的变量进行测试我们分为:value 长度对内存影响的相关压测,bucket 数量影响请求时间的压测
以下是 value 长度对内存和请求时间的影响测试,其中 heap 这项由 bblot 的 commit() 和 put() 两项 * 内存使用使用情况确定
value 长度影响测试
| 行为编号 | 变量组编号 | cpu | heap | 单批次耗时 |
|---|---|---|---|---|
| 0 | 0-0-0 | 2.4% | 13MB * (51.43%+5.71%) | 11ms |
| 1 | 0-0-0 | 4.6% | 31MB * (58.12%+4.27%) | 33ms |
| 2 | 0-0-0 | 9.0% | 52MB * (59.49%+5.06%) | 70ms |
| 3 | 0-0-0 | 18.0% | 95MB * (59.64%+6.58%) | 135ms |
| 4 | 0-0-0 | 42.0% | 230MB * (61.87%+6.66%) | 350ms |
| 0 | 0-1-0 | 2.4% | 15MB * (40.54%+5.41%) | 11ms |
| 1 | 0-1-0 | 4.6% | 44MB * (63.85%+7.69%) | 40ms |
| 2 | 0-1-0 | 9.0% | 76MB * (62.03%+6.81%) | 70ms |
| 3 | 0-1-0 | 18.0% | 144MB * (55.53%+12.42%) | 140ms |
| 4 | 0-1-0 | 43.0% | 338MB * (62.38%+11.33%) | 355ms |
| 0 | 0-1-0 | 2.4% | 21MB * (57.63%+10.25%) | 11ms |
| 1 | 0-2-0 | 4.6% | 70MB * (62.86%+12.43%) | 41ms |
| 2 | 0-2-0 | 9.0% | 141MB * (60.49%+21.81%) | 85ms |
| 3 | 0-2-0 | 18.0% | 270MB * (61.63%+20.77%) | 159ms |
| 4 | 0-2-0 | 43.0% | 670MB * (62.38%+21.23%) | 430ms |
| 0 | 0-3-0 | 2.8% | 33MB * (69.49%+13.56%) | 15ms |
| 1 | 0-3-0 | 6.3% | 130MB * (61.88%+25.57%) | 52ms |
| 2 | 0-3-0 | 10.3% | 258MB * (55.81%+31.23%) | 98ms |
| 3 | 0-3-0 | 20.0% | 499MB * (53.08%+34.94%) | 200ms |
| 4 | 0-3-0 | 70.0% | 1261MB * (51.92%+39.75%) | 1140ms |
| 0 | 0-4-0 | 3.2% | 60MB * (54.09%+24.05%) | 18ms |
| 1 | 0-4-0 | 9.3% | 269MB * (44.49%+41.34%) | 80ms |
| 2 | 0-4-0 | 19.% | 538MB * (42.02%+45.38%) | 148ms |
| 3 | 0-4-0 | 35.0% | 1023MB * (44.11%+47.93%) | 400ms |
| 4 | 0-4-0 | 147.1% | 2686M+ * ( * % + * %) | * ms |
| 0 | 0-5-0 | 4.0% | 129MB * (39.77%+46.59%) | 30ms |
| 1 | 0-5-0 | 14.0% | 583MB * (30.11%+60.22%) | 130ms |
| 2 | 0-5-0 | 37.5% | 1272MB * (24.57%+64.14%) | 310ms |
| 3 | 0-5-0 | 170% | * | * ms |
| 4 | 0-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
| 行为编号 | 变量组编号 | cpu | heap | 单批次耗时 |
|---|---|---|---|---|
| 3 | 0-2-0 | 18.0% | 270MB * (61.63%+20.77%) | 159ms |
| 3 | 0-2-3 | 17.9% | 277MB * (60.13%+22.25%) | 170ms |
可以基本得到结论,分桶数对请求时间和内存没有明显改善,根本原因是,bucket 文件在统一文件中,都是对一个文件的随机读写,所以时间上得不到较大优化
结论
1、目前初步结论 bbolt 在小 value 写入场景较好,对于存索引一类的数据还算比较合适; 2、可以采取分库策略进一步降低写入开销,并可以将文件散列在不同盘中; 3、需要关注事务的提交时机,否则分库也不是银弹。