ruvoy 0.1.0-aarch64-linux → 0.2.0-aarch64-linux
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 +4 -4
- data/README.md +149 -7
- data/lib/ruvoy/4.0.0/libruvoy_fiber.so +0 -0
- data/lib/ruvoy/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 570d5fa414a87e4f204964ad1dac4b45527948bb3fed5473559cc8698cb80689
|
|
4
|
+
data.tar.gz: 2d49878f9c9add538a8560d93988cc9c1fcbc838839f79f12e4a2f064823c739
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61fd6ebec19502a5160627811938fdcca59c4fdeceed34eaea0f9cc1869d85aa9a6280ea98ae1a984bd6b2733a083c643976fd10c210c93c4aee304ba5d8bd6c
|
|
7
|
+
data.tar.gz: 0ca15aefdd13ae67dd6c90ab67e4fd98f6d6fd3a0c72bc0da7f2983b32cd16a4c2a585daf5d0bddd7b8465695e0cdb5fcc67627da729aefc13e58a76719f5837
|
data/README.md
CHANGED
|
@@ -19,17 +19,18 @@ speaks Rack, and owned Rust messages bridge the two.
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
21
21
|
```console
|
|
22
|
-
$ gem install ruvoy
|
|
22
|
+
$ gem install ruvoy ruvoy-envoy
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
The gem carries the dynamic module
|
|
26
|
-
|
|
25
|
+
The `ruvoy` gem carries the dynamic module. Envoy comes from the `ruvoy-envoy`
|
|
26
|
+
gem or the machine: ruvoy looks for `RUVOY_ENVOY`, then `ruvoy-envoy`, then
|
|
27
|
+
`envoy` on `PATH`.
|
|
27
28
|
|
|
28
29
|
| | |
|
|
29
30
|
|---|---|
|
|
30
31
|
| Ruby | 4.0.x. The module links against the Ruby it was built for, so the gem is published per ABI. |
|
|
31
32
|
| Platform | Linux `x86_64` and `aarch64`, matching the platforms Envoy publishes binaries for. |
|
|
32
|
-
| Envoy |
|
|
33
|
+
| Envoy | 1.39 (dynamic module ABI 0.1.0), from the `ruvoy-envoy` gem or the machine. |
|
|
33
34
|
|
|
34
35
|
## Quickstart
|
|
35
36
|
|
|
@@ -54,7 +55,8 @@ Usage: ruvoy [options] [config.ru]
|
|
|
54
55
|
|
|
55
56
|
To run it under an Envoy you configure yourself, `--print-config` prints the
|
|
56
57
|
listener Ruvoy would have used; the module is `ruvoy_fiber` and its filter
|
|
57
|
-
config is the path to the rackup
|
|
58
|
+
config is the path to the rackup, or a JSON object naming it (see
|
|
59
|
+
[Envoy context and upstream calls](#envoy-context-and-upstream-calls)).
|
|
58
60
|
|
|
59
61
|
## Status
|
|
60
62
|
|
|
@@ -83,6 +85,9 @@ threads flat. Interfaces may change.
|
|
|
83
85
|
reaches Ruby.
|
|
84
86
|
- **No upstream HTTP hop** — Rack calls do not require a loopback connection to
|
|
85
87
|
a separate Ruby application server.
|
|
88
|
+
- **Envoy context and upstream calls** — optionally, the application sees the
|
|
89
|
+
route, connection, TLS and metadata Envoy has for the request, and calls
|
|
90
|
+
Envoy's clusters from inside it.
|
|
86
91
|
|
|
87
92
|
## With and without Ruvoy
|
|
88
93
|
|
|
@@ -130,7 +135,10 @@ stops the enumeration and closes the body.
|
|
|
130
135
|
Request bodies stream as well: the application is called once the headers
|
|
131
136
|
arrive and reads through `rack.input` as the body lands, so uploading and
|
|
132
137
|
processing overlap. What the application has not read yet waits in Envoy, which
|
|
133
|
-
stops reading from the client until there is room again.
|
|
138
|
+
stops reading from the client until there is room again. What it has read is
|
|
139
|
+
kept, in memory up to 128 KiB and in a temporary file beyond that, so
|
|
140
|
+
`rack.input` can be rewound and read again, as frameworks written for earlier
|
|
141
|
+
Rack versions expect. Raw socket hijacking
|
|
134
142
|
(`rack.hijack`) is not supported; the connection always belongs to Envoy.
|
|
135
143
|
|
|
136
144
|
A response body that fails after its headers are already on the wire can only
|
|
@@ -138,6 +146,140 @@ stop: a dynamic module cannot reset a stream it has started answering, so the
|
|
|
138
146
|
client receives a short body rather than an error. Applications that can fail
|
|
139
147
|
partway through should send their own length or checksum.
|
|
140
148
|
|
|
149
|
+
## Blocking calls
|
|
150
|
+
|
|
151
|
+
A fiber waits without holding the runtime thread only when the call it waits
|
|
152
|
+
on cooperates with Ruby's fiber scheduler: Ruby's own sockets and IO, `sleep`,
|
|
153
|
+
and the asynchronous APIs many drivers offer do. A C extension call that
|
|
154
|
+
blocks in the kernel instead holds the one thread every request shares, so
|
|
155
|
+
requests queue behind it: fifty-millisecond calls of that kind serve about
|
|
156
|
+
twenty requests a second however many clients wait.
|
|
157
|
+
|
|
158
|
+
`Ruvoy.offload` runs such a call on its own Ruby thread while the calling
|
|
159
|
+
fiber waits:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
rows = Ruvoy.offload { connection.sync_exec(sql) }
|
|
163
|
+
|
|
164
|
+
# At most 16 in flight; callers beyond that wait their turn.
|
|
165
|
+
DATABASE = Ruvoy::Offload.new(limit: 16)
|
|
166
|
+
rows = DATABASE.call { connection.sync_exec(sql) }
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
- It helps extensions that release the GVL while they block, which is how
|
|
170
|
+
blocking C calls are normally written. One that holds the GVL stalls every
|
|
171
|
+
thread, and nothing on the Ruby side helps.
|
|
172
|
+
- A call cannot be cut short. When the caller is interrupted, because its
|
|
173
|
+
client went away or a timeout fired, it still waits for the call to finish
|
|
174
|
+
before it unwinds, so a connection the call is using is not handed back
|
|
175
|
+
while in use.
|
|
176
|
+
- The block runs on another thread, so thread-local state, such as a
|
|
177
|
+
per-thread connection, belongs to that thread rather than to the caller.
|
|
178
|
+
- Prefer a driver's scheduler-aware API where it has one. `offload` costs a
|
|
179
|
+
thread per call and bounds nothing unless given a limit.
|
|
180
|
+
|
|
181
|
+
## Rails
|
|
182
|
+
|
|
183
|
+
Rails applications run unchanged, with one setting every fiber server needs:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
config.active_support.isolation_level = :fiber
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Active Record hands out connections per thread by default, and every
|
|
190
|
+
request's fiber runs on the one runtime thread, so without it concurrent
|
|
191
|
+
requests share a single connection. With it, each request in flight holds a
|
|
192
|
+
connection of its own.
|
|
193
|
+
|
|
194
|
+
- Action Cable is not supported: it needs `rack.hijack`.
|
|
195
|
+
- Requests reach Rails through Envoy, which adds `X-Forwarded-Proto` to what
|
|
196
|
+
it passes on.
|
|
197
|
+
- Envoy serves static files ahead of the application, so
|
|
198
|
+
`config.public_file_server.enabled = false` spares Rails a filesystem lookup
|
|
199
|
+
on every request: about a fifth more throughput on small responses here.
|
|
200
|
+
- `test/e2e/rails_app_test.rb` runs a production Rails 8.1 application on
|
|
201
|
+
Ruvoy and on a reference server, compares every answer, and checks
|
|
202
|
+
connections under concurrency and when a client leaves mid-transaction.
|
|
203
|
+
|
|
204
|
+
## Envoy context and upstream calls
|
|
205
|
+
|
|
206
|
+
Two optional capabilities give the application more of Envoy than a Rack
|
|
207
|
+
request carries. Both stay off unless the filter configuration asks for them,
|
|
208
|
+
and both are available on the fiber runtime only. The configuration is then a
|
|
209
|
+
JSON object:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"rackup": "/srv/app/config.ru",
|
|
214
|
+
"context": { "metadata_namespaces": ["acme.tenant"] },
|
|
215
|
+
"upstream": { "clusters": ["users"], "timeout_ms": 15000, "max_response_bytes": 1048576 }
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### `ruvoy.context`
|
|
220
|
+
|
|
221
|
+
Envoy's view of the request, copied on the worker when the headers arrive.
|
|
222
|
+
Ruby values are only built when the application reads them.
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
context = env["ruvoy.context"]
|
|
226
|
+
context.route_name # => "api", or nil for an unnamed route
|
|
227
|
+
context.connection # => {"id"=>7, "source_address"=>"10.0.0.7", "source_port"=>51234, ...}
|
|
228
|
+
context.tls # => nil on plaintext, else {"version"=>"TLSv1.3", "server_name"=>..., "peer_certificate"=>...}
|
|
229
|
+
context.dynamic_metadata # => {"acme.tenant"=>{"id"=>"t-42"}}, from filters ahead of Ruvoy
|
|
230
|
+
context.route_metadata # => the same namespaces, from the matched route
|
|
231
|
+
context.to_h # => all of the above
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
What Envoy's module interface can report sets some limits:
|
|
235
|
+
|
|
236
|
+
- A route has a name only if the route configuration gives it one. A terminal
|
|
237
|
+
filter still needs a route entry to be named: give it
|
|
238
|
+
`non_forwarding_action: {}`.
|
|
239
|
+
- `server_name` is the SNI the client sent, which Envoy records only when the
|
|
240
|
+
listener has the TLS inspector listener filter.
|
|
241
|
+
- `peer_certificate` means the client presented a certificate. It identifies
|
|
242
|
+
the client only on a listener that requires client certificates signed by a
|
|
243
|
+
trusted CA.
|
|
244
|
+
- Only the first URI and the first DNS subject alternative name are reported.
|
|
245
|
+
- Metadata values are strings, numbers (always Float), booleans and lists of
|
|
246
|
+
those. Nested structures are left out.
|
|
247
|
+
|
|
248
|
+
### `ruvoy.upstream`
|
|
249
|
+
|
|
250
|
+
Calls a cluster Envoy was configured with and returns a Rack-style triple:
|
|
251
|
+
|
|
252
|
+
```ruby
|
|
253
|
+
status, headers, body = env["ruvoy.upstream"].call(
|
|
254
|
+
"users", "GET", "/users/42",
|
|
255
|
+
headers: { "accept" => "application/json" }, timeout: 2
|
|
256
|
+
)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The request's worker sends the call through the cluster, so the cluster's
|
|
260
|
+
connection pool, TLS settings and circuit breakers apply, and headers such as
|
|
261
|
+
`x-envoy-retry-on` ask Envoy to retry. The calling fiber waits without holding
|
|
262
|
+
the runtime thread, so other requests keep being served and calls made from
|
|
263
|
+
one request overlap. A repeated response header arrives as an Array.
|
|
264
|
+
|
|
265
|
+
- Only the clusters in `upstream.clusters` can be called; any other raises
|
|
266
|
+
`ArgumentError`.
|
|
267
|
+
- Answers Envoy produces while the call is under way arrive as ordinary
|
|
268
|
+
responses: 504 when it timed out, 503 when the upstream could not be reached
|
|
269
|
+
or hung up.
|
|
270
|
+
- `Ruvoy::UpstreamError` is raised when the call could not be sent: the cluster
|
|
271
|
+
does not exist, or turned the call away at once because it has no healthy
|
|
272
|
+
host or a circuit breaker is open. It is also raised when the request finished
|
|
273
|
+
before the call did.
|
|
274
|
+
- A response body over `max_response_bytes`, or over Envoy's own buffer limit,
|
|
275
|
+
raises `Ruvoy::UpstreamResponseTooLarge`, a subclass of `Ruvoy::UpstreamError`,
|
|
276
|
+
so a caller can tell asking for less apart from trying again later.
|
|
277
|
+
- Responses are buffered whole, so `max_response_bytes` bounds what one call
|
|
278
|
+
can hold in memory.
|
|
279
|
+
|
|
280
|
+
[`examples/extensions`](examples/extensions) runs both against a stand-in
|
|
281
|
+
service, including two calls from one request overlapping.
|
|
282
|
+
|
|
141
283
|
## Scaling
|
|
142
284
|
|
|
143
285
|
Each Ruvoy process runs exactly one CRuby VM, so Ruby work in one process is
|
|
@@ -258,7 +400,7 @@ Four honest qualifications:
|
|
|
258
400
|
Not measured: CPU-bound Rack, real database or HTTP-client drivers, and
|
|
259
401
|
multi-process deployment.
|
|
260
402
|
|
|
261
|
-
Measurements were produced by `
|
|
403
|
+
Measurements were produced by `bench/run.rb`, which enforces the
|
|
262
404
|
protocol above — remote load generation, rotation, warm-up, per-round
|
|
263
405
|
validation, and the pre-registered judgement — and writes raw oha output,
|
|
264
406
|
CPU and RSS samples, and the generated summary for every run.
|
|
Binary file
|
data/lib/ruvoy/version.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ruvoy
|
|
4
|
-
# Kept in step with the Cargo workspace version by
|
|
4
|
+
# Kept in step with the Cargo workspace version by test/e2e/cli_test.rb: the
|
|
5
5
|
# gem and the module it carries are the same release.
|
|
6
|
-
VERSION = "0.
|
|
6
|
+
VERSION = "0.2.0"
|
|
7
7
|
end
|