mace 记录 -- 小小的优化
mace 的定位是:简单高效支持事务的KV。这里的高效指的就是读性能和写性能,读性能对比的是基于B+树的其他KV实现(如: LeanStore),写性能对比的是基于LSM树的其他KV实现(如: RocksDB)
读优化
目前所做的优化仅有一个:在 bw tree 分裂的 parent update 完成后立即对 parent 进行 consolidate。这样做有更好的空间局部性,避免遍历 delta chain。这个的效果还是不错的,在 10 万个KV的测试中(后续简称:测试),查询效率提升 10%~20%
后续的计划是:在 delta/base page 中增加 hints,类似 bloom filter,作用是减少 &[u8]
之间的比较,同时快速在 base page 中定位到 key 的范围,减少对 &[u8]
的二分比较
写优化
在 mace 中有两种写入:wal 和 data。对性能影响最大的是 wal,因为在事务提交前必须保证 wal 成功刷盘,而 mace 目前将 wal 刷盘放在了 group commit
线程中,因此工作线程需要等待 group commit
线程通知它可以结束等待,实现的原理来自 rethinkingLogging
在测试中发现,如果不等待wal刷盘,那么写耗时可以缩短 100ms,而 commit 做的无非就是:
- worker 将 commit 记录写入buffer中
- worker 等待通知
- group commit 线程拿到 buffer
- group commit 线程写入 wal
- group commit 检查能否 commit,如果可以,则通知 worker
也就是说等待 wal 刷盘的耗时绝大部分都来自 group commit
线程对 commit 记录的处理,极少量的耗时来自 worker 的自旋等待。针对这个现象,目前做了一个优化:减少无效检查
group commit
中有一个信号量在等待 worker 的通知,每当 worker 记录了 wal 记录时通知 group commit
开始工作,这样的好处是没有任务时 group commit
不必一直空转。在 worker 等待 commit 的自旋中也会不停的通知 group commit
检查是否可以提交。有多少个 worker 那么 group commit
就会收集多少个 worker 的记录,这是必须的,而对于检查来说,并不是所有的 worker 在每次检查时都收集到了 commit 记录,因此可以只检查哪些需要检查的 worker。具体做法是使用额外的变量记录需要检查的 worker,只要这些 worker 还没有完成提交,那么下一次就不必等待 worker 触发 group commit
检查。结果就是写效率提升了 10%~20%
后续的计划是:去掉 group commit,因为它不够 scalable
测试环境:CPU是AMD R5 3600,文件系统是致钛 TiPlus 5000 上的 XFS
mace 优化前 (master)
& abby @ chaos in /home/workspace/gits/github/mace (master)
λ cargo test --test bench --release -- --nocapture
Compiling io v0.0.1 (/home/workspace/gits/github/mace/deps/io)
Compiling logger v0.1.0 (/home/workspace/gits/github/mace/deps/logger)
Compiling mace v0.0.4 (/home/workspace/gits/github/mace)
Finished `release` profile [optimized] target(s) in 9.08s
Running tests/bench.rs (target/release/deps/bench-1dc9997a5bc34690)
running 1 test
db_root "/home/abby/mace_tmp_904887"
put 531ms get 35ms
test bench ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.80s
优化后
& abby @ chaos in /home/workspace/gits/github/mace (task-54)
λ cargo test --test bench --release -- --nocapture
Compiling io v0.0.1 (/home/workspace/gits/github/mace/deps/io)
Compiling logger v0.1.0 (/home/workspace/gits/github/mace/deps/logger)
Compiling mace v0.0.4 (/home/workspace/gits/github/mace)
Finished `release` profile [optimized] target(s) in 8.94s
Running tests/bench.rs (target/release/deps/bench-1dc9997a5bc34690)
running 1 test
db_root "/home/abby/mace_tmp_2639930663942"
put 419ms
hot get 30ms
cold get 44ms
test bench ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.75s
leanstore,这里是 10 万个key的测试,由于 leanstore 是C/S架构的,每个操作都需要向线程发送操作,然后等待完成,因此这里结果并不理想,后续可能会修改下它的实现,直接在本线程执行操作,看看真实的样子是啥
& abby @ chaos in /home/workspace/db/leanstore-zj/examples/cpp/build (main *%)
λ ./BasicKvExample
Clean store dir: /home/abby/leanstore_bench
put 4270ms
hot get 1045ms
cold get 1033ms
rocksdb,这里使用的是 9.11.2 版本,没有启用 lto 。有 lto 加持的 rocksdb 的测试结果要比下面的糟糕得多(这就很奇怪🤔)
& abby @ chaos in ~
λ gg -static rocksdb_bench.cc -lrocksdb -O3
& abby @ chaos in ~
λ ./a.out
db_root /home/abby/rocksdb_tmp
put 599ms
hot get 516ms
cold get 592ms
一下是测试相关的源码