벤치마크
벤치마크 하네스는 안정적인 JSON와 사람이 읽기 쉬운 보고서를 작성합니다. 이 도구는 bench/에 있으며, storh 저장소에 포함되어 있습니다. Composer 패키지에는 0.5.2 이후 포함되지 않았으므로, 아래 composer bench* 명령은 복제본에서 composer install한 후에만 실행됩니다:
composer bench
composer bench -- --dataset=100000 --engine=doc
composer bench -- --dataset=100000 --engine=log
composer bench -- --dataset=100000 --engine=queue
composer bench -- --dataset=100000 --engine=cache --cache-validation=trust
composer bench -- --dataset=100000 --engine=filter
composer bench -- --dataset=1000000 --engine=uuid
composer bench:jit -- --dataset=1000000 --engine=uuid
composer bench:repeat -- --dataset=100000 --engine=doc --repeat=5
composer bench:repeat -- --dataset=1000000 --engine=filter --repeat=5 --memory-limit=512M
composer bench:range -- --datasets=1000,10000,50000,100000
composer bench:range -- --datasets=100000 --engines=cache --memory-limit=512M
composer bench:compare build/bench-main.json build/bench-current.json
composer bench:gate -- build/bench-main.json build/bench-current.json --threshold=10 --metric=doc.put --metric=log.stream
composer bench:ci
기준 결과
50,000개의 레코드를 엔진별로 처리한 하네스 전체 실행 3회의 중앙값입니다. Apple M1 Pro(16 GB, macOS 26.5, APFS)에서 PHP 8.5 CLI를 사용했으며 opcache와 JIT는 껐습니다.
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M
storh에서 저장됐다고 보고하기 전에 모든 쓰기를 flush 및 fsync하므로 쓰기 속도는 파일 시스템에 좌우됩니다. 읽기는 OS 페이지 캐시에서 실행됩니다. 속도는 중앙값에서 산출했습니다. 사용자의 하드웨어에서 측정하려면 리포지토리를 클론해 composer bench을 실행하세요.
| DocStore, 레코드 50k개 | 중앙값 |
|---|---|
put(), 레코드당 내구성 파일 1개 |
3.3k 레코드/s |
putStream() 대량 수집 |
3.7k 레코드/s |
importJsonl() |
3.6k 레코드/s |
get() 지점 읽기, STAT 검증 |
5.6 µs |
| 기존 store 다시 열기 | 0.26 ms |
인덱스 동등성 쿼리, limit(100) |
1.1 ms |
컬렉션 전체 인덱스 count() |
41 µs |
| 인덱스 빌드, 동등성 필드 2개 + 범위 필드 1개 | 52k 레코드/s |
STAT 재검증을 포함한 전체 stream() |
103k 레코드/s |
exportJsonl() |
104k 레코드/s |
| SegmentedLog, 레코드 50k개, 세그먼트 16 KB | 중앙값 |
|---|---|
put(), 추가마다 fsync |
초당 16k회 추가 |
appendStream() 대량 수집 |
56k 레코드/s |
| 커서 읽기, 중간 지점부터 레코드 100개 | 2.1 ms |
| 시간 범위 읽기 | 1.1 ms |
동등성 count() |
11 µs |
봉인된 모든 세그먼트 compact() |
69k 레코드/s |
| 잘린 꼬리 복구를 포함해 다시 열기 | 218k 레코드/s |
벤치마크에서는 세그먼트 롤오버를 확인하기 위해 16 KB마다 세그먼트를 봉인합니다. 기본 세그먼트 크기는 1 MiB입니다.
| 큐, 작업 50k개 | 중앙값 |
|---|---|
enqueue(), 이벤트마다 fsync |
22k 작업/s |
claim() |
23k 작업/s |
complete() |
23k 작업/s |
enqueueMany() |
209k 작업/s |
claimMany() |
467k 작업/s |
completeMany() |
512k 작업/s |
| SQL Mirror, SQLite, 레코드 50k개 | 중앙값 |
|---|---|
초기 push() |
61k 행/s |
변경 사항이 없을 때 push() |
86k 레코드/s |
flush(), 100 ID |
7.1 ms |
미러 전체 인덱스 SQL COUNT |
6.9 ms |
rebuild() |
65k 행/s |
pull() 복구, 레코드당 내구성 파일 1개 |
3.2k 레코드/s |
| 마이크로 | 중앙값 |
|---|---|
캐시된 get(), 콜드 후 웜(MemoryCache, STAT) |
83 µs / 6.3 µs |
| UUIDv7 생성 | 1.3 µs |
| UUIDv7 검증 | 0.30 µs |
| 메모리 내 조건자 필터링 | 4.4M 행/s |
대상 범위:
- DocStore put, 스트리밍 put, JSONL 내보내기/가져오기, get, delete, stream 및 인덱스 빌드
- 동등성 인덱스, 복합 인덱스, 범위 인덱스, 오름차순 및 내림차순 정렬 범위, 인덱스 기반 카운트, ID 조회 및 비인덱스 DocStore 쿼리
- SegmentedLog append, 스트리밍 append, 커서 읽기, 시간 범위 읽기, 압축된 시간 범위 읽기, QueryBuilder 커서 및 ID 조회, 카운트, 일괄 append 카운트, 통계, 압축
- Queue enqueue, 일괄 enqueue, claim, 일괄 claim, complete, 일괄 complete, requeue
- 메모리 내 행에 대한 RecordQuery 및 QueryCondition 조건자 필터링
- UUIDv7 단조 증가 생성, 타임스탬프 분산 생성, 검증 및 타임스탬프 추출
- 끝부분이 잘린 줄 복구
- 해시, stat 또는 신뢰 기반 검증을 사용하는 콜드 및 웜 캐시 읽기
기본 출력 경로:
build/bench-current.json
범위 실행은 데이터 세트와 엔진별로 JSON 파일 하나를
build/bench-ranges/에 씁니다.
bench:gate은 벤치마크 JSON 파일 두 개를 비교하고, 추적하는 지표 중 하나라도 설정된 임계값보다 느리면 0이 아닌 상태 코드로 종료합니다. --metric 값이 전달되지 않으면 기준 파일에 있는 모든 지표를 검사합니다.
bench:ci은 GitHub Actions 스모크 회귀 검사 게이트입니다. 모든 엔진에서 1k 벤치마크를 실행하고, 선택한 쓰기/읽기/캐시/UUID 지표를 추적 중인 bench/baselines/ci-1k-all.json 기준과 비교합니다. 일반적인 실행 환경의 변동에도 실패하지 않도록 기본 임계값을 넉넉하게 설정하면서도 큰 성능 회귀는 감지합니다.
bench:jit은 같은 하네스를 CLI opcache와 추적 JIT를 활성화한 상태로 실행하며, 벤치마크 프로세스에서 Xdebug를 비활성화해 PHP이 실제로 JIT를 켤 수 있게 합니다. 생성된 JSON에는 해당 실행에서 opcache와 JIT가 활성 상태였는지 보여 주는 런타임 플래그가 포함됩니다.