leveldb, Rocksdb 性能实验 (YCSB)

1、YCSB Leveldb 测试

由于 leveldb 不提供 java 版本的接口,并且没有合适开源的 jni 转换代码,YCSB 无法直接使用

两种测试方式:

  • 服务端中间件

leveldb 本身没有服务端,没有 restful 接口,需要通过开源中间件进行交互:

simplehttp 和 simpleleveldb

目标数据库(比如 leveldb)需要作为服务端运行起来,并提供数据库操作的 Restful API 接口供调用:

1
2
3
http://localhost:8080/put    
http://localhost:8080/get
http://localhost:8080/del
  • 基于 C++ 重写的 YCSB(开源的)

膜拜大佬ing

1
2
https://github.com/basicthinker/YCSB-C
https://github.com/ls4154/YCSB-cpp
2、Rocksdb YCSB 测试原理

rocksdb 专门为 java 写了一版 jni 的转换方法,直接将 rocksdb 编译成 jni 并用 jar 打包,放到 ycsb lib 下进行调用即可

3、基于服务端中间件的 leveldb 测试步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# get simplehttp
git clone https://github.com/bitly/simplehttp.git

# get leveldb, install .a and .h to /usr/local
git clone --recurse-submodules https://github.com/google/leveldb.git
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build .
sudo make install

# install dependency
sudo apt install libjson-c-dev
sudo apt install libsnappy-dev

打开 simplehttp/simpleleveldb/str_list_set.c修改
#include <json/json.h> 为 #include <json-c/json.h>
打开 simplehttp/simpleleveldb/simpleleveldb.c修改
#include <json/json.h> 为 #include <json-c/json.h>

simpleleveldb 的 Makefile
-ljson应改为-ljson-c

simpleHttp 库制定 libevent1.4 作为依赖,去官网下载

1
2
3
4
5
6
7
8
tar -zxvf libevent-release-1.4.15-stable.tar
sudo apt-get install libtool
cd libevent-release-1.4.15-stable
./autogen.sh
./configure
make -j$(nproc)
sudo make install
sudo ln -s /usr/local/lib/libevent-1.4.so.2 /usr/lib/libevent-1.4.so.2

可能的错误:

1
2
3
4
5
6
7
No Python installed
... ...
regress.c:65:10: fatal error: regress.gen.h: No such file or directory

# 解决方案
vim ./event_rpcgen.py
# 首行 python 改为 python2

安装依赖库:simplehttp,simpleleveldb

1
2
3
4
5
6
7
cd simplehttp
env LIBLEVELDB=/usr/local make
sudo make install

cd simpleleveldb
env LIBLEVELDB=/usr/local make
sudo make install

可能的错误:

1
2
3
4
cc -I. -I../simplehttp/.. -I/usr/local/include -I/usr/local/include -Wall -g -O2  -o simpleleveldb simpleleveldb.c str_list_set.c -L. -L../simplehttp -L/usr/local/lib -L/usr/local/lib -L/usr/local/lib -levent -ljson-c -lsimplehttp -lleveldb -lm -lstdc++ -lsnappy -lpthread
/usr/bin/ld: /usr/local/lib/libsimplehttp.a(request.o):/home/zhdi/simplehttp/simplehttp/request.h:12: multiple definition of `simplehttp_reqs'; /usr/local/lib/libsimplehttp.a(simplehttp.o):/home/zhdi/simplehttp/simplehttp/request.h:12: first defined here
collect2: error: ld returned 1 exit status
make: *** [Makefile:26: simpleleveldb] Error 1

原因:

链接多次导致,试着把多声明的变成 extern 也许可以解决

本质原因还是 libsimplehttp.a 静态库有问题,实在不行找我,我这有编译没问题的静态库

服务端启动 leveldb
1
simpleleveldb --address=localhost --port=8080 --db-file=./leveldb_file
检查是否成功

方案1:

打开浏览器,地址栏输入
http://localhost:8080/put?key=name&value=Niko

image-20240523195216109

方案2:

1
2
3
curl -X GET "http://localhost:8080/put?key=name&value=Niko"

# return: json status:OK
用 ycsb-leveldb.git 测试 leveldb(已放弃)

安装 YCSB:(有专门针对 leveldb 的)

1
2
3
4
5
6
7
8
9
10
11
git clone https://github.com/jtsui/ycsb-leveldb.git
cd ycsb-leveldb
sudo apt install maven
mvn -pl com.yahoo.ycsb:leveldb-binding -am clean package

# 配置 JAVA_HOME ~/.bashrc or ~/.zshrc
export JAVA_HOME=/opt/jdk1.8.0_271
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
source ~/.bashrc
ycsb 负载

比如:random key-value pairs (8 B key, 1024 B value) are inserted repeatedly on a 90 GB dataset.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# vim workload/workload90GB
recordcount=96774193
operationcount=10000
workload=com.yahoo.ycsb.workloads.CoreWorkload

readallfields=true

readproportion=0
updateproportion=0
insertproportion=1
scanproportion=0

requestdistribution=zipfian
fieldcount=1
fieldlength=1024
fieldlengthdistribution=constant

原始数据导入:

1
2
cd ycsb_leveldb
./ycsb load leveldb -P workloads/workload90GB -s -p threadcount=16

数据运行:

1
./ycsb run leveldb -P workloads/workload90GB -s -p threadcount=16
4、CPU利用率分析
1
2
3
4
5
6
sudo apt-get install sysstat
mpstat -P ALL 1 # 每秒更新一次每个CPU核心的利用率

pidstat -p `pidof java` -u 1 500 # 每秒输出一次 CPU 使用统计,共输出 500 次
pidstat -p `pidof java` -r 1 500 # 每秒输出一次内存使用统计,共输出 500 次
pidstat -p `pidof java` -d 1 500 # 每秒输出一次 I/O 活动统计,共输出 500 次

测试 redis 性能

1
2
3
4
5
6
7
git clone https://github.com/redis/redis.git
make
./src/redis-server

cd ycsb
./bin/ycsb load redis -s -P workloads/workload90GB -p "redis.host=127.0.0.1" -p "redis.port=6379"
./bin/ycsb run redis -s -P workloads/workload90GB -p "redis.host=127.0.0.1" -p "redis.port=6379"

测试 rocksdb 性能

1
2
./bin/ycsb load rocksdb -s -P workloads/workload90GB -p rocksdb.dir=/tmp/ycsb-rocksdb-data
./bin/ycsb run rocksdb -s -P workloads/workload90GB -p rocksdb.dir=/tmp/ycsb-rocksdb-data

修改 rocksdb 源码后如何进行测试

以编译 rocksdb v7.6.0 为例

1
2
3
git clone https://github.com/facebook/rocksdb.git
cd rocksdb
git checkout v7.6.0

更改makefile,压缩方式选 lz4 和 zstd 就行,其他三种特别是snappy对linux兼容不如mac

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 1.只选后两种压缩方式
ifneq ($(ROCKSDB_JAVA_NO_COMPRESSION), 1)
JAVA_COMPRESSIONS = liblz4.a libzstd.a
endif

JAVA_STATIC_FLAGS = -DLZ4 -DZSTD
JAVA_STATIC_INCLUDES = -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib -I./zstd-$(ZSTD_VER)/lib/dictBuilder

# 2.关掉 Werror
# WARNING_FLAGS += -Werror

# 3.兼容 linux,没有 mac gcc 的-arch选项
ifeq ($(JAVA_HOME),)
$(error JAVA_HOME is not set)
endif
$(MAKE) clean-ext-libraries-bin
$(MAKE) clean-rocks
# ARCHFLAG="-arch $*" $(MAKE) rocksdbjavastatic_deps
ARCHFLAG= $(MAKE) rocksdbjavastatic_deps
# ARCHFLAG="-arch $*" $(MAKE) rocksdbjavastatic_libobjects
ARCHFLAG= $(MAKE) rocksdbjavastatic_libobjects
# ARCHFLAG="-arch $*" ROCKSDBJNILIB="librocksdbjni-osx-$*.jnilib" $(MAKE) rocksdbjavastatic_javalib
ARCHFLAG= ROCKSDBJNILIB="librocksdbjni-osx-$*.jnilib" $(MAKE) rocksdbjavastatic_javalib

# 4.注释掉跨平台编译项
rocksdbjavastaticrelease: rocksdbjavastaticosx rocksdbjava_javadocs_jar rocksdbjava_sources_jar
# cd java/crossbuild && (vagrant destroy -f || true) && vagrant up linux32 && vagrant halt linux32 && vagrant up linux64 && vagrant halt linux64 && vagrant up linux64-musl && vagrant halt linux64-musl
cd java; $(JAR_CMD) -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md
# cd java/target; $(JAR_CMD) -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib
cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so
cd java/target/classes; $(JAR_CMD) -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class
openssl sha1 java/target/$(ROCKSDB_JAR_ALL) | sed 's/.*= \([0-9a-f]*\)/\1/' > java/target/$(ROCKSDB_JAR_ALL).sha1

编译 jni(java native interface)

1
2
3
4
5
6
7
8
# This will generate rocksdbjni.jar and librocksdbjni.so
make -j$(nproc) rocksdbjava

# 检查
rocksdb/java/target/rocksdbjni-7.6.0-linux64.jar

# rocksdbjni.jar: 提供rocksdb java接口
# librocksdbjni.so: 提供C++的rocksdb库,并转换成java native接口供jar包调用

方法一:基于打包好的 ycsb-0.17.0.tar.gz

小缺点:无法自己修改 ycsb 源码

替换ycsb下的 rocksdb jni jar 包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
curl -O --location https://github.com/brianfrankcooper/YCSB/releases/download/0.17.0/ycsb-0.17.0.tar.gz
tar xfvz ycsb-0.17.0.tar.gz
cd ycsb-0.17.0

# 编译 rocksdb-binding java项目
mvn -pl site.ycsb:rocksdb-binding -am clean package

# 若要运行修改前的rocksdb版本
./bin/ycsb load rocksdb -s -P workloads/workload10GB -p rocksdb.dir=/tmp/ycsb-rocksdb-data
# 可以看到第一行:rocksdb的版本 5.11.3
vim /tmp/ycsb-rocksdb-data/LOG

# 替换jar包
cp rocksdb/java/target/rocksdbjni-7.6.0-linux64.jar ycsb-0.17.0/rocksdb-binding/lib
rm rocksdbjni-5.11.3.jar
rm -rf /tmp/ycsb-rocksdb-data

# 重新启动测试
./bin/ycsb load rocksdb -s -P workloads/workload90GB -p rocksdb.dir=/tmp/ycsb-rocksdb-data
./bin/ycsb run rocksdb -s -P workloads/workload90GB -p rocksdb.dir=/tmp/ycsb-rocksdb-data

方法二:基于 YCSB.git

可以自由修改源码:

比如:

自定义每隔1秒输出吞吐:https://github.com/yanghy233/YCSB/commit/8b9a58cdddde3773445bd41b94f312d37da2a1e0

自定义 rocksdb jni 包在本地导入

image-20240527144124076

1
2
3
git clone https://github.com/yanghy233/YCSB.git
git checkout 0.17.0-yhy
mvn -pl site.ycsb:rocksdb-binding -am clean package

若要重新编译 YCSB rocksdb client

1
2
# 不加 clean
mvn -pl site.ycsb:rocksdb-binding -am package

基于C++实现的YCSB-C,专门用于测试CruiseDB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo apt-get install libtbb-dev
git clone git@github.com:yanghy233/YCSB-C.git
cd YCSB-C

# 先到CruiseDB目录下编译
make static_lib -j$(nproc)
make shared_lib -j$(nproc)
sudo DEBUG_LEVEL=0 make uninstall
sudo DEBUG_LEVEL=0 make install

# 链接与编译
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
make
mkdir ramdisk_path rocksdb_disk_path

# 运行测试脚本
./ycsba.sh

基于c++实现的 ycsb-cpp,可以在cpp层面直接测试leveldb

1
2
3
4
5
6
7
8
9
10
git clone https://github.com/ls4154/YCSB-cpp.git

git submodule update --init
mkdir build
cd build
# leveldb
cmake -DBIND_LEVELDB=1 -DWITH_SNAPPY=1 -DWITH_LZ4=1 -DWITH_ZSTD=1 ..
# rocksdb
cmake -DBIND_ROCKSDB=1 -DWITH_SNAPPY=1 -DWITH_LZ4=1 -DWITH_ZSTD=1 ..
make -j$(nproc)

Load & Run

1
2
3
4
5
6
7
# leveldb
./build/ycsb -load -db leveldb -P workloads/workload10GB -P leveldb/leveldb.properties -s
taskset -c 0 ./build/ycsb -run -db leveldb -P workloads/workload10GB -P leveldb/leveldb.properties -s

# rocksdb
./build/ycsb -load -db rocksdb -P workloads/workloadb -P rocksdb/rocksdb.properties -s
./build/ycsb -run -db rocksdb -P workloads/workloadb -P rocksdb/rocksdb.properties -s

修改leveldb源码后,测试前需要更新静态库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sudo rm -rf /usr/local/include/leveldb /usr/local/lib64/cmake/leveldb /usr/local/lib64/libleveldb.a /usr/local/share/doc/leveldb \
/usr/local/lib/libleveldb.a \
/usr/local/lib/cmake/leveldb \
/usr/local/lib/cmake/leveldb/leveldbConfig.cmake \
/usr/local/lib/cmake/leveldb/leveldbConfigVersion.cmake \
/usr/local/lib/cmake/leveldb/leveldbTargets.cmake \
/usr/local/lib/cmake/leveldb/leveldbTargets-release.cmake

### 删除之后,一定要到 YCSB-cpp, 删除build,重新编译

rm -rf /tmp/ycsb-leveldb

# cd leveldb/build
cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build .
sudo make install

火焰图生成与分析

一些避坑记录:

  • leveldb 一定需要 release 版本,否则会出现两个 pid
  • perf 记录不能使用默认的 -g,要用 –call-graph dwarf,否则出现大量的 unknown 调用栈
  • 无需绑定到单一核心
  • 用 Centos8.2跑,ubuntu22有问题
1
2
3
4
5
6
7
8
9
10
11
# perf 记录运行时
sudo perf record -F 999 -p `pidof ./build/ycsb` --call-graph dwarf -o ~/Experiment_Data/perf.data

# 以文本形式展开
sudo perf script -i ~/Experiment_Data/perf.data > ~/Experiment_Data/perf.unfold

# 生成脚本文件
./FlameGraph/stackcollapse-perf.pl ~/Experiment_Data/perf.unfold > ~/Experiment_Data/perf.folded

# 生成火焰图
./FlameGraph/flamegraph.pl ~/Experiment_Data/perf.folded > ~/Experiment_Data/flame.svg

leveldb 性能实验

配置:

硬件 Model
CPU 2核 Intel Xeon Processor (Cascadelake)
Memory DDR4 16GB/ 32 GB
Storage NVMe SSD 300GB
软件 Version
OS Ubuntu 22.04 LTS
Kernel Linux 6.2
Leveldb Latest
Rocksdb v7.6.0
YCSB Benchmark 0.17.0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CPU:2核 Intel Xeon
memory:16GB
YCSB版本:https://github.com/yanghy233/YCSB-cpp

# loading 0.1GB
rm -rf /tmp/ycsb-leveldb
./build/ycsb -load -db leveldb -P workloads/workload10GB -P leveldb/leveldb.properties -s

# running 50G
# ycsb:默认-threads 1,绑定CPU核心0
taskset -c 0 ./build/ycsb -run -db leveldb -P workloads/workload10GB -P leveldb/leveldb.properties -s > ~/Experiment_Data/leveldb_ycsb.log

# 每秒记录一次CPU利用率
pidstat -p `pidof ./build/ycsb` -u 1 1400 > ~/Experiment_Data/leveldb_cpu.log

实验结果:

100% Insert 起始数据量 10GB

内存32G:吞吐量

image-20240910151953030

我们的方案:

image-20240910163857662

CPU 利用率

image-20240603195057725

0830:

ours:

image-20240830170549665

origin:

image-20240830170755059

60% Insert, 40% Read 起始数据量 10GB

1
2
3
4
5
6
7
8
9
10
11
cd YCSB-cpp

rm -rf /tmp/ycsb-leveldb
./build/ycsb -load -db leveldb -P workloads/workload10GB -P leveldb/leveldb.properties -s

# ops
taskset -c 0 ./build/ycsb -run -db leveldb -P workloads/workload10GB -P leveldb/leveldb.properties -s > ~/Experiment_Data/leveldb_ycsb_6i4r.log

# cpu
pidstat -p `pidof ./build/ycsb` -u 1 1400 > ~/Experiment_Data/leveldb_cpu_6i4r.log

image-20240924214842072

workloads/workload10GB

0~300:6,121,420

300~600:4,698,732

600~900:3,784,508

900~1200:2,980,646

吞吐量

image-20240603182036578

CPU 利用率

image-20240603182047349

Rocksdb v7.6.0 性能实验

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CPU:2核 Intel Xeon
memory:16GB
YCSB版本:0.17.0
./ycsb_0.17.0_yhy

# loading 10G
rm -rf /tmp/ycsb-rocksdb-data
./bin/ycsb load rocksdb -s -P workloads/workload10GB -p rocksdb.dir=/tmp/ycsb-rocksdb-data

# running 50G
# ycsb:默认-threads 1,绑定CPU核心0
taskset -c 0 ./bin/ycsb run rocksdb -s -P workloads/workload10GB -p rocksdb.dir=/tmp/ycsb-rocksdb-data -p measurementtype=timeseries > ~/Experiment_Data/rocksdb_ycsb.log

# 每秒记录一次CPU利用率
pidstat -p `pidof java` -u 1 1400 > ~/Experiment_Data/rocksdb_cpu.log



# 统一用 YCSB-cpp
rm -rf /tmp/ycsb-rocksdb
./build/ycsb -load -db rocksdb -P workloads/workload10GB -P rocksdb/rocksdb.properties -s

# running
./build/ycsb -run -db rocksdb -P workloads/workload10GB -P rocksdb/rocksdb.properties -s > ~/Experiment_Data/rocksdb_ycsb.log

100% Insert

起始10G,insert 50G

实验结果:

吞吐量

image-20240910143556561

前 800s 吞吐:

image-20240914103732318

image-20240914100232317

CruiseDB 性能测试

in YCSB-C,32c 32G

YCSB threadcount=100

默认 value = 1024B,

短时间内相对平稳:

  • 按照论文图片,y轴间隔3万

(cruisedb_1024B.txt)

image-20240927095140967

  • 稳定性:用方差/标准差来衡量稳定性的好坏

image-20240927095041351

Rocksdb + Auto-tuned 测试:(能够提高总吞吐)

  • 通过水位线窗口动态调控后台 flush / compaction IO数量,使后台 IO 数量稳定在一定的范围内
  • 但无法解决前台 write stall 的问题

image-20240926144635322

  • CruiseDB 基础上,打开 Auto-tuned 机制:

image-20240926154204091

总吞吐量(平均吞吐)的对比、稳定性(标准差)的对比

  • 100%write
    • CruiseDB

    image-20240927095041351

    • RocksDB

    rocksdb_i1r0.txt

image-20240927100826000

  • 50%read,50%write
    • CruiseDB

    image-20240927094429819

    • RocksDB

image-20240927094721128

理想情况下的最大吞吐

  • 针对大量写负载

  • 理想情况:不考虑读,LSM-Tree 在磁盘只有 L0 层,并且无限大,只有 Flush IO,没有 Compaction IO

  • 得到了理想情况下,充分利用IO的吞吐极限

image-20240927210848083

CruiseDB 关于 L0 层是否移除

  • 论文里提出为了能够减少 IO 次数,移除 LSM-Tree 的 L0 层,即不再有 flush 操作

  • 进行实验

CruiseDB 最大层数与 Rocksdb 相同,都是最高在第 6 层

在 YCSB 负载执行期间,异步执行一个后台线程,每隔 5 秒输出各层的文件数量

分析作者有可能的实现:

  • L0 文件数量限制为0,那么每次打印 L0 的数量都应该是 0;
  • 逐层向上覆盖,原来的 L1 层是现在的 L0 层,则不应该出现 flush 操作;

image-20240927200727554

image-20240927200839543

其他
  • kv 为 middle-size = 128B,吞吐量如下:

image-20240926172615807

  • kv 为 small-size = 32B,吞吐量如下:

image-20240926174635624

CruiseDB冷启动问题发现与解答

冷启动原因:
  • load 和 run 阶段分开,run才开始流量控制,导致开始时内存容量是满的
真实的 CruiseDB 负载吞吐(去除论文作者在 YCSB 刻意 sleep 的时间)
  • 有 130s 的达到稳态的时间间隔(论文的图去掉了达到稳态的 130s 间隔,同时论文作者的YCSB刻意增加了130s的sleep)
  • 下面实验图经过多次确认!

image-20240926201251191

  • 50%read,50%write

花了 65s 达到稳态

image-20240926202614471

  • 100%read

花了 40s 达到稳态

image-20240926204039528

Reference

https://honor-ry.github.io/posts/de979122.html

libevent install:https://blog.csdn.net/u012342808/article/details/121482259


leveldb, Rocksdb 性能实验 (YCSB)
https://yanghy233.github.io/2024/08/08/leveldb-test-ycsb/
作者
yanghy
发布于
2024年8月8日
许可协议