📘

マルチプロセスなBun.serveの並列リクエスト処理性能を調べる

2024/03/16に公開

Linux版Bunの実験的な機能の位置付けのようなのですが、Bun.serveにreusePortフラグをつけるとsetsockopt システムコールのSO_REUSEPORTオプションを設定して複数のプロセスで同じポートにリクエストを同時に待ち受けることが可能になります

https://github.com/oven-sh/bun/blob/8c5ac06113b19ed2098318575077b8c3d88aaa4a/packages/bun-usockets/src/bsd.c#L481-L488

(「WasmerのWinterJSのベンチマーク結果はあやしい」を書いていた時に知りました)

これを試してみることにしました

環境はx86_64な12 coresのWSLです

lscpuの結果

Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 48 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 12
On-line CPU(s) list: 0-11
Vendor ID: AuthenticAMD
Model name: AMD Ryzen 5 5600X 6-Core Processor
CPU family: 25
Model: 33
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 2
BogoMIPS: 7400.08
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl tsc_reliable nonstop_tsc cpuid extd_apicid pni
pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext perfctr_core ssbd ibrs ibpb stibp vmmcall fsgs
base bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves clzero xsaveerptr arat npt nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold v_v
msave_vmload umip vaes vpclmulqdq rdpid fsrm
Virtualization features:
Virtualization: AMD-V
Hypervisor vendor: Microsoft
Virtualization type: full
Caches (sum of all):
L1d: 192 KiB (6 instances)
L1i: 192 KiB (6 instances)
L2: 3 MiB (6 instances)
L3: 32 MiB (1 instance)
Vulnerabilities:
Gather data sampling: Not affected
Itlb multihit: Not affected
L1tf: Not affected
Mds: Not affected
Meltdown: Not affected
Mmio stale data: Not affected
Retbleed: Not affected
Spec rstack overflow: Mitigation; safe RET, no microcode
Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Spectre v2: Mitigation; Retpolines, IBPB conditional, IBRS_FW, STIBP conditional, RSB filling, PBRSB-eIBRS Not affected
Srbds: Not affected
Tsx async abort: Not affected

親プロセスである main.jsと子プロセスとして呼び出すserver.jsを書きます

main.js
import os from "node:os";

for (let i = 0; i < os.cpus().length; i++) {
    console.log(`CPU ${i+1}`);
    Bun.spawn(['bun', 'serve.js'], {
        onExit(proc, exitCode, signalCode, error) {
            proc.stdout.pipe(process.stdout);
            console.log(exitCode, signalCode, error);
        },
        ipc(message, childProc) {
            console.log({message});
        },
    })
}
server.js
Bun.serve({
    port: 8080,
    reusePort: true,
    fetch(req) {
        return new Response("Bun!");
    }
})

if (process.send) {
    process.send(`Server PID: ${process.pid}`)
}

最初のベンチマークシナリオです、まずserver.jsを直接実行してシンプルプロセスでリクエストを待ち受けます

$ bun server.js
$ wrk -t12 -c100 http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
  12 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   790.57us  187.00us   3.76ms   93.61%
    Req/Sec    10.16k   588.39    11.09k    78.55%
  1225290 requests in 10.10s, 139.05MB read
Requests/sec: 121319.71
Transfer/sec:     13.77MB

次にマルチプロセス化の結果を確認するために、main.jsからspwanして12個プロセスに分散してもらいます

$ bun main.js
$ ps x | grep bun
  19208 pts/3    Sl+    0:00 bun main.js
  19217 pts/3    Sl+    0:00 bun serve.js
  19218 pts/3    Sl+    0:00 bun serve.js
  19219 pts/3    Sl+    0:00 bun serve.js
  19220 pts/3    Sl+    0:00 bun serve.js
  19221 pts/3    Sl+    0:00 bun serve.js
  19222 pts/3    Sl+    0:00 bun serve.js
  19223 pts/3    Sl+    0:00 bun serve.js
  19224 pts/3    Sl+    0:00 bun serve.js
  19225 pts/3    Sl+    0:00 bun serve.js
  19226 pts/3    Sl+    0:00 bun serve.js
  19227 pts/3    Sl+    0:00 bun serve.js
  19228 pts/3    Sl+    0:00 bun serve.js
$ wrk -t12 -c100 http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
  12 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   627.55us    1.63ms  32.63ms   92.50%
    Req/Sec    52.25k     7.10k   74.42k    68.58%
  6243844 requests in 10.02s, 708.60MB read
Requests/sec: 623136.69
Transfer/sec:     70.72MB

5倍以上の秒間リクエスト数を捌けるようになったので期待した結果になりました

htopで観察するとすべてのcoreが使われている

コラム: マルチプロセスなBun Serverを運用するには?

上記のような単純なspawnしたプロセスは、子プロセスの起動終了の監視やハンドリングが入っていないので運用するには不十分です

Node.jsの世界ではcluster モジュールや PM2 などのプロセスマネージャを使用するのが一般的ですが、Bunの場合はまだベストプラクティスはないといった状況です

ただし、Bunアプリケーションのレイヤーでマルチプロセス化する機会は限定的かもしれません。多くの場合、前段にロードバランサーを配置したり、サーバーレスやコンテナベースの並列化を行うことで、アプリケーションレイヤーでの複雑なプロセス管理を回避できます

Discussion