Show HN: Keybench – 키‑값 저장소용 스크립트·확장 가능한 성능 도구
Source: Hacker News
keybench
정렬된 키‑값 저장소를 위한 스크립트 가능하고 확장 가능한 성능 도구입니다.
워크로드는 Lua로 작성합니다. keybench는 이를 하나 이상의 스토리지 엔진에 걸쳐 실행하고, 모든 연산을 측정한 뒤 처리량과 지연 시간을 보고합니다. 동일한 스크립트가 모든 엔진에서 그대로 실행되므로, 비교 결과는 엔진 자체를 측정한 것이지 벤치마크 프레임워크를 측정한 것이 아닙니다.
현재는 POSIX 환경만 지원합니다.
What it measures
keybench는 두 가지 속도와 지연 시간 분포를 보고합니다.
- wu/s – workload units per second. 하나의 단위는
run()함수 호출 한 번을 의미합니다. 이는 스크립트가 정의한 전체 연산(예: “장바구니 보기” 혹은 “결제”)의 속도이며, 각 연산이 몇 개의 키를 다루는지는 고려하지 않습니다. - ops/s – primitive operations per second. 하나의 원시 연산은
put,get,del,range,scan호출 하나이거나mget,mput,mdel안에서 처리되는 하나의 키를 의미합니다.range나scan은 반환된 행 수와 관계없이 한 번만 카운트됩니다. 이는 실제 키 접근 횟수의 속도입니다.
모든 단위가 하나의 원시 연산일 경우 두 속도가 동일하며, 보고서에는 하나의 처리량 라인만 출력됩니다. 하나의 단위가 여러 원시 연산을 포함하는 경우(예: B개의 키를 한 번에 처리하는 배치 혹은 스캔 후 삭제하는 장바구니 결제)에는 wu/sec와 ops/sec가 별도 라인에 표시됩니다. 배치 연산의 경우 ops/sec는 정확히 B배가 되며, 이는 키당 고정 호출 비용이 더 많은 키에 걸쳐 분산되기 때문입니다.
지연 시간은 평균이 아니라 분포 형태로 기록됩니다. put, get, del, range, mget, mput, mdel 각각에 히스토그램이 존재하고, scan은 range 히스토그램에 포함됩니다. 보고서에는 p50, p99, p99.9, max 값이 각각 제공됩니다.
How it works
다섯 개의 교체 가능한 구성 요소로 이루어집니다.
- 엔진 – 동시성을 담당합니다. keybench는 워커 스레드를 생성하고, 연산 예산을 스레드에 나눠주며, 마지막에 스레드를 합칩니다. 각 워커는 자체 Lua 상태와 난수 시드를 가지고 스크립트를 실행합니다. 프레임워크는 엔진 호출 주위에 락을 잡지 않으므로, 직렬 엔진은 직렬로, 병렬 엔진은 병렬로 보고됩니다. Lua 스크립트 자체는 단일 스레드이며 락에 대해 신경 쓸 필요가 없습니다.
- 워크로드 – Lua 테이블 형태입니다. 파일은
name, (선택적인)load함수,run함수를 포함한 테이블을 반환합니다.load는 측정 전에 스토어를 초기화하고, 프레임워크는 이를 모든 워커 스레드에서 동시에 실행해 각자 샤드를 채우므로 대용량 데이터셋을 병렬로 로드할 수 있습니다.run이 실제 측정 대상이며, 연산 예산이나 시간 예산이 소진될 때까지 반복 호출됩니다. 두 함수 모두ctx테이블을 받아 사용자, 아이템, 연산 수, 시드, 스레드 정보 등을 전달받습니다. - 스토어 – 모든 엔진에 공통된 여덟 개의 동작을 제공합니다. 스크립트는 전역
kv테이블을 호출하고, 선택된 엔진에 해당 동작이 전달됩니다. keybench는 호출을 측정하고, 적절한 히스토그램에 샘플을 기록하며, 원시 연산 수를 카운트합니다. - 백엔드 – 자체 등록 플러그인 형태입니다. 각 엔진은
backends/아래에 위치하고, 빌드 조각과 초기화 코드를 포함해main실행 전에 자신을 등록합니다. 이름으로 엔진을 선택하거나 여러 엔진을 동시에 지정해 비교할 수 있습니다. 새 엔진을 추가하려면 디렉터리와 가상 테이블(vtable)만 만들면 되며, 핵심 코드를 수정할 필요가 없습니다. - 리포터 – 결과를 출력하는 싱크입니다. 실행이 끝나면 메타데이터, 프로브, 집계 포인트, 실시간 샘플을 요청한 모든 리포터에게 전송합니다. 콘솔 리포터는 인간이 읽기 쉬운 형태로 출력하고, TSV 리포터는 기계가 읽을 수 있는 행을, 타임라인 리포터는 실시간 샘플당 한 행을 기록합니다. 여러 리포터를 동시에 실행할 수 있습니다.
Building
keybench는 자체 Lua 구현을 포함하고 있으므로, 기본 빌드에는 C 컴파일러와 pthreads만 있으면 됩니다.
make
위 명령은 메모리 내 스킵리스트 엔진이 컴파일된 ./keybench 실행 파일을 생성합니다. 스킵리스트는 외부 라이브러리가 필요 없으며 기준 엔진으로 사용됩니다.
영구 저장 엔진을 포함하려면 엔진 이름을 지정합니다.
make ROCKSDB=1 # RocksDB 엔진 추가
make TIDESDB=1 # TidesDB 엔진 추가
make BACKENDS="skiplist rocksdb tidesdb"
기본적으로는 시스템 기본 메모리 할당자를 사용합니다. 배포판에 포함된 RocksDB/TidesDB가 jemalloc 등 다른 할당자를 사용해 빌드된 경우, 동일한 할당자를 먼저 링크해야 합니다. 그렇지 않으면 데이터베이스를 여러 번 열 때 충돌이 발생할 수 있습니다.
make ROCKSDB=1 ROCKSDB_CFLAGS=-I/opt/rocksdb/include ROCKSDB_LIBS="-L/opt/rocksdb/lib -lrocksdb"
make TIDESDB=1 TIDESDB_CFLAGS=-I/opt/tidesdb/include TIDESDB_LIBS="-L/opt/tidesdb/lib -ltidesdb"
make ROCKSDB=1 TIDESDB=1 ALLOC=jemalloc
벤더된 Lua 대신 시스템 Lua를 사용하려면:
make LUA_SYS=1
그 외 주요 타깃:
make run # 빌드 후 mixed 워크로드를 한 번 실행
make all-wl # 빌드 후 workloads/ 디렉터리의 모든 워크로드 실행
make clean
Running
가장 간단한 실행은 워크로드 파일 하나만 지정합니다.
./keybench workloads/mixed.lua
위 명령은 메모리 내 스킵리스트 엔진을 사용해 한 스레드에서 200 000 단위를 수행합니다(디스크가 필요 없습니다). 영구 엔진을 사용할 경우 --data-dir 옵션으로 저장 위치를 지정해야 합니다.
./keybench --backend rocksdb --data-dir /mnt/disk workloads/mixed.lua
출력 예시:
keybench 0.1.0: 1 workload(s), engine=rocksdb seed=1 repeat=1
threads: 1
[probe: system]
host agpmastersystem
os Linux 6.2.0-39-generic x86_64
cpu 11th Gen Intel(R) Core(TM) i7-11700K @ 3.60GHz
cpu_cores 16
ram 46.8 GiB
page_size 4096
data_fs(/mnt/disk) 76.9 GiB free of 158.9 GiB
data_dev /dev/sdb3 on /mnt/disk (ext4)
data_disk solid state
data_disk_model WDC WDS500G2B0A
[probe: build]
keybench 0.1.0
compiler 12.3.0
std C 201112 (optimized)
allocator jemalloc (glibc 2.37)
=== mixed ===
engine=rocksdb 11.1.1 threads=1 (median of 1 run)
op count p50 p99 p99.9 max
put 60404 8.26 us 20.35 us 61.70 us 143.73 us
get 99617 9.54 us 28.80 us 40.70 us 102.28 us
del 19955 3.95 us 12.48 us 20.86 us 102.90 us
range 20024 391.17 us 577.54 us 692.22 us 790.09 us
get hit rate: 92.2%
throughput: 18658 /sec (1 op per unit)
시스템 프로브는 머신 정보(디바이스, 파일시스템, SSD/HDD 여부 등)를, 빌드 프로브는 컴파일러와 할당자를 기록합니다. 연산별 표는 지연 시간 분포를, 히트 레이트는 get 성공 비율을, 처리량 라인은 wu/s와 필요 시 ops/s를 보여줍니다.
Live progress
포인트 스트림의 두 단계는 1초마다 stderr에 출력되므로, 긴 실행도 멈춘 것이 아니라 진행 중임을 알 수 있습니다. 시드 단계는 초당 한 줄씩 현재까지 기록된 키 수와 처리 속도를 출력하고, 마지막에 총합을 표시합니다. 측정 단계도 동일하게 초당 한 줄씩 출력해 로그 형태로 스크롤됩니다. 대용량 데이터셋의 시드 과정 자체도 중요한 관찰 대상입니다.
seeding cart on rocksdb 11.1.1 with 8 threads
seeding cart rocksdb 11.1.1 1.0s 193024 keys 192349 keys/s
seeding cart rocksdb 11.1.1 2.0s 402944 keys 209217 keys/s
seeding cart rocksdb 11.1.