raptor 0.22.3 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Dockerfile +10 -2
  4. data/README.md +183 -61
  5. data/docs/assets/profile_picture.png +0 -0
  6. data/docs/assets/startup_output.svg +37 -0
  7. data/docs/raptor-vs-puma.md +104 -424
  8. data/docs/talk.md +1388 -0
  9. data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
  10. data/ext/raptor_http/extconf.rb +2 -0
  11. data/ext/raptor_http/raptor_http.c +165 -234
  12. data/ext/raptor_http2/raptor_http2.c +272 -223
  13. data/ext/raptor_native/raptor_native.c +38 -20
  14. data/lib/rackup/handler/raptor.rb +14 -12
  15. data/lib/raptor/binder.rb +12 -12
  16. data/lib/raptor/cli.rb +19 -18
  17. data/lib/raptor/cluster.rb +120 -74
  18. data/lib/raptor/control_server.rb +5 -2
  19. data/lib/raptor/detached_body.rb +6 -2
  20. data/lib/raptor/http.rb +7 -6
  21. data/lib/raptor/http1.rb +281 -234
  22. data/lib/raptor/http2.rb +356 -276
  23. data/lib/raptor/plugin.rb +98 -0
  24. data/lib/raptor/reactor.rb +138 -148
  25. data/lib/raptor/reuseport_bpf.rb +62 -38
  26. data/lib/raptor/server.rb +45 -34
  27. data/lib/raptor/stats.rb +15 -3
  28. data/lib/raptor/thread_locals.rb +4 -3
  29. data/lib/raptor/version.rb +1 -1
  30. data/sig/generated/rackup/handler/raptor.rbs +2 -1
  31. data/sig/generated/raptor/binder.rbs +9 -9
  32. data/sig/generated/raptor/cli.rbs +6 -6
  33. data/sig/generated/raptor/cluster.rbs +18 -5
  34. data/sig/generated/raptor/control_server.rbs +2 -1
  35. data/sig/generated/raptor/detached_body.rbs +5 -1
  36. data/sig/generated/raptor/http.rbs +3 -5
  37. data/sig/generated/raptor/http1.rbs +119 -102
  38. data/sig/generated/raptor/http2.rbs +204 -190
  39. data/sig/generated/raptor/plugin.rbs +52 -0
  40. data/sig/generated/raptor/reactor.rbs +107 -118
  41. data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
  42. data/sig/generated/raptor/server.rbs +26 -26
  43. data/sig/generated/raptor/stats.rbs +3 -2
  44. data/sig/generated/raptor/thread_locals.rbs +4 -3
  45. metadata +6 -2
  46. data/docs/brisrails-talk.md +0 -2398
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 813fde7063bd9f7dd9e965f57ede9e1e207f4f0db558343e777fce9769a7ec0e
4
- data.tar.gz: faf2cdec407c5c8d36f4da222a50df4b47366d2a76204e22356a91bb24736879
3
+ metadata.gz: c8c3462777a399cad50b6131c497c98ab519850ff1d88d062b681cc05c9d540b
4
+ data.tar.gz: 878a2629b500d8873836cda4bd463f0dcb727a33bd301fc5a246cd860a3f0957
5
5
  SHA512:
6
- metadata.gz: be5d3eb531ab184623787d4b18b06a0d51f908c92120fd46031c353518563da756183775155e250c9744fbc34400c82945204ba341f126be8aa3eab5b94ac8c3
7
- data.tar.gz: 4b125079b570a4e0f15a549d75ef09bcce749f1442fd18a20a27401cc9c6618797e61564e1ebc194fc93c93bf07eee669249a1e1df18064b306b09a2334b7f41
6
+ metadata.gz: 0d9b05f416a7024c91dc2861d6f748eadb1c20e01224c3e82a1202247f48220e56f81742fa600f41f1885c60587464b9506c0e2855d9392767016f5199bb0d94
7
+ data.tar.gz: 93403bcdbfaef05e6a011cc795ef66b7713dc2ed4266c157afb3f5eb1b53ae60c8f7610e60d2caee75e8b82716e58cac6b771543d6f9d43823cb03e5eed87dd8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.24.0] - 2026-11-08
4
+
5
+ - Honour `connection.max_body_size` for HTTP/2 request bodies without closing other streams
6
+ - Wait for all current workers to boot before notifying systemd that Raptor is ready
7
+ - Prefer each over call for HTTP/1 Rack response bodies that implement both
8
+ - Reject empty HTTP/1 Content-Length values and unsupported transfer codings before reading request bodies
9
+ - Preserve request Version headers instead of replacing them with the HTTP protocol
10
+ - Trim leading tabs as optional HTTP/1 header whitespace
11
+ - Honour HTTP/1 Connection token lists, with close taking precedence over keep-alive
12
+ - Run worker boot hooks before starting application threads, parsers, and request handling
13
+ - Close HTTP/2 response bodies when writing fails or a stream is cancelled
14
+ - Bound HTTP/1 chunk size lines and trailers, including incomplete metadata
15
+ - Reject invalid CRLF delimiters after HTTP/1 chunk payloads
16
+ - Prefer hyphenated request headers over underscore aliases and reject HTTP/1 framing aliases
17
+ - Add plugins with lifecycle hooks, cluster stats, and managed background threads
18
+
19
+ ## [0.23.0] - 2026-10-18
20
+
21
+ - Route connections through BPF dispatch on every TCP bind
22
+ - Reject non-integer `RAPTOR_WORKERS` and `RAPTOR_THREADS` in the Rackup handler
23
+ - Replace config file binds with `--bind` addresses instead of adding to them
24
+ - Stop BPF dispatch from routing connections to workers that stopped accepting
25
+ - Restart workers forked by the seed when they exit
26
+ - Keep draining HTTP/2 connections when another attaches during shutdown
27
+ - Close HTTP/1.1 connections on the reactor thread when a detached body fails to open
28
+ - Apply HTTP/2 initial window changes to responses waiting for flow control
29
+ - Default `SERVER_NAME` for HTTP/2 requests without a host name
30
+ - Join repeated HTTP/2 request headers instead of keeping only the last
31
+ - Ignore HTTP/2 DATA frames for streams that already closed
32
+ - Credit each HTTP/2 stream window for the data that stream receives
33
+ - Strip padding from HTTP/2 DATA and HEADERS frames
34
+ - Encode and decode long HTTP/2 header strings without overflowing the stack
35
+ - Limit HTTP/2 request header lists to 112 KiB
36
+ - Reject HTTP/2 frames larger than the maximum frame size
37
+ - Send GOAWAY when an HTTP/2 frame fails to decode
38
+ - Keep pipelined HTTP/1.1 requests out of the previous request's body
39
+ - Process chunked requests on kept-alive HTTP/1.1 connections without waiting for more data
40
+ - Validate request framing on kept-alive HTTP/1.1 connections
41
+ - Reject `Content-Length` values that overflow 64 bits
42
+ - Resume idle HTTP/1.1 keep-alive connections on application threads
43
+ - Defer accepting TCP connections until the client sends data
44
+ - Accept connections the BPF program cannot route to a worker
45
+ - Count connections waiting in accept queues towards BPF worker load
46
+ - Scale accept backpressure with the thread pool and yield at capacity
47
+ - Raise `Http2ParserError` for malformed HPACK Huffman data
48
+
3
49
  ## [0.22.3] - 2026-10-04
4
50
 
5
51
  - Keep workers accepting connections when clients reset during accept
data/Dockerfile CHANGED
@@ -1,7 +1,13 @@
1
1
  FROM golang:bookworm AS hey-builder
2
- RUN CGO_ENABLED=0 go install github.com/rakyll/hey@latest
2
+ RUN CGO_ENABLED=0 go install github.com/rakyll/hey@v0.1.5
3
3
 
4
- FROM ruby:latest
4
+ WORKDIR /go/pkg/mod/github.com/rakyll/hey@v0.1.5
5
+ # Keep the bounded initial allocation, but retain every response for full-window p95.
6
+ RUN sed -i '/if len(r.resLats) < maxRes {/d; /r.offsets = append(r.offsets, res.offset.Seconds())/{n;d;}' requester/report.go \
7
+ && gofmt -w requester/report.go \
8
+ && CGO_ENABLED=0 go build -o /go/bin/hey .
9
+
10
+ FROM rubylang/ruby:master-dev
5
11
 
6
12
  ENV DEBIAN_FRONTEND=noninteractive
7
13
  ENV LANG=C.UTF-8
@@ -12,6 +18,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
12
18
  clang \
13
19
  libbpf-dev \
14
20
  libelf-dev \
21
+ libssl-dev \
22
+ libyaml-dev \
15
23
  linux-libc-dev \
16
24
  nghttp2 \
17
25
  openssl \
data/README.md CHANGED
@@ -36,29 +36,29 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
36
36
 
37
37
  ```
38
38
  > bundle exec raptor -w 10 -t 3 hello_world.ru
39
- [Raptor 72876|Main|Main] Cluster initializing:
40
- [Raptor 72876|Main|Main] ├─ Version: 0.22.3
41
- [Raptor 72876|Main|Main] ├─ Ruby Version: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +YJIT +PRISM [arm64-darwin23]
42
- [Raptor 72876|Main|Main] ├─ Environment: development
43
- [Raptor 72876|Main|Main] ├─ Master PID: 72876
44
- [Raptor 72876|Main|Main] │ └─ 10 worker processes
45
- [Raptor 72876|Main|Main] │ ├─ 1 server thread
46
- [Raptor 72876|Main|Main] │ ├─ 1 reactor thread
47
- [Raptor 72876|Main|Main] │ ├─ 1 HTTP/1.1 pipeline ractor
48
- [Raptor 72876|Main|Main] │ ├─ 1 pipeline collector thread
49
- [Raptor 72876|Main|Main] │ ├─ 3 worker threads (scaling, no limit)
50
- [Raptor 72876|Main|Main] │ └─ 1 stats thread
51
- [Raptor 72876|Main|Main] └─ Listening on 0.0.0.0:9292
52
- [Raptor 72884|Main|Main] Worker 0 booted
53
- [Raptor 72885|Main|Main] Worker 1 booted
54
- [Raptor 72886|Main|Main] Worker 2 booted
55
- [Raptor 72887|Main|Main] Worker 3 booted
56
- [Raptor 72891|Main|Main] Worker 7 booted
57
- [Raptor 72888|Main|Main] Worker 4 booted
58
- [Raptor 72890|Main|Main] Worker 6 booted
59
- [Raptor 72889|Main|Main] Worker 5 booted
60
- [Raptor 72892|Main|Main] Worker 8 booted
61
- [Raptor 72893|Main|Main] Worker 9 booted
39
+ [Raptor 39704|Main|Main] Cluster initializing:
40
+ [Raptor 39704|Main|Main] ├─ Version: 0.23.0
41
+ [Raptor 39704|Main|Main] ├─ Ruby Version: ruby 4.0.9 (2026-10-23 revision 3e93b23afc) +YJIT +PRISM [arm64-darwin25]
42
+ [Raptor 39704|Main|Main] ├─ Environment: development
43
+ [Raptor 39704|Main|Main] ├─ Master PID: 39704
44
+ [Raptor 39704|Main|Main] │ └─ 10 worker processes
45
+ [Raptor 39704|Main|Main] │ ├─ 1 server thread
46
+ [Raptor 39704|Main|Main] │ ├─ 1 reactor thread
47
+ [Raptor 39704|Main|Main] │ ├─ 1 HTTP/1.1 pipeline ractor
48
+ [Raptor 39704|Main|Main] │ ├─ 1 pipeline collector thread
49
+ [Raptor 39704|Main|Main] │ ├─ 3 worker threads (scaling, no limit)
50
+ [Raptor 39704|Main|Main] │ └─ 1 stats thread
51
+ [Raptor 39704|Main|Main] └─ Listening on 0.0.0.0:9292
52
+ [Raptor 39720|Main|Main] Worker 1 booted
53
+ [Raptor 39719|Main|Main] Worker 0 booted
54
+ [Raptor 39722|Main|Main] Worker 3 booted
55
+ [Raptor 39721|Main|Main] Worker 2 booted
56
+ [Raptor 39723|Main|Main] Worker 4 booted
57
+ [Raptor 39725|Main|Main] Worker 6 booted
58
+ [Raptor 39724|Main|Main] Worker 5 booted
59
+ [Raptor 39728|Main|Main] Worker 8 booted
60
+ [Raptor 39726|Main|Main] Worker 7 booted
61
+ [Raptor 39730|Main|Main] Worker 9 booted
62
62
  ```
63
63
 
64
64
  ```
@@ -127,6 +127,7 @@ working directory. Pass `-c PATH` to point at a specific file.
127
127
  before_worker_boot: [],
128
128
  before_worker_shutdown: [],
129
129
  before_refork: [],
130
+ plugins: [],
130
131
  stats_file: "tmp/raptor.json",
131
132
  control_url: nil,
132
133
  pid_file: nil,
@@ -142,6 +143,46 @@ By default each worker adds threads beyond `threads` only while queued requests
142
143
  database or network calls. It stops adding them once Ruby execution becomes the bottleneck, and the extra threads
143
144
  retire when the queue is idle. Set `max_threads` to cap this growth, or set it to `threads` for a fixed pool.
144
145
 
146
+ ## Plugins
147
+
148
+ Plugins start in the master process and can register lifecycle hooks that run in workers. Use them for metrics
149
+ exporters, probes, or other integrations. Enable them in your config with `plugins: [:stats_logger]`.
150
+
151
+ Raptor loads each name from `raptor/plugin/<name>` on Ruby's load path. A gem can provide that file, or you can require
152
+ an application-local plugin before returning the config hash.
153
+
154
+ ```ruby
155
+ # lib/raptor/plugin/stats_logger.rb
156
+ require "raptor/plugin"
157
+
158
+ Raptor::Plugin.create(:stats_logger) do
159
+ def start(cluster)
160
+ in_background do
161
+ loop do
162
+ Raptor::Log.info(JSON.generate(cluster.stats_hash))
163
+ sleep 10
164
+ end
165
+ end
166
+ end
167
+ end
168
+ ```
169
+
170
+ For an existing class, inherit from `Raptor::Plugin` and register it with
171
+ `Raptor::Plugin.register(:stats_logger, MyStatsLogger)` instead.
172
+
173
+ `start(cluster)` runs once before the initial workers fork. `cluster.options` exposes configuration, including
174
+ plugin-specific keys. Append callbacks to its `before_fork`, `before_worker_boot`, `before_worker_shutdown`, and
175
+ `before_refork` arrays during startup. If your plugin opens a listener, close the worker's inherited copy with
176
+ `cluster.options[:before_worker_boot] << proc { @listener.close }` so only the master serves it.
177
+
178
+ `cluster.stats` returns per-worker snapshots. `cluster.stats_hash` returns the same aggregate structure as `GET /stats`,
179
+ including boot status, busy threads, capacity, backlog, and request counts. Both remain available while workers drain.
180
+
181
+ Override `stop` to close listeners and flush exporters. Raptor calls it after workers exit, in reverse plugin order,
182
+ then stops and joins threads created with `in_background`. These callbacks also run before hot restart and when startup
183
+ fails, so `stop` must handle partially initialized resources. Worker replacement and phased restart keep the same
184
+ master plugin instances running.
185
+
145
186
  ## Bindings
146
187
 
147
188
  `binds` accepts any combination of these URIs.
@@ -170,7 +211,8 @@ to configuration, dependencies, or Raptor itself.
170
211
  ## systemd
171
212
 
172
213
  Raptor supports socket activation and `sd_notify`, so it works with `Type=notify` units. When the socket unit is
173
- active, Raptor serves the listening sockets systemd passes in place of `binds`.
214
+ active, Raptor serves the listening sockets systemd passes in place of `binds`. Raptor sends `READY=1` after all
215
+ workers have finished booting, including their `before_worker_boot` hooks.
174
216
 
175
217
  ```ini
176
218
  # /etc/systemd/system/myapp.socket
@@ -208,43 +250,123 @@ Set `control_url` to a Unix socket URL such as `unix:///tmp/raptor-control.sock`
208
250
 
209
251
  ## (Micro) Benchmarks
210
252
 
211
- Raptor 0.22.3 vs Puma 8.0.2 vs Falcon 0.57.0 across two workload profiles. **IO-bound** is a GET endpoint that
212
- interleaves 5-10 short sleeps (total 2.5-15ms) with small CPU work, simulating a read path that makes several DB or
213
- cache calls. **CPU-bound** is a POST endpoint that accepts a small JSON body, interleaves 3-5 chunks of JSON item
214
- building (total 450-1500 items) with sub-100µs sleeps, and returns the built array, simulating a write path that does
215
- most of its work in Ruby with a few near-zero-cost cache hits.
216
-
217
- Raptor is run in two modes: **Fixed** keeps 3 application threads per worker, matching Puma, while **Scaling** starts
218
- with 3 and may add threads without a fixed limit when queued work is blocked outside the GVL. Both modes are compared
219
- with both Puma and Falcon in the table below. Raptor request-local cleanup and Puma's Fiber-per-request mode are
220
- disabled, and both threaded servers allow 999 requests per HTTP/1.1 keep-alive connection.
221
-
222
- Each cell reports the median throughput and median p95 latency independently across 3 runs, so the two numbers in a row
223
- may come from different runs. Every run starts a fresh server process so the samples are independent of each other;
224
- state accumulated in a previous run cannot bias the next. Across the whole table, the widest spread
225
- ((max - min) / 2 / median) between runs of a single cell was ±19.6% for throughput and ±13.6% for p95.
226
-
227
- | Protocol | Workload | Raptor mode | Raptor req/s | Raptor p95 | Puma req/s | Puma p95 | vs Puma req/s | vs Puma p95 | Falcon req/s | Falcon p95 | vs Falcon req/s | vs Falcon p95 |
228
- | --------------------- | -------- | ----------- | ------------ | ---------- | ----------- | --------- | ------------- | ------------ | ------------ | ---------- | --------------- | ------------- |
229
- | HTTP/1.1 | IO | Fixed | 2.95k req/s | 80.20 ms | 1.55k req/s | 122.60 ms | 90.1% higher | 34.6% lower | 12.24k req/s | 14.00 ms | 75.9% lower | 472.9% higher |
230
- | HTTP/1.1 | IO | Scaling | 7.05k req/s | 30.40 ms | 1.55k req/s | 122.60 ms | 354.1% higher | 75.2% lower | 12.24k req/s | 14.00 ms | 42.4% lower | 117.1% higher |
231
- | HTTP/1.1 | CPU | Fixed | 7.29k req/s | 35.00 ms | 8.73k req/s | 20.30 ms | 16.4% lower | 72.4% higher | 6.71k req/s | 26.80 ms | 8.7% higher | 30.6% higher |
232
- | HTTP/1.1 | CPU | Scaling | 6.73k req/s | 36.90 ms | 8.73k req/s | 20.30 ms | 22.8% lower | 81.8% higher | 6.71k req/s | 26.80 ms | 0.4% higher | 37.7% higher |
233
- | HTTP/1.1 (keep-alive) | IO | Fixed | 2.52k req/s | 70.70 ms | 1.50k req/s | 102.60 ms | 67.8% higher | 31.1% lower | 6.23k req/s | 28.20 ms | 59.5% lower | 150.7% higher |
234
- | HTTP/1.1 (keep-alive) | IO | Scaling | 8.19k req/s | 21.90 ms | 1.50k req/s | 102.60 ms | 445.8% higher | 78.7% lower | 6.23k req/s | 28.20 ms | 31.6% higher | 22.3% lower |
235
- | HTTP/1.1 (keep-alive) | CPU | Fixed | 7.04k req/s | 28.30 ms | 8.62k req/s | 21.50 ms | 18.3% lower | 31.6% higher | 7.07k req/s | 32.10 ms | 0.4% lower | 11.8% lower |
236
- | HTTP/1.1 (keep-alive) | CPU | Scaling | 7.78k req/s | 25.70 ms | 8.62k req/s | 21.50 ms | 9.8% lower | 19.5% higher | 7.07k req/s | 32.10 ms | 9.9% higher | 19.9% lower |
237
- | HTTP/2 | IO | Fixed | 1.50k req/s | 128.92 ms | N/A | N/A | - | - | 6.57k req/s | 27.29 ms | 77.2% lower | 372.4% higher |
238
- | HTTP/2 | IO | Scaling | 9.41k req/s | 19.30 ms | N/A | N/A | - | - | 6.57k req/s | 27.29 ms | 43.1% higher | 29.3% lower |
239
- | HTTP/2 | CPU | Fixed | 7.75k req/s | 26.55 ms | N/A | N/A | - | - | 7.24k req/s | 49.42 ms | 7.1% higher | 46.3% lower |
240
- | HTTP/2 | CPU | Scaling | 7.54k req/s | 26.97 ms | N/A | N/A | - | - | 7.24k req/s | 49.42 ms | 4.2% higher | 45.4% lower |
241
-
242
- > ruby 4.0.7 (2026-09-15 revision 229531a6cf) +YJIT +PRISM [aarch64-linux]
243
- > 10 worker processes; fixed Raptor and Puma run 3 threads per worker; scaling Raptor starts at 3 with no fixed limit;
244
- > Falcon runs unbounded fibers per worker; 120 concurrent HTTP/1.1 client connections; 40 concurrent HTTP/2 client
245
- > connections × 3 streams each
246
-
247
- See [bin/benchmark](bin/benchmark) for more details.
253
+ Raptor 0.24.0 vs Puma 8.0.2 vs Falcon 0.57.0. Each case has 3 fresh-process samples, interleaved in seeded shuffled
254
+ rounds. Each sample warms up for 3s and measures for 15s. Metrics are independent medians. ± is (max − min) / 2 /
255
+ median, not a confidence interval. Failure counts below each table sum all samples, so a failed run cannot disappear in
256
+ a median. Throughput counts successful ordinary requests only.
257
+
258
+ Comparison columns show each server's change from Raptor's median for the baseline mode. Higher req/s is better. Lower
259
+ p95 is better. Falcon's measurements are repeated against each Raptor mode, not rerun.
260
+
261
+ **IO** interleaves 5–10 sleeps of 0.5–1.5ms with small JSON renders. **CPU** interleaves 3–5 larger JSON renders with
262
+ 25–50µs sleeps. Both discard the rendered JSON and return a short response. **64 KiB body** sends a prebuilt response to
263
+ isolate response transfer. **SSE (native)** uses Raptor's detached body versus blocking Enumerators. **Raptor (enum)**
264
+ uses the same blocking body as Puma and Falcon, reusing their existing samples for comparison. The 20 streams are
265
+ cluster-wide connections, not guaranteed to be evenly placed. Streams without complete headers after 2s remain connected
266
+ and are counted as stalled.
267
+
268
+ Fixed (3), Scaling (3–16), and the IO-only Fixed (16) control separate tight concurrency, pool growth, and a prestarted
269
+ larger pool. Falcon uses unbounded fibers and runs once per workload/protocol. Raptor request-local cleanup, CPU
270
+ affinity, and reforking, and Puma's Fiber-per-request mode are disabled. Both threaded servers allow 999 requests per
271
+ keep-alive connection.
272
+
273
+ CPU includes the server master and descendants, including SSE event production, but excludes client CPU and warmup. CPU
274
+ ms/req divides that CPU time by successful ordinary requests. Peak aggregate PSS is sampled every 0.5s, proportionally
275
+ accounting for shared pages instead of counting them fully in every worker.
276
+
277
+ ### IO over HTTP/1.1
278
+
279
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
280
+ | ------ | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
281
+ | Raptor | Fixed (3) | - | 3.32k ±0.2% | - | 50.10 | - | 2.387 | 364.0 |
282
+ | Raptor | Scaling (3–16) | - | 8.10k ±0.3% | - | 22.45 | - | 0.514 | 340.8 |
283
+ | Puma | Fixed (3) | Fixed (3) | 1.55k ±0.9% | 53.4% lower | 121.78 | 143.1% higher | 0.737 | 283.8 |
284
+ | Puma | Scaling (3–16) | Scaling (3–16) | 8.13k ±0.7% | 0.4% higher | 22.41 | 0.1% lower | 0.481 | 341.2 |
285
+ | Falcon | Fibers | Fixed (3) | 10.65k ±4.1% | 220.8% higher | 16.18 | 67.7% lower | 0.450 | 425.5 |
286
+ | Falcon | Fibers | Scaling (3–16) | 10.65k ±4.1% | 31.5% higher | 16.18 | 27.9% lower | 0.450 | 425.5 |
287
+
288
+ ### IO over HTTP/1.1 keep-alive
289
+
290
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
291
+ | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
292
+ | Raptor | Fixed (3) | - | 3.01k ±8.2% | - | 56.38 | - | 2.306 | 365.0 |
293
+ | Raptor | Scaling (3–16) | - | 7.55k ±0.6% | - | 24.18 | - | 0.500 | 363.7 |
294
+ | Raptor | Fixed (16) | - | 7.56k ±0.7% | - | 24.16 | - | 0.468 | 367.8 |
295
+ | Puma | Fixed (3) | Fixed (3) | 1.49k ±0.2% | 50.4% lower | 105.33 | 86.8% higher | 0.784 | 290.7 |
296
+ | Puma | Scaling (3–16) | Scaling (3–16) | 7.63k ±0.9% | 1.1% higher | 23.89 | 1.2% lower | 0.466 | 329.9 |
297
+ | Puma | Fixed (16) | Fixed (16) | 7.73k ±0.5% | 2.2% higher | 23.55 | 2.5% lower | 0.461 | 332.3 |
298
+ | Falcon | Fibers | Fixed (3) | 7.54k ±1.8% | 150.8% higher | 24.40 | 56.7% lower | 0.373 | 357.4 |
299
+ | Falcon | Fibers | Scaling (3–16) | 7.54k ±1.8% | 0.1% lower | 24.40 | 0.9% higher | 0.373 | 357.4 |
300
+ | Falcon | Fibers | Fixed (16) | 7.54k ±1.8% | 0.2% lower | 24.40 | 1.0% higher | 0.373 | 357.4 |
301
+
302
+ ### IO over HTTP/2
303
+
304
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
305
+ | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
306
+ | Raptor | Fixed (3) | - | 1.60k ±6.4% | - | 119.04 | - | 0.779 | 418.0 |
307
+ | Raptor | Scaling (3–16) | - | 7.98k ±9.9% | - | 23.38 | - | 0.565 | 894.4 |
308
+ | Falcon | Fibers | Fixed (3) | 7.18k ±1.3% | 348.7% higher | 26.29 | 77.9% lower | 0.438 | 515.8 |
309
+ | Falcon | Fibers | Scaling (3–16) | 7.18k ±1.3% | 10.0% lower | 26.29 | 12.4% higher | 0.438 | 515.8 |
310
+
311
+ ### CPU over HTTP/1.1
312
+
313
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
314
+ | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
315
+ | Raptor | Fixed (3) | - | 3.13k ±0.3% | - | 72.60 | - | 2.673 | 526.0 |
316
+ | Raptor | Scaling (3–16) | - | 3.22k ±2.0% | - | 68.49 | - | 2.546 | 520.0 |
317
+ | Puma | Fixed (3) | Fixed (3) | 2.98k ±0.7% | 4.8% lower | 62.70 | 13.6% lower | 2.615 | 474.7 |
318
+ | Puma | Scaling (3–16) | Scaling (3–16) | 2.64k ±3.2% | 17.9% lower | 59.81 | 12.7% lower | 2.522 | 567.4 |
319
+ | Falcon | Fibers | Fixed (3) | 3.65k ±7.0% | 16.4% higher | 46.10 | 36.5% lower | 2.311 | 389.2 |
320
+ | Falcon | Fibers | Scaling (3–16) | 3.65k ±7.0% | 13.3% higher | 46.10 | 32.7% lower | 2.311 | 389.2 |
321
+
322
+ ### CPU over HTTP/1.1 keep-alive
323
+
324
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
325
+ | ------ | -------------- | --------------- | ----------- | --------------- | ------ | ------------- | ---------- | ------- |
326
+ | Raptor | Fixed (3) | - | 2.88k ±1.6% | - | 57.40 | - | 2.721 | 515.4 |
327
+ | Raptor | Scaling (3–16) | - | 2.81k ±3.8% | - | 56.14 | - | 2.721 | 528.2 |
328
+ | Puma | Fixed (3) | Fixed (3) | 3.24k ±0.7% | 12.3% higher | 60.73 | 5.8% higher | 2.449 | 406.4 |
329
+ | Puma | Scaling (3–16) | Scaling (3–16) | 2.71k ±3.6% | 3.3% lower | 70.56 | 25.7% higher | 2.459 | 487.6 |
330
+ | Falcon | Fibers | Fixed (3) | 3.62k ±1.0% | 25.8% higher | 62.03 | 8.1% higher | 2.369 | 542.0 |
331
+ | Falcon | Fibers | Scaling (3–16) | 3.62k ±1.0% | 29.1% higher | 62.03 | 10.5% higher | 2.369 | 542.0 |
332
+
333
+ ### CPU over HTTP/2
334
+
335
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
336
+ | ------ | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
337
+ | Raptor | Fixed (3) | - | 3.12k ±1.3% | - | 65.66 | - | 2.439 | 587.6 |
338
+ | Raptor | Scaling (3–16) | - | 3.15k ±0.4% | - | 74.95 | - | 2.344 | 609.0 |
339
+ | Falcon | Fibers | Fixed (3) | 3.49k ±20.7% | 11.7% higher | 120.58 | 83.6% higher | 2.309 | 536.3 |
340
+ | Falcon | Fibers | Scaling (3–16) | 3.49k ±20.7% | 10.7% higher | 120.58 | 60.9% higher | 2.309 | 536.3 |
341
+
342
+ ### 64 KiB body over HTTP/1.1 keep-alive
343
+
344
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
345
+ | ------ | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
346
+ | Raptor | Scaling (3–16) | - | 96.13k ±0.8% | - | 3.68 | - | 0.048 | 512.3 |
347
+ | Puma | Scaling (3–16) | Scaling (3–16) | 83.26k ±0.0% | 13.4% lower | 3.94 | 7.3% higher | 0.055 | 1344.6 |
348
+ | Falcon | Fibers | Scaling (3–16) | 98.30k ±0.5% | 2.3% higher | 3.89 | 5.7% higher | 0.051 | 471.5 |
349
+
350
+ ### IO + SSE (native) over HTTP/1.1 keep-alive
351
+
352
+ | Server | Mode | Raptor baseline | req/s | vs Raptor req/s | p95 ms | vs Raptor p95 | CPU ms/req | PSS MiB |
353
+ | ------------- | -------------- | --------------- | ------------ | --------------- | ------ | ------------- | ---------- | ------- |
354
+ | Raptor | Fixed (3) | - | 3.01k ±4.9% | - | 51.22 | - | 2.286 | 369.6 |
355
+ | Raptor | Scaling (3–16) | - | 7.49k ±0.8% | - | 24.39 | - | 0.488 | 372.7 |
356
+ | Puma | Fixed (3) | Fixed (3) | 0.39k ±13.8% | 87.0% lower | 314.80 | 514.6% higher | 1.250 | 282.9 |
357
+ | Puma | Scaling (3–16) | Scaling (3–16) | 7.51k ±0.4% | 0.4% higher | 24.23 | 0.6% lower | 0.464 | 336.3 |
358
+ | Falcon | Fibers | Fixed (3) | 7.57k ±4.4% | 151.9% higher | 24.04 | 53.1% lower | 0.375 | 355.5 |
359
+ | Falcon | Fibers | Scaling (3–16) | 7.57k ±4.4% | 1.2% higher | 24.04 | 1.4% lower | 0.375 | 355.5 |
360
+ | Raptor (enum) | Fixed (3) | - | 0.88k ±0.3% | - | 63.76 | - | 7.972 | 362.6 |
361
+
362
+ Puma, Fixed (3) had 28 timeouts, 1 stalled stream across all samples.
363
+
364
+ Raptor (enum), Fixed (3) had 29 timeouts across all samples.
365
+
366
+ > ruby 4.1.0dev (2026-10-27T15:28:18Z HEAD 33ac6afea7) +YJIT +PRISM [aarch64-linux]
367
+ > 10 workers, 120 HTTP/1.1 client connections, 40 HTTP/2 connections × 3 streams.
368
+
369
+ Run [bin/benchmark](bin/benchmark) to reproduce.
248
370
 
249
371
  ## Development
250
372
 
Binary file
@@ -0,0 +1,37 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1320" height="1008" viewBox="0 0 1320 1008" role="img" aria-labelledby="title description">
2
+ <title id="title">Raptor startup output</title>
3
+ <desc id="description">The complete startup example from the README, including every log prefix, the process tree, and all ten worker boot messages. Long lines wrap as they would in a terminal.</desc>
4
+ <rect x="1" y="1" width="1318" height="1006" rx="16" fill="#161b22" stroke="#30363d" stroke-width="2"/>
5
+ <path d="M1 60H1319" stroke="#30363d" stroke-width="2"/>
6
+ <circle cx="32" cy="30" r="8" fill="#ff5f57"/>
7
+ <circle cx="60" cy="30" r="8" fill="#febc2e"/>
8
+ <circle cx="88" cy="30" r="8" fill="#28c840"/>
9
+ <text x="660" y="37" text-anchor="middle" fill="#8b949e" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="20">raptor</text>
10
+ <g font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="24" fill="#e6edf3" xml:space="preserve">
11
+ <text x="32" y="110">&gt; bundle exec raptor -w 10 -t 3 hello_world.ru</text>
12
+ <text x="32" y="146">[Raptor 39704|Main|Main] Cluster initializing:</text>
13
+ <text x="32" y="182">[Raptor 39704|Main|Main] ├─ Version: 0.23.0</text>
14
+ <text x="32" y="218">[Raptor 39704|Main|Main] ├─ Ruby Version: ruby 4.0.9 (2026-10-23 revision 3e93b23afc) </text>
15
+ <text x="32" y="254">+YJIT +PRISM [arm64-darwin25]</text>
16
+ <text x="32" y="290">[Raptor 39704|Main|Main] ├─ Environment: development</text>
17
+ <text x="32" y="326">[Raptor 39704|Main|Main] ├─ Master PID: 39704</text>
18
+ <text x="32" y="362">[Raptor 39704|Main|Main] │ └─ 10 worker processes</text>
19
+ <text x="32" y="398">[Raptor 39704|Main|Main] │ ├─ 1 server thread</text>
20
+ <text x="32" y="434">[Raptor 39704|Main|Main] │ ├─ 1 reactor thread</text>
21
+ <text x="32" y="470">[Raptor 39704|Main|Main] │ ├─ 1 HTTP/1.1 pipeline ractor</text>
22
+ <text x="32" y="506">[Raptor 39704|Main|Main] │ ├─ 1 pipeline collector thread</text>
23
+ <text x="32" y="542">[Raptor 39704|Main|Main] │ ├─ 3 worker threads (scaling, no limit)</text>
24
+ <text x="32" y="578">[Raptor 39704|Main|Main] │ └─ 1 stats thread</text>
25
+ <text x="32" y="614">[Raptor 39704|Main|Main] └─ Listening on 0.0.0.0:9292</text>
26
+ <text x="32" y="650">[Raptor 39720|Main|Main] Worker 1 booted</text>
27
+ <text x="32" y="686">[Raptor 39719|Main|Main] Worker 0 booted</text>
28
+ <text x="32" y="722">[Raptor 39722|Main|Main] Worker 3 booted</text>
29
+ <text x="32" y="758">[Raptor 39721|Main|Main] Worker 2 booted</text>
30
+ <text x="32" y="794">[Raptor 39723|Main|Main] Worker 4 booted</text>
31
+ <text x="32" y="830">[Raptor 39725|Main|Main] Worker 6 booted</text>
32
+ <text x="32" y="866">[Raptor 39724|Main|Main] Worker 5 booted</text>
33
+ <text x="32" y="902">[Raptor 39728|Main|Main] Worker 8 booted</text>
34
+ <text x="32" y="938">[Raptor 39726|Main|Main] Worker 7 booted</text>
35
+ <text x="32" y="974">[Raptor 39730|Main|Main] Worker 9 booted</text>
36
+ </g>
37
+ </svg>