raptor 0.23.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1a18ea2df37b0221bea185c5c5a275a08eda61f059056b5f7599bf71be9661f
4
- data.tar.gz: 7586e065cf01603b2fc78f04c75768dfcea43314e77fb5b6485a4dfabe7d01f0
3
+ metadata.gz: c8c3462777a399cad50b6131c497c98ab519850ff1d88d062b681cc05c9d540b
4
+ data.tar.gz: 878a2629b500d8873836cda4bd463f0dcb727a33bd301fc5a246cd860a3f0957
5
5
  SHA512:
6
- metadata.gz: 706c2c28172d8ca0613ed8d2d27dea007205af4142f96b548147d6aefeb4e9a3ddece60b9035cca7a72e0149408dfdadb3893cdfbfb7696f1d4b69b831b05966
7
- data.tar.gz: e04215e61ac7ef2e306211615b17bf64a7c8341b2c701fb4b41e88ef75262310b65aedc310dfe758a5ed0cb8b7a5b442b2ffa49e6d5a9ddd55844ce539b91be5
6
+ metadata.gz: 0d9b05f416a7024c91dc2861d6f748eadb1c20e01224c3e82a1202247f48220e56f81742fa600f41f1885c60587464b9506c0e2855d9392767016f5199bb0d94
7
+ data.tar.gz: 93403bcdbfaef05e6a011cc795ef66b7713dc2ed4266c157afb3f5eb1b53ae60c8f7610e60d2caee75e8b82716e58cac6b771543d6f9d43823cb03e5eed87dd8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
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
+
3
19
  ## [0.23.0] - 2026-10-18
4
20
 
5
21
  - Route connections through BPF dispatch on every TCP bind
data/Dockerfile CHANGED
@@ -1,5 +1,11 @@
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
+
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 .
3
9
 
4
10
  FROM rubylang/ruby:master-dev
5
11
 
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.23.0
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,50 +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.23.0 vs Puma 8.0.2 vs Falcon 0.57.0 across two workload profiles. **IO-bound** is a GET endpoint that spends
212
- about 95% of each request waiting. It makes 5-10 simulated DB calls of 0.5-1.5ms each and renders a small JSON result
213
- after each one. **CPU-bound** is a POST endpoint that spends about 85% of each request running Ruby. It accepts a small
214
- JSON body, renders 2,100-7,000 JSON items in 3-5 chunks, each after a 25-50µs simulated cache hit, and returns a short
215
- response. **IO + SSE** repeats the IO-bound keep-alive run while 2 server-sent event streams per worker stay open,
216
- each receiving an event every second. Raptor serves them with `Raptor::DetachedBody`, which hands the stream to the
217
- reactor and frees the application thread, while Puma and Falcon stream from an Enumerator that sleeps between events.
218
- Puma keeps accepting connections while every thread in a worker is busy, so a stream or request it accepts can wait
219
- behind streams that never finish. Streams that got no response within 2 seconds and requests that hey gave up on
220
- after 20 seconds are counted in parentheses.
221
-
222
- Raptor and Puma are run in two modes: **Fixed** keeps 3 application threads per worker, while **Scaling** starts with 3
223
- and may grow to 16. Puma adds a thread whenever work is waiting, while Raptor only adds one when queued work is blocked
224
- outside the GVL. Falcon is compared against both modes. Raptor request-local cleanup and Puma's Fiber-per-request mode
225
- are disabled, and both threaded servers allow 999 requests per HTTP/1.1 keep-alive connection.
226
-
227
- Each cell reports the median throughput and median p95 latency independently across 3 runs, so the two numbers in a row
228
- may come from different runs. Every run starts a fresh server process so the samples are independent of each other;
229
- state accumulated in a previous run cannot bias the next. Across the whole table, the widest spread
230
- ((max - min) / 2 / median) between runs of a single cell was ±48.9% for throughput and ±38.0% for p95.
231
-
232
- | Protocol | Workload | 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 |
233
- | --------------------- | -------- | ------- | ------------ | ---------- | ------------------------- | --------- | ------------- | ------------ | ------------ | ---------- | --------------- | ------------- |
234
- | HTTP/1.1 | IO | Fixed | 3.41k req/s | 45.60 ms | 1.48k req/s | 127.40 ms | 130.1% higher | 64.2% lower | 10.72k req/s | 16.40 ms | 68.2% lower | 178.0% higher |
235
- | HTTP/1.1 | IO | Scaling | 8.28k req/s | 21.90 ms | 8.06k req/s | 22.70 ms | 2.7% higher | 3.5% lower | 10.72k req/s | 16.40 ms | 22.8% lower | 33.5% higher |
236
- | HTTP/1.1 | CPU | Fixed | 3.54k req/s | 62.40 ms | 3.44k req/s | 52.20 ms | 2.9% higher | 19.5% higher | 3.57k req/s | 45.90 ms | 1.0% lower | 35.9% higher |
237
- | HTTP/1.1 | CPU | Scaling | 3.40k req/s | 63.40 ms | 3.14k req/s | 51.80 ms | 8.0% higher | 22.4% higher | 3.57k req/s | 45.90 ms | 4.9% lower | 38.1% higher |
238
- | HTTP/1.1 (keep-alive) | IO | Fixed | 3.38k req/s | 37.50 ms | 1.46k req/s | 102.20 ms | 131.7% higher | 63.3% lower | 7.23k req/s | 25.20 ms | 53.2% lower | 48.8% higher |
239
- | HTTP/1.1 (keep-alive) | IO | Scaling | 7.38k req/s | 24.70 ms | 7.40k req/s | 24.70 ms | 0.3% lower | 0.0% higher | 7.23k req/s | 25.20 ms | 2.0% higher | 2.0% lower |
240
- | HTTP/1.1 (keep-alive) | CPU | Fixed | 3.34k req/s | 49.70 ms | 3.45k req/s | 53.90 ms | 3.3% lower | 7.8% lower | 3.71k req/s | 57.80 ms | 9.9% lower | 14.0% lower |
241
- | HTTP/1.1 (keep-alive) | CPU | Scaling | 3.30k req/s | 49.70 ms | 3.06k req/s | 62.90 ms | 8.0% higher | 21.0% lower | 3.71k req/s | 57.80 ms | 10.9% lower | 14.0% lower |
242
- | HTTP/1.1 (keep-alive) | IO + SSE | Fixed | 3.33k req/s | 41.90 ms | 0.35k req/s (21 timeouts) | 323.40 ms | 852.5% higher | 87.0% lower | 7.26k req/s | 25.10 ms | 54.2% lower | 66.9% higher |
243
- | HTTP/1.1 (keep-alive) | IO + SSE | Scaling | 7.33k req/s | 24.80 ms | 7.16k req/s | 25.50 ms | 2.4% higher | 2.7% lower | 7.26k req/s | 25.10 ms | 1.0% higher | 1.2% lower |
244
- | HTTP/2 | IO | Fixed | 1.23k req/s | 138.77 ms | N/A | N/A | - | - | 6.54k req/s | 34.34 ms | 81.2% lower | 304.2% higher |
245
- | HTTP/2 | IO | Scaling | 6.87k req/s | 31.13 ms | N/A | N/A | - | - | 6.54k req/s | 34.34 ms | 5.2% higher | 9.3% lower |
246
- | HTTP/2 | CPU | Fixed | 3.37k req/s | 68.35 ms | N/A | N/A | - | - | 1.75k req/s | 158.85 ms | 92.7% higher | 57.0% lower |
247
- | HTTP/2 | CPU | Scaling | 3.28k req/s | 62.50 ms | N/A | N/A | - | - | 1.75k req/s | 158.85 ms | 87.5% higher | 60.7% lower |
248
-
249
- > ruby 4.1.0dev (2026-10-17T18:56:13Z master 5b1c373522) +YJIT +PRISM [aarch64-linux]
250
- > 10 worker processes; fixed Raptor and Puma run 3 threads per worker; scaling Raptor and Puma run 3 to 16;
251
- > Falcon runs unbounded fibers per worker; 120 concurrent HTTP/1.1 client connections; 40 concurrent HTTP/2 client
252
- > connections × 3 streams each; 20 open SSE streams in IO + SSE runs
253
-
254
- 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.
255
370
 
256
371
  ## Development
257
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>