ewe@5 benchmarks

This page evaluates the performance of ewe@5 against seven other BEAM-based web servers. Benchmarks were conducted on August 17, 2026, across fifteen endpoints using both HTTP/1.1 and h2c (cleartext HTTP/2).

ewe@5 outperformed competitors in 11 of the 15 tested scenarios on HTTP/1. It demonstrated a 32% to 49% throughput improvement over ewe@4 across all shared test cases and a 23% to 83% advantage over mist. On HTTP/2, ewe@5 delivered roughly twice the throughput of bandit but trailed roadrunner on request heavy scenarios. The file_big endpoint on h2c represents its weakest comparative performance. See Known Bottlenecks and Limitations section.

All reported figures represent the median of five consecutive runs.

Content

How to read these numbers

No network overhead. The benchmark was run entirely on localhost. The load generator and server were pinned to six dedicated CPU cores each. Real world network stack and TCP overheads are not represented in these numbers.

Numbers above 200,000 req/s are approximate. At rates exceeding 200,000 req/s, h2load’s own CPU consumption impacts the results as it competes with the server for system resources. Adjusting h2load’s thread count on the hello case produced a 14% spread ranging from 228,000 to 261,000 req/s without any changes to the server configuration. Small performance gaps in this high range should therefore be treated as approximate. Comparisons below 200,000 req/s provide more stable and reliable data.

Cases that has no native API for the implementation are skipped. Some test cases were omitted for servers lacking native API support. bandit, chatterbox, elli and httpd have no SSE API and elli and httpd cannot read a request body incrementally so the cases are skipped for them.

Every server runs on default settings. All servers were tested using default configurations. The single exception is TCP_NODELAY. elli and httpd do not enable it themselves so during the benchmark we turn it on for them since every other server already has it on by default.

Not every row survived. 95 of 740 throughput rows and 83 of 282 latency rows are missing from these tables because the server stalled, answered incorrectly or was driven past its capacity. In each case the number not really described the server’s speed.

How it was run

load        h2load, 4 threads
            h1         50 connections x 1 stream
            h2         50 connections x 10 streams
duration    10s measured, 3s warmup, 5 repeats
latency     wrk2 http/1, 50 connections, 4 threads, fixed rate ladders
cpu         server pinned to cores 0-5, load generator to cores 6-11
Versions
version
Gleam1.18.0
Erlang/OTP29
Elixir1.19.4
h2loadnghttp2 1.69.0
wrk244a94c1
ewe@44.0.1
mist6.0.3
elli3.3.0
bandit1.12.0
roadrunner0.8.0
chatterbox0.8.0

Throughput: HTTP/1.1

Requests per second, higher is better. Best in each row is bold.

caseewe@5ewe@4mistelliroadrunnerbandithttpd
hello263,626177,750195,679260,834245,115145,446100,347
hello_headers212,005142,136152,771214,961197,112120,48284,057
echo_1kb219,243157,622172,493231,526210,856133,83073,699
echo_1kb_headers177,036126,760142,050205,897185,680113,01855,418
echo_10kb190,931144,334155,218166,859183,455114,65212,497
echo_chunked_10kb194,402136,069155,080183,763104,944
file_tiny68,96937,64938,74537,69950,00733,566
file_small60,21335,12335,74335,30345,29329,604
file_big3,4113,4203,3693,3763,3461,314
stream139,20734,51382,57798,329
stream_small7,9216,3846,8017,890
stream_big7,8537,3136,7297,692
sse17,39812,350
sse_small6,1226,039
sse_big5,8672,432

ewe@5 achieved the highest throughput in 11 out of 15 scenarios. Three of the cases where it trailed involved small payload request handling where elli maintained an advantage. The fourth was file_big where five of the servers performed within 2% of each other, as the 5 MiB payload transfer time became the primary bottleneck.

The streaming benchmarks sent multiple frames per request:

caseewe@5roadrunnerbandithttpd
stream278,41469,026165,154196,658
stream_small792,090638,440680,100788,960
stream_big502,592468,019430,682492,307
sse556,720395,184
sse_small612,230603,940
sse_big375,482155,616

Throughput: HTTP/2

caseewe@5roadrunnerbanditchatterbox
hello161,818208,64677,1638,307
hello_headers132,414170,41470,7818,307
echo_1kb114,323179,32066,7937,087
echo_1kb_headers100,474146,62360,9327,072
echo_10kb94,743139,06355,1538,695
echo_chunked_10kb91,506134,67152,6498,688
file_tiny82,60134,65340,1928,372
file_small51,61024,96530,24626,198
file_big4971,005750527
stream88,401114,73350,0328,764
stream_small5,0866,1494,45510,687
stream_big5,1896,4284,554
sse10,95515,414
sse_small3,8175,547
sse_big3,3232,162

Roadrunner led in most h2c scenarios. Known bottlenecks and limitations covers where are the flaws of ewe@5 implementation.

Against ewe@4

Shared endpoints over HTTP/1.1:

caseewe@5ewe@4gain
hello263,626177,750+48%
hello_headers212,005142,136+49%
echo_1kb219,243157,622+39%
echo_1kb_headers177,036126,760+40%
echo_10kb190,931144,334+32%
echo_chunked_10kb194,402136,069+43%

ewe@5 achieved a 32% to 49% throughput increase across all shared test cases. The remaining nine cases are omitted because ewe@4 did not successfully process multiple requests per connection.

ewe@4 shows latency degradation under sustained loads. When held at 150,000 req/s on hello_headers, its p99 latency rose to 872.96 ms while ewe@5 maintained 3.67 ms. Similarly on echo_chunked_10kb at 100,000 req/s ewe@4 recorded 511.49 ms compared to ewe@5’s 4.01 ms.

Against mist

caseewe@5mistgain
hello263,626195,679+35%
hello_headers212,005152,771+39%
echo_1kb219,243172,493+27%
echo_1kb_headers177,036142,050+25%
echo_10kb190,931155,218+23%
echo_chunked_10kb194,402155,080+25%
file_tiny68,96937,649+83%
file_small60,21335,123+71%

ewe@5 showed a 23% to 39% throughput improvement on standard request cases and a 71% to 83% advantage on static file serving.

Under load ewe@5 also demonstrated lower latency percentiles. At 150,000 req/s on hello_headers, mist’s p99 latency was 9.94 ms compared to ewe@5’s 3.67 ms. At 100,000 req/s on echo_chunked_10kb mist recorded 7.12 ms against ewe@5’s 4.01 ms.

Against elli and roadrunner

These two engines represent the primary benchmarks for HTTP/1.1 performance.

elli is a mature production-proven Erlang server. ewe@5 maintained competitive performance alongside it:

caseewe@5ellidifference
hello263,626260,834+1%
hello_headers212,005214,961−1%
echo_1kb219,243231,526−5%
echo_1kb_headers177,036205,897−14%
echo_10kb190,931166,859+14%
file_tiny68,96938,745+78%
file_small60,21335,743+68%
file_big3,4113,369+1%

There is no SSE API and no incremental body read for elli.

roadrunner competes across the whole tests:

caseewe@5roadrunnerdifference
hello263,626245,115+8%
hello_headers212,005197,112+8%
echo_1kb219,243210,856+4%
echo_1kb_headers177,036185,680−5%
echo_10kb190,931183,455+4%
echo_chunked_10kb194,402183,763+6%
file_tiny68,96937,699+83%
file_small60,21335,303+71%
file_big3,4113,376+1%
stream139,20734,513+303%
stream_small7,9216,384+24%
stream_big7,8537,313+7%
sse17,39812,350+41%
sse_small6,1226,039+1%
sse_big5,8672,432+141%

ewe@5 led in 14 of 15 on HTTP/1.1, a few percent ahead on the plain request cases, far ahead on files and streaming.

Against bandit on HTTP/2

caseewe@5banditdifference
hello161,81877,163+110%
hello_headers132,41470,781+87%
echo_1kb114,32366,793+71%
echo_1kb_headers100,47460,932+65%
echo_10kb94,74355,153+72%
echo_chunked_10kb91,50652,649+74%
file_tiny82,60140,192+106%
file_small51,61030,246+71%
file_big497750−34%
stream88,40150,032+77%
stream_small5,0864,455+14%
stream_big5,1894,554+14%

ewe@5 led in 11 of the 12 shared test cases showing a 65% to 110% improvement on request and file tasks and a 14% improvement on smaller stream runs. The exception is file_big which is affected by the HTTP/2 window limitation detailed in the Known Bottlenecks and Limitations section.

wrk2 has no HTTP/2 support so there are no latency numbers for this table.

Latency

wrk2 targets a fixed request rate meaning its latency percentiles account for queuing delays before transmission. If the load generator fails to maintain the target rate the server has exceeded its capacity and the resulting percentiles may no longer accurately reflect performance.

A means the server couldn’t hold that rate.

caseewe@5ewe@4mistelliroadrunnerbandithttpd
hello @ 150,0003.97ms4.96ms4.09ms3.84ms4.03ms53.98ms
echo_1kb @ 100,0003.91ms3.75ms3.69ms3.77ms3.63ms3.90ms
echo_10kb @ 100,0003.75ms3.86ms4.17ms4.52ms3.78ms6.43ms
echo_chunked_10kb @ 70,0003.57ms3.57ms3.49ms3.50ms7.70ms
file_small @ 30,0004.65ms6.08ms4.73ms5.13ms4.49ms191.74ms
file_big @ 1,5004.61ms4.89ms4.76ms4.72ms4.72ms
stream @ 60,0003.33ms4.28ms3.85ms
sse @ 6,0002.59ms3.36ms3.12ms2.83ms

ewe@5 achieved the lowest latency in four of the eight scenarios and remained within 0.28 ms of the top-performing server in the remaining four.

Known Bottlenecks and Limitations

HTTP/2 Request Handling: Roadrunner maintains a 22% to 36% performance lead over h2c on hello, echo cases and stream. While ewe@5 significantly outperforms bandit in these tests, roadrunner’s HTTP/2 implementation is simply faster.

Large Files over HTTP/2: ewe@5 achieves only 497 req/s compared to roadrunner’s 1,005 req/s and bandit’s 750 req/s. The bottleneck stems from the connection flow-control window. ewe@5 defaults to a static window size of 65,535 bytes without dynamic window scaling. Consequently delivering a 5 MiB response requires approximately eighty WINDOW_UPDATE round trips which must be coordinated across ten concurrent streams.

Small Payloads on HTTP/1.1: ewe@5 falls behind elli by up to 14% on specific cases like echo_1kb_headers.

SSE over HTTP/2. ewe@5 trails roadrunner by 29% to 31% on sse and sse_small over h2c.

Reproducing

To run these benchmarks locally:

cd benchmark
REPEATS=5 ./throughput.sh
./latency.sh
Search Document