HTTP: a how-to for serving requests from actors, and whether an axum crate is worth it
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 60/100
- Issue type
- Documentation
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- rust
- Domain
- backend, documentation
Research direction
Read tellus/examples/how_to/ and the graceful-shutdown example, shut_down_gracefully.rs, then check how the documentation pages are organized on tellus.rs. The proposal is to add a “Serve HTTP from actors” how-to covering ask, status mapping, fire-and-forget tell, disconnect dead letters, and mailbox-full handling; done means the page and example explain those cases. The issue author offers to write it, so confirm the work is still available before starting.
Written by the indexing model from the issue text.
Description
I have been wondering whether tellus wants an HTTP layer. Akka has Akka HTTP on top of its actors and streams. tellus has axum handlers in the graceful shutdown how-to, in tellus-cluster-inspect and in the cluster demo, and no HTTP layer of its own. Three directions came to mind: a how-to page, an integration crate for axum, or a server of tellus's own. The appeal of an own server would be a cheaper path from the socket to the actor, so before thinking about it I measured what a handler pays for reaching an actor today.
What a handler pays for the actor
The same axum 0.8 server with routes that differ only in how the handler gets at a counter, driven by a closed-loop HTTP/1.1 client over loopback: 8 client threads, 64 keep-alive connections, 5 rounds of 3 s, medians (3 rounds for the work rows and the single connection). Linux aarch64 in Docker on an Apple M3 Max with 14 CPUs, tellus 0.5.3 from crates.io. The handlers do nothing but the call, so this is the worst case for the actor's share of a request. A native macOS run caps every route near 175k req/s on loopback and hides most of the difference, which is why the numbers come from the Linux VM.
| Handler | 1 worker, µs per request | 4 workers, req/s | 4 workers, against plain |
|---|---|---|---|
| formats a constant | 3.00 | 508,298 | |
| counter behind a mutex | 3.08 | 503,027 | -1.0% |
| hand-written Tokio task, unbounded channel plus oneshot | 3.48 | 449,900 | -11.5% |
tellus tell, a oneshot in the message |
3.74 | 449,986 | -11.5% |
tellus ask |
4.29 | 412,080 | -18.9% |
The hop into an actor and back costs about 0.7 µs per request on this machine, ask about 1.3 µs. The difference, about 0.5 µs, is ask's own per-call work, the timer and the boxed ReplyTo; I have not split the two, and that split is less stable between runs than the sum. A mutex costs under 0.1 µs. tellus is within 0.3 µs of the hand-written task at 1 worker and equal at 4, so most of the hop is the price of a second task at all.
Once the request does work, the number of actors matters more than any of this:
| 20 µs of spinning per request, 4 workers | req/s | p50 |
|---|---|---|
| in the handler | 146,596 | 436 µs |
| in 8 actors, round robin | 136,651 | 468 µs |
| in 1 actor | 43,025 | 1,486 µs |
On one connection the difference is about 1 µs on a round trip of 38 µs.
All of this comes from one small crate, which you can read or rerun from this gist: https://gist.github.com/cosmir17/6ca9d41f75fcb5e00a58a34b6506471d. Its README has the commands. The files behind the numbers:
server.rs: the axum server, one route per row above.client.rs: the closed-loop client.drive.py: runs the rounds and prints the medians.disconnect.py: the disconnect test mentioned below.results-linux-aarch64-docker-tellus-0.5.3.txt: the raw output these tables come from.
What Akka HTTP gets from its actors
Not a faster request path. A route is a function from RequestContext to Future[RouteResult], and the docs reach actors from it by tell and ask: the auction example in the introduction has a fire-and-forget ! in its PUT route and a 5 s ask in its GET route, the same hop as above. The strength the docs name is streaming, back-pressure through every layer and constant memory for large entities, plus the shared ActorSystem and coordinated shutdown. The migration guide from spray says it outright: spray offered an API based on actor messaging, Akka HTTP replaced it with streams. Rust already has the pieces that design needed. axum handlers and tellus actors are tasks on one Tokio runtime, hyper bodies are pull-based, and tower carries the limits.
The three directions
| how-to page | tellus-axum crate |
own server | |
|---|---|---|---|
| request path | ask, or tell plus oneshot |
the same | the same hop, since receive cannot own a socket |
| streaming, HTTP/2, TLS, middleware | hyper and tower | hyper and tower | ours, or a smaller axum on hyper |
| shutdown ordering | by hand, as in the how-to | packaged | follows the actor tree |
| cost | a page and an example | a small crate on two 0.x APIs | HTTP/1.1, HTTP/2, TLS and request smuggling, for good |
A crate could add: AskError to HTTP status (the orphan rule asks for a newtype, ? still works through From); MailboxFull as an immediate 503, with a tower concurrency limit as the back-pressure knob, the counterpart of ActorFlow.ask's parallelism; the tell plus oneshot reply for local actors, cheaper but without a per-call timeout and not remotable; health plus graceful shutdown as in the how-to; WebSocket or SSE bridges through helper tasks, since receive cannot await. Nothing in that list needs a server of our own. The one thing a crate cannot change sits in core: a client which disconnects while the handler awaits the ask makes the reply a dead letter, one warning per disconnect (disconnect.py shows it).
Precedents point the same way. actix-web started on the actix actors and no longer depends on them, actix-web-actors is deprecated, Akka.NET injects actors into ASP.NET Core and Orleans co-hosts with it.
Proposal
- A how-to page "Serve HTTP from actors" on tellus.rs, from an example in
tellus/examples/how_to/like the graceful shutdown one:askfrom a handler, the status mapping,tellfor fire-and-forget routes, the dead letter on disconnect, mailbox full as 503 against a concurrency limit. I would write it. - A
tellus-axumworkspace crate only once the page turns out not to be enough. - No server of our own.
Does that match how you see it? And is the page welcome, or is shut_down_gracefully.rs already as much HTTP as the docs should carry?
- Dominant language
- Rust
- Stars
- 37
- Forks
- 1
- Avg merge
- 6h 36m
- Merged PRs (30d)
- 123
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- No contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from tellus-rs/tellus
-
fix(cluster): keep a panicking encode of a remote reply from failing the responderPossibly taken @cosmir17 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
tellus-rs/tellus#312 · 2 comments ·
Maintainers usually reply within 1 day
-
docs: document the PostgreSQL store's schema for applications managing their ownPossibly taken @cosmir17 claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
tellus-rs/tellus#310 · 2 comments ·
Maintainers usually reply within 1 day
-
good first issue test
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 42/100
tellus-rs/tellus#311 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
Maintainers usually reply within 1 day
All issues in tellus-rs/tellus
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
ElectronicWave/Elemental#252 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
area:mcp effort:low P5 source:agent
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
jlcodes99/cockpit-tools#3103 ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
eliasstravik/herdr-projects#1032 ·