Skip to content
This page View Markdown Open in ChatGPT Open in Claude
WebSocket benchmarks · Scaling · API Maker 4.0.0 · 14 October 2026

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.

Clients on 1 server 2,022,026 1 × 8 vCPU · 16 GB, one Redis
Memory per client 8.5 to 8.8KB the whole of it : API Maker, the kernel, Redis
Servers for N users N ÷ C + 1 C : the clients one server holds, by its memory
One server of four stops 25 % of the clients dropped, back after 38.0 s

The picture

N WebSocket users : a load balancer, API Maker servers which each hold their clients, one Redis they share N users : browsers, mobile apps, deviceseach keeps one WebSocket open : wss://ws.example.com/?x-am-authorization=…&x-am-user-authorization=… Load balancer : Caddy, HAProxy, or the one of your cloudends TLS, hands each WebSocket to one server. No sticky session : a WebSocket stays where it connected API Maker server 1holds the clients whichreached it : a socket,about 8 to 9 KB each API Maker server 2holds the clients whichreached it : a socket,about 8 to 9 KB each API Maker server 3holds the clients whichreached it : a socket,about 8 to 9 KB each API Maker servers + 1 server + 26k to 3.66M clients, by its memory. Nothing else changes Redis of API Maker (internal)a key and a channel for every subscription :about 1.0 to 1.1 KB per client · carries every notificationto the server which holds the client MongoDB of API Makeraccounts, events, groups,the can user connect code(read once, then kept) What notifiesan API call whichsucceeds, on any server,or emitEventWS(…) publish Servers = N ÷ clients one server holds, + 1 spare. Memory of the API servers : N × 8 to 9 KB, over the servers. Memory of Redis : N × 1.0 to 1.1 KB per subscription. Measured : 1,915,960 clients on 2 servers, 1,619,372 on 4.
  • 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

  1. 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.
  2. 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.
  3. 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.

Clients connected at once, for the same price
1 × 8 vCPU · 16 GB$144 / month2,022,026
2 × 4 vCPU · 8 GB$144 / month1,915,960
4 × 2 vCPU · 4 GB$144 / month1,619,372
New clients per second : both tokens and a subscription
1 × 8 vCPU · 16 GB$144 / month12,412
2 × 4 vCPU · 8 GB$144 / month12,696
4 × 2 vCPU · 4 GB$144 / month10,297
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
Clients connected 2,022,026 2,022,026 on each of 1 server
Memory per client 8.8KB API Maker 3.2 · kernel 4.5 · Redis 1.1
New clients / second 12,412 both tokens and a subscription · 19,917 without one
Notification delivered in 1.01ms p50 at 1,000 a second · p99 1.72 ms

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 and memory used during the run 00 MB506k3.9 GB1.01M7.8 GB1.52M11.7 GB2.02M15.6 GB 0 min7 min14 min21 min28 min35 min42 min clients connected memory used, of 15.6 GB
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 %

Clients / second 12,412 745,486 clients in 60 s, 100 at a time
Let in after, p50 7.04ms connected 2.10 ms · TCP 0.24 ms
Let in after, p99 10.4ms max 21.3 ms
Refused or lost 0 not connected, not subscribed, no answer

Server during this run API Maker 7.0 vCPU · Redis 0.5 vCPU · whole server busy 91 % · load generator busy 26 %

am_ws_load · connect with both tokens and subscribe · 1 server

$ 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

Calls / second (paced) 1,000 60,000 calls in 60 s, 10 connections
Notifications / second 983 60,010 received by 10,000 clients
Delivery p50 1.01ms average 1.04 ms
Delivery p99 1.72ms max 5.91 ms
Not delivered 0 0 calls failed

Server during this run API Maker 0.3 vCPU · Redis 0.0 vCPU · whole server busy 4 % · load generator busy 3 %

autocannon · A notification for one person, 1,000 a second

$ 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 · what the clients received

$ 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

Calls / second 24,592 1,475,433 calls in 60 s, 100 connections
Notifications / second 24,305 1,475,533 received by 10,000 clients
Delivery p50 3.88ms average 3.93 ms
Delivery p99 5.66ms max 529 ms
Not delivered 0 0 calls failed

Server during this run API Maker 7.7 vCPU · Redis 0.3 vCPU · whole server busy 97 % · load generator busy 28 %

autocannon · A notification for one person, as many as the server takes

$ 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 · what the clients received

$ 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

Calls / second 222 13,321 calls in 60 s, 10 connections
Notifications / second 217,711 13,330,000 received by 1,000 clients
Delivery p50 36.7ms average 36.7 ms
Delivery p99 56.8ms max 77.6 ms
Not delivered 0 0 calls failed

Server during this run API Maker 6.9 vCPU · Redis 0.3 vCPU · whole server busy 89 % · load generator busy 32 %

autocannon · A broadcast to 1,000 clients, as many as the server takes

$ 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 · what the clients received

$ 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.

Clients dropped 1,516,519 100 % of 1,516,519
All back after 221.1s on the server, restarted
A client was away, p50 111.3s its own wait of 1 to 5 s counted
A client was away, p99 180.7s the longest : 189 s

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
Clients connected 1,915,960 957,980 on each of 2 servers
Memory per client 8.8KB API Maker 3.2 · kernel 4.5 · Redis 1.1
New clients / second 12,696 both tokens and a subscription · 21,417 without one
Notification delivered in 1.18ms p50 at 1,000 a second · p99 1.80 ms

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 and memory used during the run 00 MB479k3.9 GB958k7.8 GB1.44M11.6 GB1.92M15.5 GB 0 min7 min14 min21 min28 min35 min clients connected memory used, of 15.5 GB
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 %

Clients / second 12,696 762,499 clients in 60 s, 100 at a time
Let in after, p50 6.32ms connected 1.66 ms · TCP 0.24 ms
Let in after, p99 14.0ms max 45.5 ms
Refused or lost 0 not connected, not subscribed, no answer

Server during this run API Maker 7.3 vCPU · Redis 0.8 vCPU · whole server busy 94 % · load generator busy 28 %

am_ws_load · connect with both tokens and subscribe · 2 servers

$ 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

Calls / second (paced) 1,000 60,004 calls in 60 s, 10 connections
Notifications / second 984 60,014 received by 10,000 clients
Delivery p50 1.18ms average 1.19 ms
Delivery p99 1.80ms max 10.0 ms
Not delivered 0 0 calls failed

Server during this run API Maker 0.4 vCPU · Redis 0.1 vCPU · whole server busy 5 % · load generator busy 3 %

autocannon · A notification for one person, 1,000 a second · server 1 of 2

$ 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 · A notification for one person, 1,000 a second · server 2 of 2

$ 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 · what the clients received

$ 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

Calls / second 25,389 1,523,298 calls in 60 s, 100 connections
Notifications / second 24,977 1,523,398 received by 10,000 clients
Delivery p50 3.75ms average 3.95 ms
Delivery p99 6.31ms max 530 ms
Not delivered 0 0 calls failed

Server during this run API Maker 7.8 vCPU · Redis 0.6 vCPU · whole server busy 99 % · load generator busy 46 %

autocannon · A notification for one person, as many as the server takes · server 1 of 2

$ 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 · A notification for one person, as many as the server takes · server 2 of 2

$ 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 · what the clients received

$ 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

Calls / second 255 15,275 calls in 60 s, 10 connections
Notifications / second 228,910 15,285,000 received by 1,000 clients
Delivery p50 39.3ms average 1,295 ms
Delivery p99 6,701ms max 6,902 ms
Not delivered 0 0 calls failed

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 · A broadcast to 1,000 clients, as many as the server takes · server 1 of 2

$ 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 · A broadcast to 1,000 clients, as many as the server takes · server 2 of 2

$ 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 · what the clients received

$ 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.

Clients dropped 383,192 50 % of 766,384
All back after 68.0s on the 1 server left
A client was away, p50 36.4s its own wait of 1 to 5 s counted
A client was away, p99 55.8s the longest : 57.9 s

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
Clients connected 1,619,372 404,843 on each of 4 servers
Memory per client 8.6KB API Maker 3.2 · kernel 4.4 · Redis 1.1
New clients / second 10,297 both tokens and a subscription · 17,882 without one
Notification delivered in 1.37ms p50 at 1,000 a second · p99 2.51 ms

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 and memory used during the run 00 MB405k3.8 GB810k7.6 GB1.21M11.5 GB1.62M15.3 GB 0 min6 min12 min18 min24 min30 min36 min clients connected memory used, of 15.3 GB
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 %

Clients / second 10,297 618,456 clients in 60 s, 100 at a time
Let in after, p50 3.91ms connected 1.12 ms · TCP 0.34 ms
Let in after, p99 30.2ms max 53.5 ms
Refused or lost 0 not connected, not subscribed, no answer

Server during this run API Maker 6.6 vCPU · Redis 1.0 vCPU · whole server busy 88 % · load generator busy 29 %

am_ws_load · connect with both tokens and subscribe · 4 servers

$ 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

Calls / second (paced) 1,000 60,000 calls in 60 s, 8 connections
Notifications / second 983 60,003 received by 10,000 clients
Delivery p50 1.37ms average 1.44 ms
Delivery p99 2.51ms max 12.2 ms
Not delivered 0 0 calls failed

Server during this run API Maker 0.4 vCPU · Redis 0.1 vCPU · whole server busy 5 % · load generator busy 4 %

autocannon · A notification for one person, 1,000 a second · server 1 of 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 · A notification for one person, 1,000 a second · server 2 of 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 │ 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 · A notification for one person, 1,000 a second · server 3 of 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 │ 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 · A notification for one person, 1,000 a second · server 4 of 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 │ 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 · what the clients received

$ 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

Calls / second 25,997 1,559,712 calls in 60 s, 100 connections
Notifications / second 25,566 1,559,812 received by 10,000 clients
Delivery p50 3.70ms average 3.77 ms
Delivery p99 6.54ms max 26.1 ms
Not delivered 0 0 calls failed

Server during this run API Maker 7.8 vCPU · Redis 0.9 vCPU · whole server busy 99 % · load generator busy 64 %

autocannon · A notification for one person, as many as the server takes · server 1 of 4

$ 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 · A notification for one person, as many as the server takes · server 2 of 4

$ 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 · A notification for one person, as many as the server takes · server 3 of 4

$ 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 · A notification for one person, as many as the server takes · server 4 of 4

$ 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 · what the clients received

$ 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

Calls / second 276 16,562 calls in 60 s, 8 connections
Notifications / second 272,609 16,570,000 received by 1,000 clients
Delivery p50 25.6ms average 27.9 ms
Delivery p99 63.0ms max 113 ms
Not delivered 0 0 calls failed

Server during this run API Maker 7.8 vCPU · Redis 0.8 vCPU · whole server busy 98 % · load generator busy 38 %

autocannon · A broadcast to 1,000 clients, as many as the server takes · server 1 of 4

$ 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 · A broadcast to 1,000 clients, as many as the server takes · server 2 of 4

$ 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 · A broadcast to 1,000 clients, as many as the server takes · server 3 of 4

$ 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 · A broadcast to 1,000 clients, as many as the server takes · server 4 of 4

$ 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 · what the clients received

$ 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.

Clients dropped 242,906 25 % of 971,623
All back after 38.0s on the 3 servers left
A client was away, p50 14.5s its own wait of 1 to 5 s counted
A client was away, p99 28.2s the longest : 31.0 s

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.