matthias_toepp
How much more crashproof is a server with Elixir/Beam compared to Rust's axum?
I posted the following also in rust’s reddit https://www.reddit.com/r/rust/comments/1f39vhz/how_much_more_crashproof_is_a_server_with/
My understanding is that because elixir uses the beam vm it has per process isolation which is basically used to guarantee that the whole server will not crash on any one request. As I understand it, this is an advantage over what a framework like Rust’s Axum offers, is this correct? How significant of an advantage is this really over presumably just restarting the whole server? (I get that there is a performance tradeoff for gaining this robustness).
Most Liked
LostKobrakai
This is comparing apples with oranges. Rust is all about preventing crashes. The BEAM is about allowing for crashes by isolating them as best as possible and letting the system as a whole stay up even in the face of things going sideways in a context, where things going sideways by definition cannot be prevented – distributed systems communicating over networks.
SichangHe
What you want to know is not whether they are crash-proof, but whether they are fault-tolerant. Let me try to provide some good points as opposed to being a fan of either side.
- Crash-proof: Tokio isolates panic unwinding within each task; the Erlang VM isolates exception throwing within each process. Neither of them should “crash” unless, e.g. you do
panic = abort, or use a NIF that SegFaults, respectively. However, the Erlang VM has a tiny error kernel, so its implementation is more reliable in theory. - State corruption: Tokio tasks share memory through synchronization primitives, so state corruption is more likely to mediate from one task to another. Erlang processes do not share memory, and instead pass message around by copying them, so states of Erlang processes are highly isolated. Axum encourages shared state through
Clone + Send + Sync + 'staticstructs (e.g.,Arc<Mutex<_>>) by feeding them as magic arguments to request handlers, which can be a large source of state corruption; Erlang only allows the actor model, and encourages processes to not expect other processes to reply. Note that unwind should only cause low-level memory corruption if it is notUnwindSafe. - Scheduling: Tokio tasks are cooperatively scheduled, so you should call
yield_nowfor long-running functions and run blocking code (e.g.syscall) on a blocking thread, or else you can suffer from stalls; Erlang processes are preemptively scheduled, so you can do stupid things and get away with it. - Debugging: When you do have faults, Erlang provides a superior interactive debugging experience. The Tokio debugger is highly experimental and people just read the logs or traces instead.
- Mindset: Rust is perfect for problems you understand as a whole, and problematic for situations with many unknown unknowns; Erlang takes the burden off of developers shoulders by applying the end-to-end argument on fault-tolerance. This is why I tend to reach for Axum when I need to serve just a few well-defined requests, a small problem, but reach for Phoenix whenever I feel the problem is larger or I do not fully understand it.
Hope this helps!
Steven Hé (Sīchàng)
jhogberg
Yes and no; those actor frameworks will not help you manage runaway tasks (nor will they grant you much in the way of introspection). In any M:N setup without pre-emption, a job that takes too long will screw up responsiveness, giving you bad latency of unrelated tasks in the best case, and locking things up entirely in the worst.
If we define “crash” as “the system no longer does what it is supposed to do,” Rust et al pushes way more responsibility to avoid them onto the programmer than Elixir.
There’s no free lunch however, and the trade-offs may tilt in Rust’s favor anyway, but I find “language X also has actors with supervision” a bit reductionist. I’ve yet to see any other solution that supports all aspects of resilience (by Hollnagel’s definition) as well as Elixir/Erlang does.
dimitarvp
To expand a little on Rust: both Axum and ActixWeb are amazing frameworks and I am certain you’ll never have an OS process crash because of them. Still, you the developer can write a non-crash-proof code inside your responders / handlers and then all bets are off.
In Elixir you can straight up try writing this inside your controller function(s):
something = nil
IO.inspect(something.inserted_at)
Which is an error, but nothing will stop, just one request will fail and everything else goes as normal with zero capacity affected.
In Rust, if you write this:
value = None;
stuff = value.unwrap();
Then your entire program might get aborted and die on the spot. Though final disclaimer: I haven’t looked at Axum and ActixWeb in at least a year so maybe double-check; maybe they have added protections against defective code like this since the last time I checked them out.
sbuttgereit
Isn’t it true that what we’re really talking about two tools that are really aimed at different problems?
When I look at Rust, I see something that gets me in close contact with the running system with few abstractions: yes there are rules enforced by the borrow checker, rules about mutable state, and lifetimes, etc. But all of that is to protect me from the low level access I have (and all of which I can bypass with unsafe if I need to). Rust is a systems language.
When working with the BEAM VM, aren’t I really working at a different level of abstraction? Moreso than by using this or that library? A lot of that lower level stuff is dealt with by the BEAM and as an Elixir developer I’m largely protected from those details.
At least that’s my hand-wavy understanding.
I think this is why the earlier “Apples and Oranges” comment is really justified. The trade-offs are different, the guarantees are different, and all of those choices where made in efforts to satisfy different needs. Sure, there’s overlap in applicability, but I’d personally take the larger picture and consider what my application needs the most: many web apps don’t benefit from systems level programming… in which case allowing the good people building the BEAM handle those complexities for me is a win (especially considering they’ll likely deal with that level of complexity better than I would as a business systems programmer). However, at a certain level of performance requirement or computational complexity that situation could change (and even then I might write rust on the hot path and allow the BEAM to orchestrate the whole thing). Trying to straight up compare them without a better sense of these larger goals seems misguided.
Anyway… my two bits.








