Przejdź do treści

Testy wydajnościowe

Zestaw testów wydajności zapisuje stabilne JSON oraz czytelny dla człowieka raport. Znajduje się w bench/ w repozytorium storh i nie jest częścią pakietu Composer od wersji 0.5.2, dlatego poniższe polecenia composer bench* można uruchomić tylko z klonu repozytorium, po composer install:

Powłoka
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

Wyniki referencyjne

Mediany z trzech pełnych przebiegów zestawu testów, po 50,000 rekordów na silnik, na Apple M1 Pro (16 GB, macOS 26.5, APFS) z PHP 8.5 CLI, opcache i JIT wyłączonymi:

Powłoka
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M

Każdy zapis jest opróżniany z bufora i synchronizowany przez fsync, zanim storh zgłosi, że został zapisany, więc szybkość zapisu zależy od systemu plików. Odczyty korzystają z pamięci podręcznej stron systemu operacyjnego. Szybkości obliczono na podstawie median; uruchom composer bench z klonu repozytorium, aby sprawdzić działanie na własnym sprzęcie.

DocStore, 50 tys. rekordów mediana
put(), jeden trwały plik na rekord 3.3 tys. rekordów/s
masowe pobieranie putStream() 3.7 tys. rekordów/s
importJsonl() 3.6 tys. rekordów/s
odczyt punktowy get(), z weryfikacją STAT 5.6 µs
ponowne otwarcie istniejącego magazynu 0.26 ms
indeksowane zapytanie równościowe, limit(100) 1.1 ms
indeksowane count() w całej kolekcji 41 µs
budowanie indeksu, 2 pola równościowe + 1 zakresu 52 tys. rekordów/s
pełne stream() z ponowną weryfikacją STAT 103 tys. rekordów/s
exportJsonl() 104 tys. rekordów/s
SegmentedLog, 50 tys. rekordów, segmenty 16 KB mediana
put(), fsync przy każdym dopisaniu 16 tys. dopisań/s
masowe pobieranie appendStream() 56 tys. rekordów/s
odczyt kursorem, 100 rekordów od środka 2.1 ms
odczyt z zakresu czasu 1.1 ms
indeks równościowy count() 11 µs
compact() wszystkich zamkniętych segmentów 69 tys. rekordów/s
ponowne otwarcie z odzyskiwaniem niekompletnego końca 218 tys. rekordów/s

Test wydajności zamyka segment co 16 KB, aby obciążyć rotację segmentów; domyślny rozmiar segmentu to 1 MiB.

Queue, 50 tys. zadań mediana
enqueue(), fsync przy każdym zdarzeniu 22 tys. zadań/s
claim() 23 tys. zadań/s
complete() 23 tys. zadań/s
enqueueMany() 209 tys. zadań/s
claimMany() 467 tys. zadań/s
completeMany() 512 tys. zadań/s
SQL Mirror, SQLite, 50 tys. rekordów mediana
początkowe push() 61 tys. wierszy/s
push() bez żadnych zmian 86 tys. rekordów/s
flush(), identyfikatory 100 7.1 ms
indeksowany SQL COUNT w całym mirrorze 6.9 ms
rebuild() 65 tys. wierszy/s
przywracanie pull(), jeden trwały plik na rekord 3.2 tys. rekordów/s
Micro mediana
buforowane get(), zimne, a następnie ciepłe (MemoryCache, STAT) 83 µs / 6.3 µs
generowanie UUIDv7 1.3 µs
weryfikacja UUIDv7 0.30 µs
filtrowanie predykatów w pamięci 4.4 mln wierszy/s

Objęte testami cele:

  • DocStore put, put strumieniowe, JSONL eksport/import, get, delete, stream i budowanie indeksu
  • zapytania DocStore z indeksami równościowymi, złożonymi i zakresowymi, uporządkowanymi zakresami rosnącymi i malejącymi, zliczaniem indeksowanym, wyszukiwaniem po ID oraz zapytania bez indeksu
  • SegmentedLog append, append strumieniowe, odczyty kursorem, odczyty zakresu czasu, odczyty skompaktowanego zakresu czasu, wyszukiwanie kursorem i po ID przez QueryBuilder, zliczanie, zliczanie masowych zapisów, statystyki, kompaktowanie
  • Queue enqueue, masowe enqueue, claim, masowe claim, complete, masowe complete, requeue
  • filtrowanie predykatami RecordQuery i QueryCondition wierszy przechowywanych w pamięci
  • UUIDv7 generowanie monotoniczne, generowanie z rozłożeniem znaczników czasu, walidacja i wyodrębnianie znaczników czasu
  • odzyskiwanie po urwanej końcowej linii
  • odczyty z zimnej i ciepłej pamięci podręcznej z walidacją przez hash, stat lub zaufaną walidację

Domyślna ścieżka wyjściowa:

Text
build/bench-current.json

Uruchomienia zakresowe zapisują jeden plik JSON dla każdego zbioru danych i silnika w build/bench-ranges/.

bench:gate porównuje dwa pliki JSON z benchmarkami i kończy działanie kodem różnym od zera, gdy dowolna śledzona metryka jest wolniejsza niż skonfigurowany próg. Jeśli nie przekazano wartości --metric, sprawdza każdą metrykę z pliku bazowego.

bench:ci to bramka regresji dymnej GitHub Actions. Uruchamia benchmark 1k dla wszystkich silników, porównuje wybrane metryki zapisu/odczytu/pamięci podręcznej/UUID z śledzonym punktem odniesienia bench/baselines/ci-1k-all.json i stosuje szeroki domyślny próg, aby uniknąć typowych wahań środowiska uruchomieniowego, a zarazem wykrywać duże regresje wydajności.

bench:jit uruchamia ten sam zestaw testowy z włączonym CLI opcache i JIT śledzącym, a dla procesu benchmarku wyłącza Xdebug, aby PHP mógł rzeczywiście włączyć JIT. Wygenerowany JSON zawiera flagi środowiska uruchomieniowego pokazujące, czy opcache i JIT były aktywne podczas tego uruchomienia.