Verified against Semitexa Ultimate 2026.11.05.1529
Benchmark Baseline
These are the numbers of one release on one machine. They are here so a later release can be compared with this one, and so you can see how they were made. They are not a claim about your application: a real page with its own queries will behave differently, and only measuring it will tell you how.
What ran
- Release: 2026.11.05.1529,
APP_ENV=prod,APP_DEBUG=0. PHP 8.4.27, Swoole 6.2.3. - Server: 4 workers,
SWOOLE_MAX_REQUEST=10000, MySQL and Redis on the same host. - Machine: a laptop, Intel Core i7-1185G7 (4 cores, 8 threads), 31 GB of memory, otherwise idle.
- Load:
bin/semitexa dev:bench, on the same machine as the server, so the load generator takes CPU from it. - Method: a closed loop over keep-alive connections, opened 10 ms apart. Each route and level ran 5 windows of 10 seconds after a 2-second warm-up, the defaults but for
--repeat, and the table shows the median window.
Results
Requests per second of the median window, with the range across the 5 windows, then latency in milliseconds.
| Route | Connections | req/s | Range | p50 | p95 | p99 |
|---|---|---|---|---|---|---|
GET / |
1 | 193 | 185-194 | 5.1 | 6.0 | 7.2 |
| 16 | 479 | 371-589 | 11.9 | 84.9 | 108.8 | |
| 64 | 518 | 485-604 | 110.7 | 286.0 | 386.3 | |
GET /demo/get-started/installation |
1 | 130 | 128-132 | 7.4 | 10.5 | 11.6 |
| 16 | 351 | 334-427 | 32.6 | 93.5 | 110.9 | |
| 64 | 421 | 361-438 | 186.8 | 313.0 | 372.8 | |
GET /demo/data/relations |
1 | 108 | 107-111 | 9.0 | 10.8 | 13.5 |
| 16 | 299 | 137-314 | 38.1 | 112.9 | 130.4 | |
| 64 | 385 | 352-396 | 112.3 | 401.8 | 444.8 | |
GET /demo/api/v1/products (JSON) |
1 | 922 | 913-927 | 1.0 | 1.5 | 1.8 |
| 16 | 2862 | 2577-3405 | 4.6 | 14.7 | 15.7 | |
| 64 | 3247 | 3170-3661 | 11.8 | 44.1 | 50.8 |
The three HTML routes render a full page. /demo/data/relations runs 5 kinds of query per request. The JSON route answers from the database with no template.
19 of about 504,000 requests failed (0.004%), all at 64 connections. They are requests cut as a worker was replaced: see Worker recycling.
How far to trust these
- One connection is steady: its windows stay within 5%.
- Sixteen and more are not as steady. Most routes stay within a factor of 1.6 between windows.
/demo/data/relationsat 16 connections had one window at 137 req/s against about 300 for the rest, and nothing here explains it. - The machine drifts. With identical code a route has measured about 150 and about 115 req/s at one connection on the same laptop, on different runs. Compare numbers taken side by side, not numbers taken days apart.
- Each connection opens when the one before has had time. A burst of connections in the same instant goes to whichever worker wakes first, and measures that rather than the server.
- It is a closed loop. A client waits for its answer before sending again, so the server sets the pace. Real traffic does not wait, and this says nothing about behaviour past saturation.
- Minutes, not days. For what happens over hours, see the long-run test.
Measure your own
Start the server in production mode, then:
bin/semitexa dev:bench -r / -r /your/route -c 1,16,64 --repeat=5
A JSON route answers 406 to the default Accept, so name it. A route bound to a tenant needs its host:
bin/semitexa dev:bench -r /api/orders --accept=application/json
bin/semitexa dev:bench -r / --host=shop.example.test
To compare two versions, alternate them on the same machine and repeat: one before and one after, minutes apart, can show a difference that is only the machine.