Skip to Content
HTTP & Performance

HTTP And Performance

qaws 0.2.7 is optimized for static HTTP throughput while keeping backend selection internal. The common cached path avoids blocking I/O, shared cached-hit locks, per-request allocation, and access-log work when logging is disabled.

Event engine

qaws selects its runtime backend automatically:

PlatformBackend
Linux and Androidepoll
macOS and FreeBSDkqueue
Windows and unsupported targetsblocking worker pool

On evented platforms, each worker normally owns a nonblocking SO_REUSEPORT listener, event descriptor, serve-root handle, cache view, and its connection state. If multiple listeners are unsupported, qaws falls back to the main-thread accept dispatcher without exposing a backend setting.

--workers <n> remains the explicit override. Automatic mode selects the highest-capacity CPU cluster on Linux/Android, uses macOS performance-level CPU information when available, and falls back to the logical CPU count. qaws does not pin worker threads.

Resumable responses

Memory responses, buffered files, and sendfile bodies all use resumable per-connection output state. qaws tracks header/body offsets, enables write readiness only while output is pending, and resumes after EAGAIN. Read readiness is disabled while a response is pending so later pipelined responses cannot overtake it.

Connections that make no write progress for keep_alive_timeout_ms are closed. Client resets and broken pipes during body transfer are treated as normal disconnects rather than noisy internal failures.

Keep-alive and pipelining

HTTP/1.1 keeps connections alive by default. HTTP/1.0 closes by default unless the client requests keep-alive. Connection: close always closes after the current response.

Pipelined requests are processed sequentially and responses remain ordered. Request storage uses start/end offsets rather than copying leftovers after each request. An internal 16-request batch keeps busy pipelines fair; buffered work is requeued internally instead of waiting for another socket event.

HTTP/1.1 requires a nonempty Host. qaws rejects malformed framing, conflicting Content-Length, positive request bodies, every Transfer-Encoding, and oversized headers. A malformed pipelined request closes defensively after an error response when possible.

Cache V2

The small-file cache is enabled by default. It uses shared immutable, reference-counted generations and worker-local views. Established cached hits do not take the global slot-map lock. Refresh locking is per path, so one file reload does not block unrelated paths.

After revalidate_ms, qaws stats the file first. Unchanged size and nanosecond mtime extend the TTL without rereading the body. Changed files receive a new generation while in-flight responses retain the old generation until their references are released.

The memory limit accounts for bodies, paths, headers, response storage, and live old generations. If a new entry cannot fit, qaws serves it uncached; there is no LRU/CLOCK eviction in 0.2.7.

When access logging is disabled, qaws also skips access-record construction, user-agent extraction, remote-address formatting, and per-request timing.

Sendfile

Sendfile remains enabled by default for uncached regular-file GET bodies on supported Unix platforms. Headers are written first, then the file body is resumed through the event output state. Unsupported platforms and failures before body bytes are sent use buffered streaming.

Cached files, HEAD, conditional 304, redirects, and errors do not use the sendfile body path. Byte ranges can use cached, buffered, or sendfile transfer according to the selected representation.

Benchmark systems

The published measurements came from three very different machines. Every HTTP load generator targeted loopback, so the results measure the server and local TCP stack rather than LAN or Internet throughput.

HostHardwareOperating systemqaws automatic runtime
MacBook AirApple M4, 10 cores (4 performance + 6 efficiency), 16 GB unified memorymacOS 26.5.2, Darwin 25.5.0, arm644 workers using kqueue
lite114Qualcomm SM7125 ARM phone, 2 Kryo Gold cores up to 2.323 GHz + 6 Kryo Silver cores up to 1.805 GHz, about 5.5 GiB RAMpostmarketOS edge (Alpine-based), Linux 6.14.7, aarch642 capacity-1024 workers using epoll
local01Intel Core i5-8500T, 6 cores/6 threads up to 3.5 GHz, about 15 GiB RAMDebian 13.5, Linux 6.17.13-2-pve, x86_646 workers using epoll

Linux reported capacities 322 for the six efficiency cores and 1024 for the two performance cores on lite114, which is why automatic mode selected two workers. local01 was under substantial unrelated memory pressure during validation: roughly 12 GiB of RAM and 5.7 GiB of its 8 GiB swap were in use. It was retained as an observational comparison host rather than a release throughput gate.

The cached fixture was a 268-byte HTML document. Transfer tests used sparse 1 MiB and 64 MiB files. Primary qaws comparisons used the native release artifact for each architecture, access logs disabled, and a file-descriptor limit of 65536.

Reproducible benchmark method

Use the same machine, file, binary options, and file-descriptor limit. Primary throughput runs disable access logs:

ulimit -n 65536 qaws --host 127.0.0.1 --port 18086 --serve ./public --no-access-log wrk -t1 -c1 -d10s http://127.0.0.1:18086/ wrk -t1 -c10 -d10s http://127.0.0.1:18086/ wrk -t8 -c100 -d10s http://127.0.0.1:18086/ wrk -t8 -c1000 -d10s http://127.0.0.1:18086/

The release gate alternated 0.2.6 and 0.2.7 for five repetitions and compared medians. The fixture was the same cached 268-byte HTML file. Every listed run completed without socket errors or timeouts.

Host-c1-c10-c100-c1000
Apple Silicon MacBook Air-0.81%+4.03%+4.77%+2.83%
lite114 PostmarketOS ARM+5.05%+0.74%+22.76%-4.11%

Automatic topology detection selected the two 1024-capacity cores on lite114. Its 1 MiB and 64 MiB sendfile medians were about 6-7% below 0.2.6, while cached -c100 improved substantially. On the Mac, 1 MiB and 64 MiB transfer medians improved about 13% and 4%. This is an explicit throughput tradeoff observed on those machines, not a universal prediction.

nginx and Caddy comparison

A separate five-repetition, rotating-order matrix used five-second runs, the same fixtures, and disabled access logging. Values below are medians shown as c100 req/s / c1000 req/s / 64 MiB transfer.

HostqawsnginxCaddy
MacBook Air200.7k / 181.0k / 7.24 GiB/s84.7k / 79.6k / 6.76 GiB/s47.3k / 41.1k / 6.79 GiB/s
lite114166.7k / 122.1k / 10.20 GiB/s73.3k / 70.9k / 11.68 GiB/s24.4k / 22.8k / 11.38 GiB/s
local01298.5k / 322.2k / 10.10 GiB/s125.3k / 127.9k / 10.17 GiB/s54.1k / 57.4k / 9.91 GiB/s

The comparison used nginx 1.31.2 and Caddy 2.11.4 on the Mac, nginx 1.30.3 and Caddy 2.11.4 on lite114, and nginx 1.26.3 and Caddy 2.6.2 on local01. All measured runs reported zero socket errors. Loopback results are useful for regression analysis, not promises for production networks.

Additional diagnostics

Force one request per connection to measure connection churn:

qaws --host 127.0.0.1 --port 18086 --serve ./public --no-access-log --no-keep-alive wrk -t8 -c100 -d10s http://127.0.0.1:18086/

Create large files for sendfile comparisons:

truncate -s 1m public/one-mib.bin truncate -s 64m public/sixty-four-mib.bin wrk -t4 -c16 -d30s http://127.0.0.1:18086/one-mib.bin wrk -t4 -c16 -d30s http://127.0.0.1:18086/sixty-four-mib.bin

Repeat with --no-sendfile, a cache-disabled JSON config, enabled access logs, idle keep-alive clients, and throttled large downloads to isolate each path.

Not implemented

qaws does not provide runtime compression, multipart range responses, TLS, authentication, directory listings, SPA fallback, filesystem watchers, immutable cache mode, LRU/CLOCK eviction, worker affinity, io_uring, or IOCP.

qaws docs. Built for static hosting.