Pruebas de rendimiento
El banco de pruebas escribe JSON estables y un informe legible. Se encuentra en bench/ dentro del storh repositorio y no forma parte del paquete Composer desde 0.5.2, por lo que los comandos composer bench* de abajo solo se ejecutan desde un clon, después de 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
Resultados de referencia
Medianas de tres ejecuciones completas del banco de pruebas, 50,000 registros por motor, en un Apple M1 Pro (16 GB, macOS 26.5, APFS), con PHP 8.5 CLI, opcache y JIT desactivados:
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M
Cada escritura se vacía y sincroniza con fsync antes de que storh informe que se ha almacenado, así que las tasas de escritura dependen del sistema de archivos. Las lecturas se ejecutan en la caché de páginas del sistema operativo. Las tasas se calculan a partir de las medianas; ejecute composer bench desde un clon del repositorio para probar su propio hardware.
| DocStore, 50k registros | mediana |
|---|---|
put(), un archivo persistente por registro |
3.3k registros/s |
putStream() ingesta masiva |
3.7k registros/s |
importJsonl() |
3.6k registros/s |
get() lectura puntual, validada con STAT |
5.6 µs |
| reapertura de un almacén existente | 0.26 ms |
consulta de igualdad indexada, limit(100) |
1.1 ms |
count() indexado de la colección |
41 µs |
| creación de índice, 2 campos de igualdad + 1 de rango | 52k registros/s |
stream() completo con nueva validación STAT |
103k registros/s |
exportJsonl() |
104k registros/s |
| SegmentedLog, 50k registros, segmentos de 16 KB | mediana |
|---|---|
put(), fsync por anexado |
16k anexados/s |
appendStream() ingesta masiva |
56k registros/s |
| lectura con cursor, 100 registros desde el punto medio | 2.1 ms |
| lectura por intervalo de tiempo | 1.1 ms |
count() de igualdad |
11 µs |
compact() todos los segmentos sellados |
69k registros/s |
| reapertura con recuperación de cola truncada | 218k registros/s |
El banco de pruebas sella un segmento cada 16 KB para poner a prueba la rotación de segmentos; el tamaño predeterminado es 1 MiB.
| Queue, 50k trabajos | mediana |
|---|---|
enqueue(), fsync por evento |
22k trabajos/s |
claim() |
23k trabajos/s |
complete() |
23k trabajos/s |
enqueueMany() |
209k trabajos/s |
claimMany() |
467k trabajos/s |
completeMany() |
512k trabajos/s |
| SQL Mirror, SQLite, 50k registros | mediana |
|---|---|
push() inicial |
61k filas/s |
push() sin cambios |
86k registros/s |
flush(), 100 ids |
7.1 ms |
SQL indexado COUNT en la réplica |
6.9 ms |
rebuild() |
65k filas/s |
pull() restauración, un archivo persistente por registro |
3.2k registros/s |
| Micro | mediana |
|---|---|
get() en caché, frío y luego caliente (MemoryCache, STAT) |
83 µs / 6.3 µs |
| generación de UUIDv7 | 1.3 µs |
| validación de UUIDv7 | 0.30 µs |
| filtrado de predicados en memoria | 4.4M filas/s |
Objetivos cubiertos:
- Guardar en DocStore, guardar en flujo, importar/exportar JSONL, obtener, eliminar, transmitir y crear índices
- Consultas de DocStore con índices de igualdad, compuestos y de rango; rangos ordenados ascendentes y descendentes; recuento indexado; búsqueda por ID y consultas sin índice
- Anexado a SegmentedLog, anexado en flujo, lecturas con cursor, lecturas por intervalo de tiempo, lecturas de intervalos compactados, búsquedas con cursor y por ID en QueryBuilder, recuentos, recuentos de anexado masivo, estadísticas y compactación
- Encolar, encolar en lote, reclamar, reclamar en lote, completar, completar en lote y volver a encolar en Queue
- Filtrado de predicados de RecordQuery y QueryCondition en filas en memoria
- Generación monótona de UUIDv7, generación con marcas de tiempo dispersas, validación y extracción de marcas de tiempo
- Recuperación de líneas finales incompletas
- Lecturas de caché en frío y en caliente con validación por hash, estadísticas o de confianza
Ruta de salida predeterminada:
build/bench-current.json
Las ejecuciones por rango escriben un archivo JSON por conjunto de datos y motor en build/bench-ranges/.
bench:gate compara dos archivos de JSON de pruebas de rendimiento y devuelve un código distinto de cero si alguna métrica monitorizada es más lenta que el umbral configurado. Si no se proporcionan valores de --metric, se aplica el umbral a todas las métricas del archivo de referencia.
bench:ci es la puerta de control de regresiones de GitHub Actions. Ejecuta una prueba de rendimiento de 1k con todos los motores, compara métricas seleccionadas de escritura, lectura, caché y UUID con la referencia bench/baselines/ci-1k-all.json registrada y usa un umbral predeterminado amplio para evitar el ruido habitual de los entornos de ejecución y aun así detectar regresiones de rendimiento importantes.
bench:jit ejecuta el mismo banco de pruebas con CLI opcache y JIT de rastreo activados, y desactiva Xdebug para el proceso de pruebas, de modo que PHP pueda activar JIT. El JSON generado incluye indicadores de ejecución que muestran si opcache y JIT estaban activos durante esa ejecución.