Scale WebSockets to N users : add servers, they share one Redis.
A WebSocket stays on the server it reached, and a notification can come from any server : Redis carries it to the one which holds the client. So N users are a matter of memory, spread over as many servers as it takes. Measured on 3 clusters of the same price : the same 8 dedicated vCPUs and 16 GB for API Maker as one server, two, and four, with MongoDB and Redis on a server of their own.
The picture¶
- The clients connect to one address,
wss://ws.example.com, with their tokens in the query string. - A load balancer ends TLS and hands each WebSocket to one API Maker server : Caddy (the installer writes its line), HAProxy, or the load balancer of your cloud. It needs no sticky session, a WebSocket stays on the server it reached.
- Each API Maker server checks the tokens, keeps its clients and their subscriptions, and pings them. Its clients cost it memory, about 8.5 to 8.8 KB each, and almost no CPU while they wait.
- One Redis, the internal Redis of API Maker, is shared by the servers : a key and a channel for every subscription. The server which answers an API call, or runs
emitEventWS, publishes the notification there ; the server which holds the client hears it and sends it. - MongoDB keeps the accounts, the events and their can user connect code : read when a client subscribes, then kept by each server.
Count the servers¶
- Memory. Users connected at once × the memory of a client (8.5 to 8.8 KB with both tokens and one subscription ; more subscriptions cost a little more each). It is spread over the API servers ; Redis takes 1.0 to 1.1 KB of it per subscription.
- Servers. That memory ÷ what one server has left once API Maker, its sandboxes and Linux have theirs : the page of each server gives the clients it held. Add one server, so that the others can take the clients of one which stops.
- The rest follows. New clients a second and notifications a second grow with the vCPUs : the results of each server size say how many.
| Users connected at once | Memory they take, in all | Servers of 16 GB (1,732,035 clients each) | Memory of Redis |
|---|---|---|---|
| 10,000 | 86 MB | 1 + 1 spare | 10 MB |
| 100,000 | 861 MB | 1 + 1 spare | 104 MB |
| 500,000 | 4.2 GB | 1 + 1 spare | 522 MB |
| 1,000,000 | 8.4 GB | 1 + 1 spare | 1 GB |
| 5,000,000 | 42.1 GB | 3 + 1 spare | 5.1 GB |
The open files of the service and the table of the firewall follow the memory of the server (the installer, 256 for each MB) : the memory is what ends, on every server measured.
One big server or several small ones¶
The same 8 dedicated vCPUs and 16 GB of memory for API Maker, at the same price a month, as one server, two, and four. Each time, MongoDB and Redis run on a server of their own, the clients go to the API servers in turn, and autocannon calls every API server at once.
| 1 × 8 vCPU · 16 GB | 2 × 4 vCPU · 8 GB | 4 × 2 vCPU · 4 GB | |
|---|---|---|---|
| Clients connected at once | 2,022,026 | 1,915,960 | 1,619,372 |
| … on each server | 2,022,026 | 957,980 | 404,843 |
| Memory per client, in all | 8.8 KB | 8.8 KB | 8.6 KB |
| New clients a second (both tokens, a subscription) | 12,412 | 12,696 | 10,297 |
| … connect only, both tokens | 19,917 | 21,417 | 17,882 |
| A notification for one person : delivery p50 | 1.01 ms | 1.18 ms | 1.37 ms |
| … p99 | 1.72 ms | 1.80 ms | 2.51 ms |
| Notifications a second, one person each | 24,305 | 24,977 | 25,566 |
| Notifications a second, broadcast | 217,711 | 228,910 | 272,609 |
| One server stops : clients dropped | 1,516,519 of 1,516,519 (100 %) | 383,192 of 766,384 (50 %) | 242,906 of 971,623 (25 %) |
| … all back after | 221 s | 68.0 s | 38.0 s |
- They hold about the same. Every server keeps some memory for itself (API Maker at rest, two sandboxes, Linux, Docker), so four small servers hold a little less than one big one.
- They let in about as many clients a second, and tell as many : the work is the same, spread over the same vCPUs, and Redis does its part on its own server.
- A server which stops takes its share of the clients with it : all of them on one big server, a quarter on four. The clients come back by themselves, in 68.0 s on 2 × 4 vCPU · 8 GB and 38.0 s on 4 × 2 vCPU · 4 GB ; on one server they wait for it to start again, 221 s in all.
- Several servers can be restarted one at a time : a new version of API Maker, or of your code, without every client going away at once.
So : one server while everything fits on it, several as soon as the clients must not all drop together. Keep the other servers under 75 % full with four of them (50 % with two) : they then have room for the clients of the one which stops.
What ends first¶
| Limit | Where | What it is |
|---|---|---|
| Memory | Each API server | 8.5 to 8.8 KB per client. The first limit on every server measured. |
| Open files | Each API server | A client is one open file. The installer lets the service open as many as the firewall tracks connections, 256 for each MB of memory and 1,048,576 at the least (LimitNOFILE, and fs.nr_open with it) : API Maker keeps a few for itself, and a sixteenth of its connections (4,096 at the most) for the calls of the APIs : without them, a server whose clients held every connection would answer no API call for as long as they stay. One more client is answered 503. |
| Tracked connections | Each API server | Docker makes Linux track every connection, and Linux drops new ones once its table is full (nf_conntrack: table full). As Linux sets it up, the table holds 262,144 connections on 4 GB or more, and a closed connection stays in it two minutes : the installer gives it room for 256 connections per MB of memory (twice the clients the memory holds, for the ones which come back after a restart while their old connections are still in it), and API Maker says at its start when the table is what limits it. |
| Memory of the network | Each API server | A socket which holds something unread, or something its client has not acknowledged yet, keeps a page of the memory Linux gives all its TCP connections together (net.ipv4.tcp_mem : about 9 % of the memory of the server, as Linux sets it up). Past it, Linux slows every connection of the server down, the ones to its databases too : it happens when every client of a full server does something in the same second (they all come back, they all leave, they are all told). API Maker raises that memory as its clients come, two pages for each connection and never more than a quarter of the memory of the server (tcpMemoryPercent), and closes a socket without waiting for anything else : 601,479 clients which left in the same second were closed after 18.0 s. |
| Ports of a proxy | The load balancer | A proxy reaches one port of one server from one address about 64,000 times (28,000 as Linux sets it up). More clients per server behind a proxy need more addresses on the proxy, or a load balancer which passes the connections on without opening its own. |
| Redis | The data server | One key and one channel per subscription, in memory ; one thread carries every notification. With 2,022,026 clients it held 2,010 MB. |
| Tokens | Your application | A client is closed when its token runs out : give tokens different lifetimes, or the clients all come back in the same second. |
The clusters, one by one¶
1 × 8 vCPU · 16 GB¶
| API servers | 1 × Dedicated 16 GB (g6-dedicated-8) : 8 dedicated vCPUs and 16 GB each, $144 a month in all. API Maker and its two sandboxes on each, attached to the data server by the installer (--secondary) |
| Data server | Dedicated 8 GB (g6-dedicated-4) : AMD EPYC 7642 48-Core Processor, 4 dedicated vCPUs (KVM). MongoDB 8.0.10 and Redis 7.0.5 of API Maker, in Docker ; its own API Maker stopped |
| CPU | AMD EPYC 7642 48-Core Processor |
| System | Ubuntu 24.04.4 LTS, Linux 6.8.0-134, Docker 29.9.0 |
| API Maker | 4.0.0 : release build of api_maker_be_rust 423a34c with the new WebSocket core of 14 October 2026 (opt-level 3, fat LTO), installed by its installer (https://apimaker.dev/v4/install.sh), no reverse proxy |
| Network limits | as the installer sets them, on every API server : 4,093,696 connections tracked (net.netfilter.nf_conntrack_max), 16,384 waiting to be accepted, 4,093,696 open files. The memory of all its TCP connections together : raised by API Maker as the clients came, to 3.9 GB at the most (net.ipv4.tcp_mem, 25 % of the memory ; Linux gives 9 %) |
| Load generator | Dedicated 32 GB (g6-dedicated-16) : AMD EPYC 7642 48-Core Processor, 16 dedicated vCPUs. The clients go to the API servers in turn, as a load balancer hands them out ; autocannon calls every API server at the same time |
| Network | A VLAN of the same data centre, round trip 0.33 ms (ping) |
| Date | 14 October 2026, 03:19 to 04:02 UTC |
The run stopped adding clients at 2,022,026 : the memory of the server api-1 was full. 2,116 MB of its 15,992 MB were still counted as available, and Linux had moved 1,317 MB to the swap since the clients began to connect, to make that room : 799 MB were left (the run stops when what is available, less what Linux moved to the swap since the clients began to connect, is under 5 % of the memory of the server).
| Clients connected | API Maker, all servers | Linux kernel, all servers | API servers, used | Least available on a server | Redis (its data) | Data server, used | CPU busy |
|---|---|---|---|---|---|---|---|
| No client (at rest) | 127 MB | 184 MB | 792 MB | 15,200 MB | 1 MB | 713 MB | 0.3 % |
| 10,000 clients | 166 MB | 328 MB | 1,816 MB | 14,176 MB | 12 MB | 704 MB | 1.8 % |
| 100,000 clients | 451 MB | 634 MB | 2,359 MB | 13,632 MB | 103 MB | 896 MB | 0.9 % |
| 200,000 clients | 764 MB | 1,080 MB | 3,029 MB | 12,963 MB | 204 MB | 1,009 MB | 1.9 % |
| 400,000 clients | 1,408 MB | 1,971 MB | 4,428 MB | 11,564 MB | 404 MB | 1,204 MB | 3.8 % |
| 600,000 clients | 2,062 MB | 2,856 MB | 5,810 MB | 10,182 MB | 625 MB | 1,448 MB | 5.7 % |
| 800,000 clients | 2,666 MB | 3,745 MB | 7,166 MB | 8,826 MB | 810 MB | 1,620 MB | 7.8 % |
| 1,000,000 clients | 3,344 MB | 4,634 MB | 8,561 MB | 7,431 MB | 995 MB | 1,812 MB | 9.8 % |
| 1,200,000 clients | 3,954 MB | 5,524 MB | 9,903 MB | 6,089 MB | 1,249 MB | 2,064 MB | 12.1 % |
| 1,400,000 clients | 4,562 MB | 6,412 MB | 11,349 MB | 4,643 MB | 1,430 MB | 2,265 MB | 14.5 % |
| 1,600,000 clients | 5,167 MB | 7,293 MB | 12,658 MB | 3,334 MB | 1,619 MB | 2,479 MB | 16.7 % |
| 1,800,000 clients | 5,713 MB | 8,136 MB | 13,907 MB | 2,085 MB | 1,800 MB | 2,649 MB | 19.5 % |
| 2,000,000 clients | 5,972 MB | 9,014 MB | 13,747 MB | 2,245 MB | 1,990 MB | 2,842 MB | 22.1 % |
| 2,022,026 clients | 5,890 MB | 9,112 MB | 13,687 MB | 2,305 MB | 2,010 MB | 2,874 MB | 21.7 % |
Server during this run API Maker 7.0 vCPU · Redis 0.5 vCPU · whole server busy 91 % · load generator busy 26 %
$ am_ws_load churn -c 100 -d 60 "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>" --register 'order-updates'
Running 60s test @ ws://<server>:38246
100 clients at a time : connect, CONNECTED, REGISTER, leave
┌─────────────┬─────────┬─────────┬──────────┬──────────┬─────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ TCP connect │ 0.18 ms │ 0.24 ms │ 0.44 ms │ 0.57 ms │ 0.26 ms │ 0.15 ms │ 10.94 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ CONNECTED │ 0.77 ms │ 2.1 ms │ 3.98 ms │ 4.43 ms │ 2.17 ms │ 0.85 ms │ 12.06 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ REGISTER │ 4.6 ms │ 7.04 ms │ 9.69 ms │ 10.42 ms │ 7.06 ms │ 1.29 ms │ 21.26 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ Left │ 5.52 ms │ 7.93 ms │ 11.06 ms │ 11.87 ms │ 8.03 ms │ 1.4 ms │ 23.38 ms │
└─────────────┴─────────┴─────────┴──────────┴──────────┴─────────┴─────────┴──────────┘
┌─────────────┬────────┬────────┬────────┬────────┬──────────┬────────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├─────────────┼────────┼────────┼────────┼────────┼──────────┼────────┼────────┤
│ Clients/Sec │ 11,238 │ 11,547 │ 12,468 │ 12,797 │ 12,424.8 │ 301.49 │ 11,238 │
└─────────────┴────────┴────────┴────────┴────────┴──────────┴────────┴────────┘
Time from the start of the connection to each step. Clients counted once per second.
# of samples: 60
745k clients let in in 60.06s, 0 refused or lost
Server during this run API Maker 0.3 vCPU · Redis 0.0 vCPU · whole server busy 4 % · load generator busy 3 %
$ autocannon -c 10 -d 60 -R 1000 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
10 connections
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────┤
│ Latency │ 0 ms │ 1 ms │ 1 ms │ 1 ms │ 0.87 ms │ 0.41 ms │ 8 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────┘
┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤
│ Req/Sec │ 999 │ 999 │ 1,000 │ 1,001 │ 1,000 │ 0.32 │ 999 │
├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤
│ Bytes/Sec │ 357 kB │ 357 kB │ 357 kB │ 357 kB │ 357 kB │ 81 B │ 357 kB │
└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘
Req/Bytes counts sampled once per second.
# of samples: 60
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 0 │
├────────────┼──────────────┤
│ 0.01 │ 0 │
├────────────┼──────────────┤
│ 0.1 │ 0 │
├────────────┼──────────────┤
│ 1 │ 0 │
├────────────┼──────────────┤
│ 2.5 │ 0 │
├────────────┼──────────────┤
│ 10 │ 0 │
├────────────┼──────────────┤
│ 25 │ 1 │
├────────────┼──────────────┤
│ 50 │ 1 │
├────────────┼──────────────┤
│ 75 │ 1 │
├────────────┼──────────────┤
│ 90 │ 1 │
├────────────┼──────────────┤
│ 97.5 │ 1 │
├────────────┼──────────────┤
│ 99 │ 1 │
├────────────┼──────────────┤
│ 99.9 │ 4 │
├────────────┼──────────────┤
│ 99.99 │ 7 │
├────────────┼──────────────┤
│ 99.999 │ 8 │
└────────────┴──────────────┘
61k requests in 60.03s, 21.4 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
10,000 clients subscribed, 64.05s
┌──────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Delivery │ 0.84 ms │ 1.01 ms │ 1.45 ms │ 1.72 ms │ 1.04 ms │ 0.17 ms │ 5.91 ms │
└──────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
┌───────────┬────┬──────┬───────┬───────┬───────┬────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼───────┼───────┼───────┼────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 1,000 │ 1,000 │ 951.8 │ 213.87 │ 0 │
└───────────┴────┴──────┴───────┴───────┴───────┴────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
60k notifications received in 64.05s (60,010 of 60,000 expected), 10.7 MB read
Server during this run API Maker 7.7 vCPU · Redis 0.3 vCPU · whole server busy 97 % · load generator busy 28 %
$ autocannon -c 100 -d 60 -w 10 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
100 connections
10 workers
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
│ Latency │ 2 ms │ 4 ms │ 5 ms │ 5 ms │ 3.54 ms │ 0.81 ms │ 251 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Req/Sec │ 22,895 │ 22,927 │ 24,831 │ 25,087 │ 24,591.47 │ 622.5 │ 22,887 │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Bytes/Sec │ 8.17 MB │ 8.18 MB │ 8.86 MB │ 8.95 MB │ 8.78 MB │ 222 kB │ 8.17 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 600
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 1 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 1 │
├────────────┼──────────────┤
│ 1 │ 2 │
├────────────┼──────────────┤
│ 2.5 │ 2 │
├────────────┼──────────────┤
│ 10 │ 3 │
├────────────┼──────────────┤
│ 25 │ 3 │
├────────────┼──────────────┤
│ 50 │ 4 │
├────────────┼──────────────┤
│ 75 │ 4 │
├────────────┼──────────────┤
│ 90 │ 4 │
├────────────┼──────────────┤
│ 97.5 │ 5 │
├────────────┼──────────────┤
│ 99 │ 5 │
├────────────┼──────────────┤
│ 99.9 │ 6 │
├────────────┼──────────────┤
│ 99.99 │ 11 │
├────────────┼──────────────┤
│ 99.999 │ 28 │
└────────────┴──────────────┘
1476k requests in 60.01s, 527 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
10,000 clients subscribed, 63.71s
┌──────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼──────────┤
│ Delivery │ 2.63 ms │ 3.88 ms │ 5.31 ms │ 5.66 ms │ 3.93 ms │ 2.62 ms │ 528.9 ms │
└──────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴──────────┘
┌───────────┬────┬──────┬────────┬────────┬──────────┬─────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼────────┼────────┼──────────┼─────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 24,801 │ 25,090 │ 23,602.0 │ 4602.67 │ 0 │
└───────────┴────┴──────┴────────┴────────┴──────────┴─────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
1.48M notifications received in 63.71s (1,475,533 of 1,475,433 expected), 264.2 MB read
Server during this run API Maker 6.9 vCPU · Redis 0.3 vCPU · whole server busy 89 % · load generator busy 32 %
$ autocannon -c 10 -d 60 -w 10 -l -m POST -b '{"name":"announcements","eventData":{"t":<now>,"type":"announcement","text":"Hello from API Maker"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
10 connections
10 workers
┌─────────┬───────┬───────┬───────┬───────┬──────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼─────────┼───────┤
│ Latency │ 30 ms │ 44 ms │ 59 ms │ 63 ms │ 44.45 ms │ 7.06 ms │ 77 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 206 │ 208 │ 221 │ 234 │ 222.02 │ 9.42 │ 206 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 74.2 kB │ 74.9 kB │ 79.6 kB │ 84.3 kB │ 79.9 kB │ 3.39 kB │ 74.2 kB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 600
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 11 │
├────────────┼──────────────┤
│ 0.01 │ 12 │
├────────────┼──────────────┤
│ 0.1 │ 21 │
├────────────┼──────────────┤
│ 1 │ 27 │
├────────────┼──────────────┤
│ 2.5 │ 30 │
├────────────┼──────────────┤
│ 10 │ 36 │
├────────────┼──────────────┤
│ 25 │ 40 │
├────────────┼──────────────┤
│ 50 │ 44 │
├────────────┼──────────────┤
│ 75 │ 49 │
├────────────┼──────────────┤
│ 90 │ 53 │
├────────────┼──────────────┤
│ 97.5 │ 59 │
├────────────┼──────────────┤
│ 99 │ 63 │
├────────────┼──────────────┤
│ 99.9 │ 71 │
├────────────┼──────────────┤
│ 99.99 │ 77 │
├────────────┼──────────────┤
│ 99.999 │ 77 │
└────────────┴──────────────┘
13k requests in 60.02s, 4.8 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
1,000 clients subscribed, 64.23s
┌──────────┬──────────┬──────────┬──────────┬─────────┬──────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼──────────┼──────────┼──────────┼─────────┼──────────┼─────────┼──────────┤
│ Delivery │ 20.86 ms │ 36.67 ms │ 52.93 ms │ 56.8 ms │ 36.73 ms │ 7.88 ms │ 77.63 ms │
└──────────┴──────────┴──────────┴──────────┴─────────┴──────────┴─────────┴──────────┘
┌───────────┬────┬──────┬─────────┬─────────┬───────────┬──────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼─────────┼─────────┼───────────┼──────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 220,766 │ 241,756 │ 212,054.7 │ 45274.23 │ 0 │
└───────────┴────┴──────┴─────────┴─────────┴───────────┴──────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
13.33M notifications received in 64.23s (13,330,000 of 13,321,000 expected), 2045.3 MB read
One server stops. With 1,516,519 clients connected, API Maker was restarted on the only server : every client was closed with 1001, waited one to five seconds, and came back to it.
They were then told again, each for itself, 1,000 notifications a second for 60 seconds : all 60,000 arrived, in 1.06 ms (p50) and 51.7 ms (p99). A client whose subscription had not come back with it would have been told nothing.
2 × 4 vCPU · 8 GB¶
| API servers | 2 × Dedicated 8 GB (g6-dedicated-4) : 4 dedicated vCPUs and 8 GB each, $144 a month in all. API Maker and its two sandboxes on each, attached to the data server by the installer (--secondary) |
| Data server | Dedicated 8 GB (g6-dedicated-4) : AMD EPYC 7642 48-Core Processor, 4 dedicated vCPUs (KVM). MongoDB 8.0.10 and Redis 7.0.5 of API Maker, in Docker ; its own API Maker stopped |
| CPU | AMD EPYC 7642 48-Core Processor |
| System | Ubuntu 24.04.4 LTS, Linux 6.8.0-134, Docker 29.9.0 |
| API Maker | 4.0.0 : release build of api_maker_be_rust 423a34c with the new WebSocket core of 14 October 2026 (opt-level 3, fat LTO), installed by its installer (https://apimaker.dev/v4/install.sh), no reverse proxy |
| Network limits | as the installer sets them, on every API server : 2,032,896 connections tracked (net.netfilter.nf_conntrack_max), 16,384 waiting to be accepted, 2,032,896 open files. The memory of all its TCP connections together : raised by API Maker as the clients came, to 1.9 GB at the most (net.ipv4.tcp_mem, 25 % of the memory ; Linux gives 9 %) |
| Load generator | Dedicated 32 GB (g6-dedicated-16) : AMD EPYC 7642 48-Core Processor, 16 dedicated vCPUs. The clients go to the API servers in turn, as a load balancer hands them out ; autocannon calls every API server at the same time |
| Network | A VLAN of the same data centre, round trip 0.32 ms (ping) |
| Date | 14 October 2026, 03:20 to 04:00 UTC |
The run stopped adding clients at 1,915,960 : the memory of the server api-1 was full. 1,072 MB of its 7,941 MB were still counted as available, and Linux had moved 683 MB to the swap since the clients began to connect, to make that room : 389 MB were left (the run stops when what is available, less what Linux moved to the swap since the clients began to connect, is under 5 % of the memory of the server).
| Clients connected | API Maker, all servers | Linux kernel, all servers | API servers, used | Least available on a server | Redis (its data) | Data server, used | CPU busy |
|---|---|---|---|---|---|---|---|
| No client (at rest) | 225 MB | 338 MB | 1,341 MB | 7,262 MB | 2 MB | 703 MB | 0.3 % |
| 10,000 clients | 285 MB | 479 MB | 2,313 MB | 6,775 MB | 12 MB | 714 MB | 1.7 % |
| 100,000 clients | 564 MB | 789 MB | 2,860 MB | 6,495 MB | 103 MB | 913 MB | 1.0 % |
| 200,000 clients | 875 MB | 1,236 MB | 3,532 MB | 6,156 MB | 204 MB | 1,040 MB | 2.1 % |
| 400,000 clients | 1,529 MB | 2,131 MB | 4,936 MB | 5,452 MB | 404 MB | 1,217 MB | 3.7 % |
| 600,000 clients | 2,178 MB | 3,028 MB | 6,332 MB | 4,730 MB | 625 MB | 1,452 MB | 5.7 % |
| 800,000 clients | 2,787 MB | 3,921 MB | 7,709 MB | 4,026 MB | 810 MB | 1,637 MB | 7.6 % |
| 1,000,000 clients | 3,459 MB | 4,813 MB | 9,150 MB | 3,289 MB | 996 MB | 1,828 MB | 9.8 % |
| 1,200,000 clients | 4,065 MB | 5,696 MB | 10,517 MB | 2,585 MB | 1,249 MB | 2,093 MB | 12.1 % |
| 1,400,000 clients | 4,672 MB | 6,545 MB | 11,926 MB | 1,877 MB | 1,430 MB | 2,270 MB | 13.8 % |
| 1,600,000 clients | 5,271 MB | 7,398 MB | 12,820 MB | 1,518 MB | 1,619 MB | 2,475 MB | 16.3 % |
| 1,800,000 clients | 5,774 MB | 8,263 MB | 13,795 MB | 1,027 MB | 1,800 MB | 2,729 MB | 19.1 % |
| 1,915,960 clients | 5,569 MB | 8,758 MB | 13,708 MB | 1,051 MB | 1,910 MB | 2,781 MB | 20.1 % |
Server during this run API Maker 7.3 vCPU · Redis 0.8 vCPU · whole server busy 94 % · load generator busy 28 %
$ am_ws_load churn -c 100 -d 60 "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>" --register 'order-updates'
Running 60s test @ ws://<server>:38246 and 1 more
100 clients at a time : connect, CONNECTED, REGISTER, leave
┌─────────────┬─────────┬─────────┬──────────┬──────────┬─────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ TCP connect │ 0.18 ms │ 0.24 ms │ 0.44 ms │ 0.53 ms │ 0.26 ms │ 0.17 ms │ 10.98 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ CONNECTED │ 0.64 ms │ 1.66 ms │ 4.39 ms │ 4.95 ms │ 1.95 ms │ 1.08 ms │ 19.33 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ REGISTER │ 2.53 ms │ 6.32 ms │ 13.06 ms │ 13.98 ms │ 6.89 ms │ 3.44 ms │ 45.54 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ Left │ 2.92 ms │ 7.32 ms │ 14.71 ms │ 15.74 ms │ 7.85 ms │ 3.93 ms │ 47.58 ms │
└─────────────┴─────────┴─────────┴──────────┴──────────┴─────────┴─────────┴──────────┘
┌─────────────┬────────┬────────┬────────┬────────┬──────────┬────────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├─────────────┼────────┼────────┼────────┼────────┼──────────┼────────┼────────┤
│ Clients/Sec │ 11,195 │ 11,486 │ 12,765 │ 13,261 │ 12,708.3 │ 364.16 │ 11,195 │
└─────────────┴────────┴────────┴────────┴────────┴──────────┴────────┴────────┘
Time from the start of the connection to each step. Clients counted once per second.
# of samples: 60
762k clients let in in 60.06s, 0 refused or lost
Server during this run API Maker 0.4 vCPU · Redis 0.1 vCPU · whole server busy 5 % · load generator busy 3 %
$ autocannon -c 5 -d 60 -R 500 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
5 connections
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 0 ms │ 1 ms │ 1 ms │ 2 ms │ 0.87 ms │ 0.47 ms │ 10 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤
│ Req/Sec │ 499 │ 499 │ 500 │ 501 │ 500 │ 0.37 │ 499 │
├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤
│ Bytes/Sec │ 178 kB │ 178 kB │ 179 kB │ 179 kB │ 178 kB │ 140 B │ 178 kB │
└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘
Req/Bytes counts sampled once per second.
# of samples: 60
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 0 │
├────────────┼──────────────┤
│ 0.01 │ 0 │
├────────────┼──────────────┤
│ 0.1 │ 0 │
├────────────┼──────────────┤
│ 1 │ 0 │
├────────────┼──────────────┤
│ 2.5 │ 0 │
├────────────┼──────────────┤
│ 10 │ 0 │
├────────────┼──────────────┤
│ 25 │ 1 │
├────────────┼──────────────┤
│ 50 │ 1 │
├────────────┼──────────────┤
│ 75 │ 1 │
├────────────┼──────────────┤
│ 90 │ 1 │
├────────────┼──────────────┤
│ 97.5 │ 1 │
├────────────┼──────────────┤
│ 99 │ 2 │
├────────────┼──────────────┤
│ 99.9 │ 5 │
├────────────┼──────────────┤
│ 99.99 │ 9 │
├────────────┼──────────────┤
│ 99.999 │ 10 │
└────────────┴──────────────┘
31k requests in 60.01s, 10.7 MB read
$ autocannon -c 5 -d 60 -R 500 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
5 connections
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────┤
│ Latency │ 0 ms │ 1 ms │ 1 ms │ 1 ms │ 0.96 ms │ 0.33 ms │ 9 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────┘
┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤
│ Req/Sec │ 497 │ 499 │ 500 │ 502 │ 500.07 │ 0.63 │ 497 │
├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤
│ Bytes/Sec │ 178 kB │ 178 kB │ 179 kB │ 179 kB │ 179 kB │ 231 B │ 177 kB │
└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘
Req/Bytes counts sampled once per second.
# of samples: 60
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 0 │
├────────────┼──────────────┤
│ 0.01 │ 0 │
├────────────┼──────────────┤
│ 0.1 │ 0 │
├────────────┼──────────────┤
│ 1 │ 0 │
├────────────┼──────────────┤
│ 2.5 │ 0 │
├────────────┼──────────────┤
│ 10 │ 1 │
├────────────┼──────────────┤
│ 25 │ 1 │
├────────────┼──────────────┤
│ 50 │ 1 │
├────────────┼──────────────┤
│ 75 │ 1 │
├────────────┼──────────────┤
│ 90 │ 1 │
├────────────┼──────────────┤
│ 97.5 │ 1 │
├────────────┼──────────────┤
│ 99 │ 1 │
├────────────┼──────────────┤
│ 99.9 │ 4 │
├────────────┼──────────────┤
│ 99.99 │ 9 │
├────────────┼──────────────┤
│ 99.999 │ 9 │
└────────────┴──────────────┘
31k requests in 60.01s, 10.7 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
10,000 clients subscribed, 64.03s
┌──────────┬─────────┬─────────┬─────────┬────────┬─────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼──────────┤
│ Delivery │ 0.83 ms │ 1.18 ms │ 1.63 ms │ 1.8 ms │ 1.19 ms │ 0.28 ms │ 10.04 ms │
└──────────┴─────────┴─────────┴─────────┴────────┴─────────┴─────────┴──────────┘
┌───────────┬────┬──────┬───────┬───────┬───────┬────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼───────┼───────┼───────┼────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 1,000 │ 1,000 │ 951.8 │ 213.59 │ 0 │
└───────────┴────┴──────┴───────┴───────┴───────┴────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
60k notifications received in 64.03s (60,014 of 60,004 expected), 10.7 MB read
Server during this run API Maker 7.8 vCPU · Redis 0.6 vCPU · whole server busy 99 % · load generator busy 46 %
$ autocannon -c 50 -d 60 -w 10 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
50 connections
10 workers
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
│ Latency │ 2 ms │ 3 ms │ 5 ms │ 5 ms │ 2.96 ms │ 1.47 ms │ 531 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Req/Sec │ 10,431 │ 13,639 │ 14,215 │ 15,439 │ 14,278.94 │ 663.06 │ 10,426 │
├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼────────┼─────────┤
│ Bytes/Sec │ 3.72 MB │ 4.87 MB │ 5.07 MB │ 5.51 MB │ 5.1 MB │ 237 kB │ 3.72 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 600
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 0 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 1 │
├────────────┼──────────────┤
│ 1 │ 1 │
├────────────┼──────────────┤
│ 2.5 │ 2 │
├────────────┼──────────────┤
│ 10 │ 2 │
├────────────┼──────────────┤
│ 25 │ 2 │
├────────────┼──────────────┤
│ 50 │ 3 │
├────────────┼──────────────┤
│ 75 │ 3 │
├────────────┼──────────────┤
│ 90 │ 4 │
├────────────┼──────────────┤
│ 97.5 │ 5 │
├────────────┼──────────────┤
│ 99 │ 5 │
├────────────┼──────────────┤
│ 99.9 │ 9 │
├────────────┼──────────────┤
│ 99.99 │ 16 │
├────────────┼──────────────┤
│ 99.999 │ 209 │
└────────────┴──────────────┘
857k requests in 60.02s, 306 MB read
$ autocannon -c 50 -d 60 -w 10 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
50 connections
10 workers
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼────────┤
│ Latency │ 2 ms │ 4 ms │ 6 ms │ 6 ms │ 3.96 ms │ 1.31 ms │ 525 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴────────┘
┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼────────┼─────────┤
│ Req/Sec │ 10,527 │ 10,559 │ 10,983 │ 11,759 │ 11,110 │ 330.41 │ 10,525 │
├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 3.76 MB │ 3.77 MB │ 3.92 MB │ 4.2 MB │ 3.97 MB │ 118 kB │ 3.76 MB │
└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 600
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 1 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 1 │
├────────────┼──────────────┤
│ 1 │ 2 │
├────────────┼──────────────┤
│ 2.5 │ 2 │
├────────────┼──────────────┤
│ 10 │ 3 │
├────────────┼──────────────┤
│ 25 │ 3 │
├────────────┼──────────────┤
│ 50 │ 4 │
├────────────┼──────────────┤
│ 75 │ 5 │
├────────────┼──────────────┤
│ 90 │ 5 │
├────────────┼──────────────┤
│ 97.5 │ 6 │
├────────────┼──────────────┤
│ 99 │ 6 │
├────────────┼──────────────┤
│ 99.9 │ 7 │
├────────────┼──────────────┤
│ 99.99 │ 20 │
├────────────┼──────────────┤
│ 99.999 │ 55 │
└────────────┴──────────────┘
667k requests in 60.02s, 238 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
10,000 clients subscribed, 64.00s
┌──────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────────┤
│ Delivery │ 2.12 ms │ 3.75 ms │ 5.85 ms │ 6.31 ms │ 3.95 ms │ 7.32 ms │ 529.92 ms │
└──────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────────┘
┌───────────┬────┬──────┬────────┬────────┬──────────┬─────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼────────┼────────┼──────────┼─────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 25,431 │ 26,179 │ 24,446.4 │ 4538.65 │ 0 │
└───────────┴────┴──────┴────────┴────────┴──────────┴─────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
1.52M notifications received in 64.00s (1,523,398 of 1,523,298 expected), 272.8 MB read
Server during this run API Maker 7.2 vCPU · Redis 0.6 vCPU · whole server busy 91 % · load generator busy 32 %
Told for 66.8 s The calls came faster than the server tells its clients : it went on telling them for 5.5 s after the calls stopped, and API Maker held up to 594 MB while they waited. Notifications per second counts the whole of that time ; the delivery times are the times of notifications which waited their turn.
$ autocannon -c 5 -d 60 -w 5 -l -m POST -b '{"name":"announcements","eventData":{"t":<now>,"type":"announcement","text":"Hello from API Maker"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
5 connections
5 workers
┌─────────┬───────┬───────┬───────┬───────┬──────────┬─────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼─────────┼────────┤
│ Latency │ 21 ms │ 33 ms │ 48 ms │ 52 ms │ 33.22 ms │ 7.25 ms │ 103 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴─────────┴────────┘
┌───────────┬─────────┬───────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼───────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 137 │ 139 │ 146 │ 159 │ 147.87 │ 6.08 │ 137 │
├───────────┼─────────┼───────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 49.3 kB │ 50 kB │ 52.6 kB │ 57.2 kB │ 53.2 kB │ 2.19 kB │ 49.3 kB │
└───────────┴─────────┴───────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 300
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 8 │
├────────────┼──────────────┤
│ 0.01 │ 8 │
├────────────┼──────────────┤
│ 0.1 │ 14 │
├────────────┼──────────────┤
│ 1 │ 19 │
├────────────┼──────────────┤
│ 2.5 │ 21 │
├────────────┼──────────────┤
│ 10 │ 24 │
├────────────┼──────────────┤
│ 25 │ 28 │
├────────────┼──────────────┤
│ 50 │ 33 │
├────────────┼──────────────┤
│ 75 │ 38 │
├────────────┼──────────────┤
│ 90 │ 43 │
├────────────┼──────────────┤
│ 97.5 │ 48 │
├────────────┼──────────────┤
│ 99 │ 52 │
├────────────┼──────────────┤
│ 99.9 │ 65 │
├────────────┼──────────────┤
│ 99.99 │ 103 │
├────────────┼──────────────┤
│ 99.999 │ 103 │
└────────────┴──────────────┘
9k requests in 60.01s, 3.19 MB read
$ autocannon -c 5 -d 60 -w 5 -l -m POST -b '{"name":"announcements","eventData":{"t":<now>,"type":"announcement","text":"Hello from API Maker"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
5 connections
5 workers
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼───────┤
│ Latency │ 28 ms │ 46 ms │ 69 ms │ 73 ms │ 46.24 ms │ 10.24 ms │ 98 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴───────┘
┌───────────┬─────────┬───────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼───────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 98 │ 100 │ 106 │ 115 │ 106.72 │ 4.2 │ 98 │
├───────────┼─────────┼───────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 35.3 kB │ 36 kB │ 38.2 kB │ 41.4 kB │ 38.4 kB │ 1.51 kB │ 35.3 kB │
└───────────┴─────────┴───────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 300
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 19 │
├────────────┼──────────────┤
│ 0.01 │ 19 │
├────────────┼──────────────┤
│ 0.1 │ 20 │
├────────────┼──────────────┤
│ 1 │ 25 │
├────────────┼──────────────┤
│ 2.5 │ 28 │
├────────────┼──────────────┤
│ 10 │ 34 │
├────────────┼──────────────┤
│ 25 │ 39 │
├────────────┼──────────────┤
│ 50 │ 46 │
├────────────┼──────────────┤
│ 75 │ 53 │
├────────────┼──────────────┤
│ 90 │ 60 │
├────────────┼──────────────┤
│ 97.5 │ 69 │
├────────────┼──────────────┤
│ 99 │ 73 │
├────────────┼──────────────┤
│ 99.9 │ 84 │
├────────────┼──────────────┤
│ 99.99 │ 98 │
├────────────┼──────────────┤
│ 99.999 │ 98 │
└────────────┴──────────────┘
6k requests in 60.02s, 2.31 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
1,000 clients subscribed, 69.78s
┌──────────┬──────────┬─────────┬────────────┬────────────┬────────────┬────────────┬────────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼──────────┼─────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│ Delivery │ 14.95 ms │ 39.3 ms │ 6590.46 ms │ 6701.06 ms │ 1295.23 ms │ 2293.94 ms │ 6901.76 ms │
└──────────┴──────────┴─────────┴────────────┴────────────┴────────────┴────────────┴────────────┘
┌───────────┬────┬──────┬─────────┬─────────┬───────────┬──────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼─────────┼─────────┼───────────┼──────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 244,237 │ 256,512 │ 223,239.3 │ 62807.61 │ 0 │
└───────────┴────┴──────┴─────────┴─────────┴───────────┴──────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 70
15.29M notifications received in 69.78s (15,285,000 of 15,275,000 expected), 2345.2 MB read
One server stops. Nobody runs a cluster full : the servers which are left need room for the clients of the one which stops. So the newest clients left first, down to 766,384 : what 1 of the 2 servers hold when each of them is 80 % full. API Maker was then stopped on one of the servers (systemctl stop api_maker_be) and stayed down : its clients were closed with 1001, waited one to five seconds, and connected to the servers which were left.
They were then told again, each for itself, 1,000 notifications a second for 60 seconds : all 60,002 arrived, in 1.85 ms (p50) and 144 ms (p99). A client whose subscription had not come back with it would have been told nothing.
4 × 2 vCPU · 4 GB¶
| API servers | 4 × Dedicated 4 GB (g6-dedicated-2) : 2 dedicated vCPUs and 4 GB each, $144 a month in all. API Maker and its two sandboxes on each, attached to the data server by the installer (--secondary) |
| Data server | Dedicated 8 GB (g6-dedicated-4) : AMD EPYC 7642 48-Core Processor, 4 dedicated vCPUs (KVM). MongoDB 8.0.10 and Redis 7.0.5 of API Maker, in Docker ; its own API Maker stopped |
| CPU | AMD EPYC 7642 48-Core Processor · AMD EPYC 7713 64-Core Processor |
| System | Ubuntu 24.04.4 LTS, Linux 6.8.0-134, Docker 29.9.0 |
| API Maker | 4.0.0 : release build of api_maker_be_rust 423a34c with the new WebSocket core of 14 October 2026 (opt-level 3, fat LTO), installed by its installer (https://apimaker.dev/v4/install.sh), no reverse proxy |
| Network limits | as the installer sets them, on every API server : 1,002,240 connections tracked (net.netfilter.nf_conntrack_max), 16,384 waiting to be accepted, 1,048,576 open files. The memory of all its TCP connections together : raised by API Maker as the clients came, to 979 MB at the most (net.ipv4.tcp_mem, 25 % of the memory ; Linux gives 9 %) |
| Load generator | Dedicated 32 GB (g6-dedicated-16) : AMD EPYC 7713 64-Core Processor, 16 dedicated vCPUs. The clients go to the API servers in turn, as a load balancer hands them out ; autocannon calls every API server at the same time |
| Network | A VLAN of the same data centre, round trip 0.51 ms (ping) |
| Date | 14 October 2026, 03:26 to 04:05 UTC |
The run stopped adding clients at 1,619,372 : the memory of the server api-2 was full. 456 MB of its 3,916 MB were still counted as available, and Linux had moved 266 MB to the swap since the clients began to connect, to make that room : 190 MB were left (the run stops when what is available, less what Linux moved to the swap since the clients began to connect, is under 5 % of the memory of the server).
| Clients connected | API Maker, all servers | Linux kernel, all servers | API servers, used | Least available on a server | Redis (its data) | Data server, used | CPU busy |
|---|---|---|---|---|---|---|---|
| No client (at rest) | 417 MB | 630 MB | 2,478 MB | 3,288 MB | 2 MB | 721 MB | 0.7 % |
| 10,000 clients | 491 MB | 749 MB | 3,088 MB | 3,115 MB | 12 MB | 718 MB | 1.6 % |
| 100,000 clients | 790 MB | 1,079 MB | 3,710 MB | 2,954 MB | 103 MB | 906 MB | 1.2 % |
| 200,000 clients | 1,091 MB | 1,527 MB | 4,440 MB | 2,775 MB | 204 MB | 1,023 MB | 2.1 % |
| 400,000 clients | 1,734 MB | 2,421 MB | 5,911 MB | 2,392 MB | 404 MB | 1,204 MB | 4.4 % |
| 600,000 clients | 2,388 MB | 3,305 MB | 7,358 MB | 2,016 MB | 625 MB | 1,459 MB | 6.1 % |
| 800,000 clients | 2,995 MB | 4,155 MB | 8,723 MB | 1,665 MB | 810 MB | 1,628 MB | 7.8 % |
| 1,000,000 clients | 3,673 MB | 5,006 MB | 10,180 MB | 1,301 MB | 996 MB | 1,815 MB | 9.6 % |
| 1,200,000 clients | 4,280 MB | 5,864 MB | 11,512 MB | 941 MB | 1,249 MB | 2,077 MB | 11.8 % |
| 1,400,000 clients | 4,790 MB | 6,713 MB | 12,967 MB | 567 MB | 1,430 MB | 2,265 MB | 14.9 % |
| 1,600,000 clients | 5,079 MB | 7,560 MB | 13,681 MB | 455 MB | 1,619 MB | 2,450 MB | 16.3 % |
| 1,619,372 clients | 5,077 MB | 7,645 MB | 13,764 MB | 454 MB | 1,638 MB | 2,490 MB | 16.2 % |
Server during this run API Maker 6.6 vCPU · Redis 1.0 vCPU · whole server busy 88 % · load generator busy 29 %
$ am_ws_load churn -c 100 -d 60 "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>" --register 'order-updates'
Running 60s test @ ws://<server>:38246 and 3 more
100 clients at a time : connect, CONNECTED, REGISTER, leave
┌─────────────┬─────────┬─────────┬──────────┬──────────┬─────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ TCP connect │ 0.22 ms │ 0.34 ms │ 0.65 ms │ 0.88 ms │ 0.38 ms │ 0.31 ms │ 20.11 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ CONNECTED │ 0.75 ms │ 1.12 ms │ 7.89 ms │ 8.79 ms │ 2.22 ms │ 2.19 ms │ 30.96 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ REGISTER │ 2.63 ms │ 3.91 ms │ 28.11 ms │ 30.19 ms │ 8.45 ms │ 8.48 ms │ 53.5 ms │
├─────────────┼─────────┼─────────┼──────────┼──────────┼─────────┼─────────┼──────────┤
│ Left │ 3.04 ms │ 4.39 ms │ 31.98 ms │ 34.34 ms │ 9.68 ms │ 9.77 ms │ 55.1 ms │
└─────────────┴─────────┴─────────┴──────────┴──────────┴─────────┴─────────┴──────────┘
┌─────────────┬───────┬───────┬────────┬────────┬──────────┬────────┬───────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├─────────────┼───────┼───────┼────────┼────────┼──────────┼────────┼───────┤
│ Clients/Sec │ 9,718 │ 9,725 │ 10,283 │ 11,091 │ 10,307.6 │ 350.49 │ 9,718 │
└─────────────┴───────┴───────┴────────┴────────┴──────────┴────────┴───────┘
Time from the start of the connection to each step. Clients counted once per second.
# of samples: 60
618k clients let in in 60.06s, 0 refused or lost
Server during this run API Maker 0.4 vCPU · Redis 0.1 vCPU · whole server busy 5 % · load generator busy 4 %
$ autocannon -c 2 -d 60 -R 250 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
┌─────────┬──────┬──────┬───────┬──────┬──────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼──────┼─────────┼───────┤
│ Latency │ 0 ms │ 1 ms │ 2 ms │ 2 ms │ 1 ms │ 0.47 ms │ 12 ms │
└─────────┴──────┴──────┴───────┴──────┴──────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤
│ Req/Sec │ 250 │ 250 │ 250 │ 250 │ 250 │ 0 │ 250 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤
│ Bytes/Sec │ 89.3 kB │ 89.3 kB │ 89.3 kB │ 89.3 kB │ 89.2 kB │ 0 B │ 89.3 kB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 60
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 0 │
├────────────┼──────────────┤
│ 0.01 │ 0 │
├────────────┼──────────────┤
│ 0.1 │ 0 │
├────────────┼──────────────┤
│ 1 │ 0 │
├────────────┼──────────────┤
│ 2.5 │ 0 │
├────────────┼──────────────┤
│ 10 │ 1 │
├────────────┼──────────────┤
│ 25 │ 1 │
├────────────┼──────────────┤
│ 50 │ 1 │
├────────────┼──────────────┤
│ 75 │ 1 │
├────────────┼──────────────┤
│ 90 │ 1 │
├────────────┼──────────────┤
│ 97.5 │ 2 │
├────────────┼──────────────┤
│ 99 │ 2 │
├────────────┼──────────────┤
│ 99.9 │ 7 │
├────────────┼──────────────┤
│ 99.99 │ 11 │
├────────────┼──────────────┤
│ 99.999 │ 12 │
└────────────┴──────────────┘
15k requests in 60.03s, 5.36 MB read
$ autocannon -c 2 -d 60 -R 250 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 1 ms │ 1 ms │ 2 ms │ 3 ms │ 1.34 ms │ 0.64 ms │ 12 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Req/Sec │ 249 │ 250 │ 250 │ 250 │ 250 │ 0.19 │ 249 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────┼─────────┤
│ Bytes/Sec │ 88.9 kB │ 89.3 kB │ 89.3 kB │ 89.3 kB │ 89.2 kB │ 70.1 B │ 88.9 kB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 60
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 1 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 1 │
├────────────┼──────────────┤
│ 1 │ 1 │
├────────────┼──────────────┤
│ 2.5 │ 1 │
├────────────┼──────────────┤
│ 10 │ 1 │
├────────────┼──────────────┤
│ 25 │ 1 │
├────────────┼──────────────┤
│ 50 │ 1 │
├────────────┼──────────────┤
│ 75 │ 2 │
├────────────┼──────────────┤
│ 90 │ 2 │
├────────────┼──────────────┤
│ 97.5 │ 2 │
├────────────┼──────────────┤
│ 99 │ 3 │
├────────────┼──────────────┤
│ 99.9 │ 9 │
├────────────┼──────────────┤
│ 99.99 │ 11 │
├────────────┼──────────────┤
│ 99.999 │ 12 │
└────────────┴──────────────┘
15k requests in 60.03s, 5.36 MB read
$ autocannon -c 2 -d 60 -R 250 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────┤
│ Latency │ 0 ms │ 1 ms │ 1 ms │ 2 ms │ 0.99 ms │ 0.36 ms │ 8 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤
│ Req/Sec │ 250 │ 250 │ 250 │ 250 │ 250 │ 0 │ 250 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤
│ Bytes/Sec │ 89.3 kB │ 89.3 kB │ 89.3 kB │ 89.3 kB │ 89.2 kB │ 0 B │ 89.3 kB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 60
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 0 │
├────────────┼──────────────┤
│ 0.01 │ 0 │
├────────────┼──────────────┤
│ 0.1 │ 0 │
├────────────┼──────────────┤
│ 1 │ 0 │
├────────────┼──────────────┤
│ 2.5 │ 0 │
├────────────┼──────────────┤
│ 10 │ 1 │
├────────────┼──────────────┤
│ 25 │ 1 │
├────────────┼──────────────┤
│ 50 │ 1 │
├────────────┼──────────────┤
│ 75 │ 1 │
├────────────┼──────────────┤
│ 90 │ 1 │
├────────────┼──────────────┤
│ 97.5 │ 1 │
├────────────┼──────────────┤
│ 99 │ 2 │
├────────────┼──────────────┤
│ 99.9 │ 5 │
├────────────┼──────────────┤
│ 99.99 │ 8 │
├────────────┼──────────────┤
│ 99.999 │ 8 │
└────────────┴──────────────┘
15k requests in 60.03s, 5.36 MB read
$ autocannon -c 2 -d 60 -R 250 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────┤
│ Latency │ 0 ms │ 1 ms │ 1 ms │ 2 ms │ 1.01 ms │ 0.33 ms │ 8 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤
│ Req/Sec │ 250 │ 250 │ 250 │ 250 │ 250 │ 0 │ 250 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤
│ Bytes/Sec │ 89.3 kB │ 89.3 kB │ 89.3 kB │ 89.3 kB │ 89.2 kB │ 0 B │ 89.3 kB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 60
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 0 │
├────────────┼──────────────┤
│ 0.01 │ 0 │
├────────────┼──────────────┤
│ 0.1 │ 0 │
├────────────┼──────────────┤
│ 1 │ 0 │
├────────────┼──────────────┤
│ 2.5 │ 0 │
├────────────┼──────────────┤
│ 10 │ 1 │
├────────────┼──────────────┤
│ 25 │ 1 │
├────────────┼──────────────┤
│ 50 │ 1 │
├────────────┼──────────────┤
│ 75 │ 1 │
├────────────┼──────────────┤
│ 90 │ 1 │
├────────────┼──────────────┤
│ 97.5 │ 1 │
├────────────┼──────────────┤
│ 99 │ 2 │
├────────────┼──────────────┤
│ 99.9 │ 5 │
├────────────┼──────────────┤
│ 99.99 │ 8 │
├────────────┼──────────────┤
│ 99.999 │ 8 │
└────────────┴──────────────┘
15k requests in 60.03s, 5.36 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
10,000 clients subscribed, 64.06s
┌──────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼──────────┤
│ Delivery │ 0.91 ms │ 1.37 ms │ 2.25 ms │ 2.51 ms │ 1.44 ms │ 0.42 ms │ 12.21 ms │
└──────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴──────────┘
┌───────────┬────┬──────┬───────┬───────┬───────┬────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼───────┼───────┼───────┼────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 1,000 │ 1,000 │ 951.7 │ 214.37 │ 0 │
└───────────┴────┴──────┴───────┴───────┴───────┴────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
60k notifications received in 64.06s (60,003 of 60,000 expected), 10.7 MB read
Server during this run API Maker 7.8 vCPU · Redis 0.9 vCPU · whole server busy 99 % · load generator busy 64 %
$ autocannon -c 25 -d 60 -w 5 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
25 connections
5 workers
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 2 ms │ 3 ms │ 4 ms │ 5 ms │ 3.17 ms │ 0.81 ms │ 21 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬────────┬─────────┬──────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼────────┼─────────┼──────────┼─────────┼─────────┤
│ Req/Sec │ 5,751 │ 6,175 │ 6,719 │ 7,035 │ 6,726.14 │ 213.6 │ 5,750 │
├───────────┼─────────┼─────────┼────────┼─────────┼──────────┼─────────┼─────────┤
│ Bytes/Sec │ 2.05 MB │ 2.21 MB │ 2.4 MB │ 2.51 MB │ 2.4 MB │ 76.2 kB │ 2.05 MB │
└───────────┴─────────┴─────────┴────────┴─────────┴──────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 300
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 1 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 1 │
├────────────┼──────────────┤
│ 1 │ 2 │
├────────────┼──────────────┤
│ 2.5 │ 2 │
├────────────┼──────────────┤
│ 10 │ 2 │
├────────────┼──────────────┤
│ 25 │ 3 │
├────────────┼──────────────┤
│ 50 │ 3 │
├────────────┼──────────────┤
│ 75 │ 4 │
├────────────┼──────────────┤
│ 90 │ 4 │
├────────────┼──────────────┤
│ 97.5 │ 4 │
├────────────┼──────────────┤
│ 99 │ 5 │
├────────────┼──────────────┤
│ 99.9 │ 9 │
├────────────┼──────────────┤
│ 99.99 │ 14 │
├────────────┼──────────────┤
│ 99.999 │ 18 │
└────────────┴──────────────┘
404k requests in 60.02s, 144 MB read
$ autocannon -c 25 -d 60 -w 5 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
25 connections
5 workers
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 3 ms │ 5 ms │ 7 ms │ 8 ms │ 4.64 ms │ 1.26 ms │ 27 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Req/Sec │ 3,993 │ 4,407 │ 4,807 │ 5,259 │ 4,823.25 │ 228.72 │ 3,993 │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Bytes/Sec │ 1.43 MB │ 1.57 MB │ 1.72 MB │ 1.88 MB │ 1.72 MB │ 81.7 kB │ 1.43 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 300
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 1 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 2 │
├────────────┼──────────────┤
│ 1 │ 2 │
├────────────┼──────────────┤
│ 2.5 │ 3 │
├────────────┼──────────────┤
│ 10 │ 3 │
├────────────┼──────────────┤
│ 25 │ 4 │
├────────────┼──────────────┤
│ 50 │ 5 │
├────────────┼──────────────┤
│ 75 │ 5 │
├────────────┼──────────────┤
│ 90 │ 6 │
├────────────┼──────────────┤
│ 97.5 │ 7 │
├────────────┼──────────────┤
│ 99 │ 8 │
├────────────┼──────────────┤
│ 99.9 │ 14 │
├────────────┼──────────────┤
│ 99.99 │ 21 │
├────────────┼──────────────┤
│ 99.999 │ 26 │
└────────────┴──────────────┘
289k requests in 60.02s, 103 MB read
$ autocannon -c 25 -d 60 -w 5 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
25 connections
5 workers
┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 2 ms │ 3 ms │ 4 ms │ 4 ms │ 2.91 ms │ 0.72 ms │ 22 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Req/Sec │ 6,267 │ 6,859 │ 7,231 │ 7,443 │ 7,218.94 │ 198.19 │ 6,265 │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Bytes/Sec │ 2.24 MB │ 2.45 MB │ 2.58 MB │ 2.66 MB │ 2.58 MB │ 70.7 kB │ 2.24 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 300
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 1 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 1 │
├────────────┼──────────────┤
│ 1 │ 1 │
├────────────┼──────────────┤
│ 2.5 │ 2 │
├────────────┼──────────────┤
│ 10 │ 2 │
├────────────┼──────────────┤
│ 25 │ 2 │
├────────────┼──────────────┤
│ 50 │ 3 │
├────────────┼──────────────┤
│ 75 │ 3 │
├────────────┼──────────────┤
│ 90 │ 4 │
├────────────┼──────────────┤
│ 97.5 │ 4 │
├────────────┼──────────────┤
│ 99 │ 4 │
├────────────┼──────────────┤
│ 99.9 │ 7 │
├────────────┼──────────────┤
│ 99.99 │ 12 │
├────────────┼──────────────┤
│ 99.999 │ 19 │
└────────────┴──────────────┘
433k requests in 60.02s, 155 MB read
$ autocannon -c 25 -d 60 -w 5 -l -m POST -b '{"name":"order-updates","eventData":{"recipientId":"<id of a client>","t":<now>,"type":"order-updated","order_no":1001,"status":"SHIPPED"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
25 connections
5 workers
┌─────────┬──────┬──────┬───────┬──────┬────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼──────┼──────┼───────┼──────┼────────┼─────────┼───────┤
│ Latency │ 2 ms │ 3 ms │ 4 ms │ 4 ms │ 2.9 ms │ 0.71 ms │ 27 ms │
└─────────┴──────┴──────┴───────┴──────┴────────┴─────────┴───────┘
┌───────────┬────────┬─────────┬─────────┬─────────┬──────────┬─────────┬────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────────┼─────────┼─────────┼─────────┼──────────┼─────────┼────────┤
│ Req/Sec │ 6,451 │ 6,987 │ 7,247 │ 7,327 │ 7,228.94 │ 122.08 │ 6,449 │
├───────────┼────────┼─────────┼─────────┼─────────┼──────────┼─────────┼────────┤
│ Bytes/Sec │ 2.3 MB │ 2.49 MB │ 2.59 MB │ 2.62 MB │ 2.58 MB │ 43.5 kB │ 2.3 MB │
└───────────┴────────┴─────────┴─────────┴─────────┴──────────┴─────────┴────────┘
Req/Bytes counts sampled once per second.
# of samples: 300
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 1 │
├────────────┼──────────────┤
│ 0.01 │ 1 │
├────────────┼──────────────┤
│ 0.1 │ 1 │
├────────────┼──────────────┤
│ 1 │ 2 │
├────────────┼──────────────┤
│ 2.5 │ 2 │
├────────────┼──────────────┤
│ 10 │ 2 │
├────────────┼──────────────┤
│ 25 │ 2 │
├────────────┼──────────────┤
│ 50 │ 3 │
├────────────┼──────────────┤
│ 75 │ 3 │
├────────────┼──────────────┤
│ 90 │ 4 │
├────────────┼──────────────┤
│ 97.5 │ 4 │
├────────────┼──────────────┤
│ 99 │ 4 │
├────────────┼──────────────┤
│ 99.9 │ 7 │
├────────────┼──────────────┤
│ 99.99 │ 12 │
├────────────┼──────────────┤
│ 99.999 │ 22 │
└────────────┴──────────────┘
434k requests in 60.02s, 155 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
10,000 clients subscribed, 64.01s
┌──────────┬─────────┬────────┬─────────┬─────────┬─────────┬─────────┬──────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┼──────────┤
│ Delivery │ 2.17 ms │ 3.7 ms │ 5.94 ms │ 6.54 ms │ 3.77 ms │ 0.99 ms │ 26.05 ms │
└──────────┴─────────┴────────┴─────────┴─────────┴─────────┴─────────┴──────────┘
┌───────────┬────┬──────┬────────┬────────┬──────────┬─────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼────────┼────────┼──────────┼─────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 25,989 │ 26,829 │ 25,009.0 │ 4691.65 │ 0 │
└───────────┴────┴──────┴────────┴────────┴──────────┴─────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
1.56M notifications received in 64.01s (1,559,812 of 1,559,712 expected), 279.3 MB read
Server during this run API Maker 7.8 vCPU · Redis 0.8 vCPU · whole server busy 98 % · load generator busy 38 %
$ autocannon -c 2 -d 60 -w 2 -l -m POST -b '{"name":"announcements","eventData":{"t":<now>,"type":"announcement","text":"Hello from API Maker"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
2 workers
┌─────────┬───────┬───────┬───────┬───────┬──────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼─────────┼───────┤
│ Latency │ 17 ms │ 27 ms │ 40 ms │ 44 ms │ 27.21 ms │ 5.82 ms │ 54 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┤
│ Req/Sec │ 64 │ 65 │ 72 │ 80 │ 71.91 │ 4.11 │ 64 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┤
│ Bytes/Sec │ 23.1 kB │ 23.4 kB │ 25.9 kB │ 28.8 kB │ 25.9 kB │ 1.48 kB │ 23 kB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┘
Req/Bytes counts sampled once per second.
# of samples: 120
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 11 │
├────────────┼──────────────┤
│ 0.01 │ 11 │
├────────────┼──────────────┤
│ 0.1 │ 13 │
├────────────┼──────────────┤
│ 1 │ 16 │
├────────────┼──────────────┤
│ 2.5 │ 17 │
├────────────┼──────────────┤
│ 10 │ 20 │
├────────────┼──────────────┤
│ 25 │ 23 │
├────────────┼──────────────┤
│ 50 │ 27 │
├────────────┼──────────────┤
│ 75 │ 31 │
├────────────┼──────────────┤
│ 90 │ 35 │
├────────────┼──────────────┤
│ 97.5 │ 40 │
├────────────┼──────────────┤
│ 99 │ 44 │
├────────────┼──────────────┤
│ 99.9 │ 51 │
├────────────┼──────────────┤
│ 99.99 │ 54 │
├────────────┼──────────────┤
│ 99.999 │ 54 │
└────────────┴──────────────┘
4k requests in 60.02s, 1.55 MB read
$ autocannon -c 2 -d 60 -w 2 -l -m POST -b '{"name":"announcements","eventData":{"t":<now>,"type":"announcement","text":"Hello from API Maker"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
2 workers
┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼────────┤
│ Latency │ 26 ms │ 48 ms │ 82 ms │ 87 ms │ 49.24 ms │ 14.07 ms │ 105 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴────────┘
┌───────────┬─────────┬─────────┬───────┬───────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 33 │ 34 │ 39 │ 50 │ 40.12 │ 3.91 │ 33 │
├───────────┼─────────┼─────────┼───────┼───────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 11.9 kB │ 12.2 kB │ 14 kB │ 18 kB │ 14.4 kB │ 1.41 kB │ 11.9 kB │
└───────────┴─────────┴─────────┴───────┴───────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 120
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 17 │
├────────────┼──────────────┤
│ 0.01 │ 17 │
├────────────┼──────────────┤
│ 0.1 │ 18 │
├────────────┼──────────────┤
│ 1 │ 22 │
├────────────┼──────────────┤
│ 2.5 │ 26 │
├────────────┼──────────────┤
│ 10 │ 32 │
├────────────┼──────────────┤
│ 25 │ 39 │
├────────────┼──────────────┤
│ 50 │ 48 │
├────────────┼──────────────┤
│ 75 │ 58 │
├────────────┼──────────────┤
│ 90 │ 68 │
├────────────┼──────────────┤
│ 97.5 │ 82 │
├────────────┼──────────────┤
│ 99 │ 87 │
├────────────┼──────────────┤
│ 99.9 │ 103 │
├────────────┼──────────────┤
│ 99.99 │ 105 │
├────────────┼──────────────┤
│ 99.999 │ 105 │
└────────────┴──────────────┘
2k requests in 60.03s, 867 kB read
$ autocannon -c 2 -d 60 -w 2 -l -m POST -b '{"name":"announcements","eventData":{"t":<now>,"type":"announcement","text":"Hello from API Maker"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
2 workers
┌─────────┬───────┬───────┬───────┬───────┬──────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼─────────┼───────┤
│ Latency │ 16 ms │ 24 ms │ 36 ms │ 38 ms │ 24.39 ms │ 4.96 ms │ 50 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬───────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 71 │ 73 │ 80 │ 86 │ 80.09 │ 3.42 │ 71 │
├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 25.6 kB │ 26.3 kB │ 28.8 kB │ 31 kB │ 28.8 kB │ 1.23 kB │ 25.6 kB │
└───────────┴─────────┴─────────┴─────────┴───────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 120
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 10 │
├────────────┼──────────────┤
│ 0.01 │ 10 │
├────────────┼──────────────┤
│ 0.1 │ 12 │
├────────────┼──────────────┤
│ 1 │ 15 │
├────────────┼──────────────┤
│ 2.5 │ 16 │
├────────────┼──────────────┤
│ 10 │ 18 │
├────────────┼──────────────┤
│ 25 │ 21 │
├────────────┼──────────────┤
│ 50 │ 24 │
├────────────┼──────────────┤
│ 75 │ 27 │
├────────────┼──────────────┤
│ 90 │ 31 │
├────────────┼──────────────┤
│ 97.5 │ 36 │
├────────────┼──────────────┤
│ 99 │ 38 │
├────────────┼──────────────┤
│ 99.9 │ 46 │
├────────────┼──────────────┤
│ 99.99 │ 50 │
├────────────┼──────────────┤
│ 99.999 │ 50 │
└────────────┴──────────────┘
5k requests in 60.03s, 1.73 MB read
$ autocannon -c 2 -d 60 -w 2 -l -m POST -b '{"name":"announcements","eventData":{"t":<now>,"type":"announcement","text":"Hello from API Maker"}}' -H "x-am-authorization: <token>" -H "x-am-user-authorization: <token>" -H "content-type: application/json" "http://<server>:38246/api/system-api/bench/emit-event-ws"
Running 60s test @ http://<server>:38246/api/system-api/bench/emit-event-ws
2 connections
2 workers
┌─────────┬───────┬───────┬───────┬───────┬──────────┬─────────┬───────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├─────────┼───────┼───────┼───────┼───────┼──────────┼─────────┼───────┤
│ Latency │ 15 ms │ 23 ms │ 33 ms │ 36 ms │ 23.25 ms │ 4.55 ms │ 42 ms │
└─────────┴───────┴───────┴───────┴───────┴──────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec │ 77 │ 77 │ 84 │ 90 │ 83.94 │ 3.03 │ 77 │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 27.7 kB │ 27.7 kB │ 30.3 kB │ 32.4 kB │ 30.2 kB │ 1.09 kB │ 27.7 kB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
Req/Bytes counts sampled once per second.
# of samples: 120
┌────────────┬──────────────┐
│ Percentile │ Latency (ms) │
├────────────┼──────────────┤
│ 0.001 │ 7 │
├────────────┼──────────────┤
│ 0.01 │ 7 │
├────────────┼──────────────┤
│ 0.1 │ 10 │
├────────────┼──────────────┤
│ 1 │ 14 │
├────────────┼──────────────┤
│ 2.5 │ 15 │
├────────────┼──────────────┤
│ 10 │ 18 │
├────────────┼──────────────┤
│ 25 │ 20 │
├────────────┼──────────────┤
│ 50 │ 23 │
├────────────┼──────────────┤
│ 75 │ 26 │
├────────────┼──────────────┤
│ 90 │ 29 │
├────────────┼──────────────┤
│ 97.5 │ 33 │
├────────────┼──────────────┤
│ 99 │ 36 │
├────────────┼──────────────┤
│ 99.9 │ 40 │
├────────────┼──────────────┤
│ 99.99 │ 42 │
├────────────┼──────────────┤
│ 99.999 │ 42 │
└────────────┴──────────────┘
5k requests in 60.03s, 1.81 MB read
$ am_ws_load listen "ws://<server>:38246/?x-am-authorization=<token>&x-am-user-authorization=<token>"
1,000 clients subscribed, 63.78s
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬─────────┬───────────┐
│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │
├──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼─────────┼───────────┤
│ Delivery │ 11.09 ms │ 25.63 ms │ 55.68 ms │ 63.01 ms │ 27.85 ms │ 11.6 ms │ 113.22 ms │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴─────────┴───────────┘
┌───────────┬────┬──────┬─────────┬─────────┬───────────┬──────────┬─────┐
│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │
├───────────┼────┼──────┼─────────┼─────────┼───────────┼──────────┼─────┤
│ Notif/Sec │ 0 │ 0 │ 275,810 │ 286,858 │ 265,211.0 │ 50767.04 │ 0 │
└───────────┴────┴──────┴─────────┴─────────┴───────────┴──────────┴─────┘
Delivery : from the moment the request is written to the moment the client reads the notification.
# of samples: 64
16.57M notifications received in 63.78s (16,570,000 of 16,562,000 expected), 2542.3 MB read
One server stops. Nobody runs a cluster full : the servers which are left need room for the clients of the one which stops. So the newest clients left first, down to 971,623 : what 3 of the 4 servers hold when each of them is 80 % full. API Maker was then stopped on one of the servers (systemctl stop api_maker_be) and stayed down : its clients were closed with 1001, waited one to five seconds, and connected to the servers which were left.
They were then told again, each for itself, 1,000 notifications a second for 60 seconds : all 60,000 arrived, in 1.33 ms (p50) and 2.45 ms (p99). A client whose subscription had not come back with it would have been told nothing.
Questions¶
How do I scale API Maker WebSockets to N users ?¶
Add API Maker servers behind a load balancer. Each server holds the clients which reached it (8.5 to 8.8 KB of memory per client), and all of them share one Redis, which carries every notification to the server which holds the client. Count N × 8.5 to 8.8 KB of memory over the servers, and one spare server.
Should I run one big server or several small ones ?¶
For the same price they hold about the same number of clients (2,022,026 on 1 × 8 vCPU · 16 GB, 1,915,960 on 2 × 4 vCPU · 8 GB, 1,619,372 on 4 × 2 vCPU · 4 GB) and let in about as many new clients a second. Several servers lose only their share of the clients when one stops (50 % on 2 × 4 vCPU · 8 GB, 25 % on 4 × 2 vCPU · 4 GB) and can be restarted one at a time ; one big server is less to run. Above a few hundred thousand users, several servers.
Do WebSockets need sticky sessions ?¶
No. A WebSocket is one long connection : it stays on the server it reached. What is said to it can come from any server, through Redis.
What limits the number of WebSocket connections ?¶
Memory : 8.5 to 8.8 KB per client, the kernel and Redis included. The installer sizes the other limits of a server from its memory, so that memory is what ends : the open files of the service and the table of the connections the firewall of Linux tracks, 256 for each MB (with 1,048,576 open files, a server of 16 GB would stop at about a million clients with half of its memory left). Behind a proxy : the ports it reaches one server from (about 64,000 per address) and its own memory for each connection.
What happens to the clients when a server stops ?¶
API Maker closes them with the code 1001 (Server is going away). They connect again, to another server when there is one, and subscribe again. Measured on 4 × 2 vCPU · 4 GB : 242,906 clients were all back on the other servers after 38.0 s.