Unpacking Elixir: Resilience
underjord.io
underjord.io
> Fundamentally, if there is nothing useful your code can do on a failure, no mitigation, no meaningful fallback, then you might as well have it blow up and rely on the underlying tree to make whatever is most useful out of it.
I would add this is possible only because process heaps are isolated. Other systems with traditional threads/co-routines/green threads can mimic supervision trees, but unless they have isolated heaps and can safely crash at any time never affecting any other processes, it would be hard to achieve the same safety properties.
> Scheduling and preempting was intended to produce consistently low latencies but it also protects you from heavy workloads bogging things down, it prevents infinitely looping bugs from slowing the system to a crawl and in general makes things resilient to things not being ideal all the time.
Well put. Reliability was one of the initial requirements of Erlang, but soft real-time properties was another one. This is hard to do, but BEAM VM does a great job there. To get this right the VM has to be able to preempt processes, even if they run a tight CPU bound loop. A misbehaving process may not just crash, it could just endlessly recurse, burning CPU cycles, but still shouldn't bring the system down.
> The strategies are normally one_for_one, one_for_all and one_for_rest
Minor correction, the last one is rest_for_one https://www.erlang.org/doc/man/supervisor.html#supervision-p...
Obviously it depends on what you’re building and you can craft your own supervision structures if you want to. But most of the time you can get the benefits of BEAM resiliency without having to drop down into the details.
Its a common pitfall for more inexperienced devs and I think its just a symptom of the language conventions.
Everyone is taught genservers with a client-side API existing inside the same module as handle_ functions and it blurs the distiction some.
I think if the convention was to create a separate module to implement the client-side convenience functions then the client-server split would be more obvious.
It's my assumption that needless process-starting is from classic OO developers trying to get something closer to the objects they're used to. I'm not sure separating the client and API logic in tutorials would necessarily stop them from doing this. People who are hellbent on shoehorning familiar idioms into an unfamiliar language are going to do so regardless. But I see what you're saying.
I think it's more important to drill home that modules and processes have nothing to do with each other. I still catch myself conflating them once in a while three years in and it's caused the most confusion while trying to teach friends (although I'm not a particularly good teacher).
Both simple config and GenServers have their place in libraries. But if a library doesn't expose the fundamentals it gets gnarly quickly.
I don't believe the programming language matters much. What matters is one's familiarity with it. There are good tools and frameworks available for just about any moderately popular language. Every language has caveats. Every language has lots of pros and cons and when you average them out, they're essentially all the same.
Just a matter of personal preferences and habits.
For instance, take Mint (https://github.com/elixir-mint/mint):
> Mint is different from most Erlang and Elixir HTTP clients because it provides a process-less architecture.
Mint is a low-level library which doesn't make attempt to manage processes (including HTTP pooling).
In contrast, Finch (which builds on top of Mint) includes pool management:
https://github.com/elixir-mint/mint#connection-management-an...
It can take someone a bit off guard when they realise that the library they use provide a "default pool" they were not aware of, and that it can become a bottleneck etc.
It was originally implemented as a single GenServer. When you app started up it'd read the filesystem and create a map of svg name => svg contents in a process. Then each lookup call would send a message to that process and get back the contents.
This has had the flaw of making template rendering single threaded. It's far worse than a file read per lookup
Hence, a library which contains a service is likely to have their own internal supervision tree. Your code can then sort-of latch onto this tree and piggyback on its existence. This is very common in web applications because when a new connection is opened, you spawn a process to handle the connection. If connections are (de-)multiplexed into channels like in HTTP/2 and HTTP/3, you'd just move up one layer and spawn a process per channel.
Likewise, if you have a limited resource such as Postgres connections, they are usually maintained in a pool, and access is queued until a connection from the pool is ready for use. This can circumvent the need for something like pg_bouncer in some use-cases.
I can give an example where this could be useful, I once worked on a node-app that couldn't use clustering mode because reasons and we only had 1 server running this particular piece of code. It turned out that we had a bug that made the node app crash whenever a user posted some invalid data. The problem here was that the app itself was a SPA and the users could just keep on posting even if it failed and they did in frustration.
So then app crashed and took a few seconds to reload, then crashed again. This mean that the entire api went down while the user was posting and thus could not respond to any other requests. This would never happen in Elixir and the load would just continue being ok even if 100 users at the same time would keep posting bad data.
The bad thing about Elixir resilience is that it is only applied to application logic. The rest of the time shit can go wrong is just the same as any other app since most elixir projects use the same kind of tooling (postgres, some web server in front etc). Not that many seems to use the built in mnesia database, no downtime deployment etc. The BEAM comes with many cool feature in theory but very few actually utilizes them so this 99.99999% uptime rarely comes into effect. The amount of time I've had downtime on apps because of things in the application logic like the first story I mentioned has been very, very few and most of the time it's something else entirely and that thing does Elixir not really help with most of the time.
Sure you could utilize all the cool features of the BEAM but it seems like in the absolute majority of cases the amount of work is simply too great for it to be worth the time investment required.
It is key to run the thought process of system failure. You need to understand what will happen if a certain part of your system errors out in an unforeseen way and make sure your restart policies are likely to right the ship.
So if the supervisor (“parent”) has other children, they won’t be restarted
There was an article about the implementation of supervisors and you can’t use the “let is crash philosophy” in the whole erlang/beam stack but there is a small (loc) implementation in C which has to be proven safely in order for the “let it crash philosophy” to work.
I don’t know if this was the definition and implementation of a supervisor itself or a process itself.
Does anybody know what I mean and which article I am referring to? If so, I would be glad if you could post a link to the article
I actually do small PRs to update deps etc on those kinds of repo and people are usually are OK to merge.
Someone refernced ElixirConf US and some of the news-flow is certainly from that.
Elixir has been building a steady steam pressure for a long time and I think steady is still the case.
As someone writing about Elixir I will say that it has good traction here as a general rule. I think Erlang appeals to the CS-interested, Elixir appeals to a lot of Ruby and startup folks. The ecosystem also innovates a decent bit. I think it punches above its weight on the HN front page.
It is a healthy ecosystem and seems to grow.
(edit: a spelling)
Or some Typescript style front for Elixir?
[1]: In the sense you mean here, anyhow. It has a selection of atomic types, int, list, tuple, etc., but there's no "user" types as one would have a strong typing system for. It's not an exact match but if you imagined a programming language that had nothing but JSON, not Javascript, Javascript has user types, literally just JSON as your underlying data model, you get pretty close to BEAM/Erlang.
For now you have typespecs and dialyzer that tries to infer types and detect incorrect assumptions in code but I found it cumbersome to work with. Testing in Elixir is first class so I prefer that for now. You get doctests (documentation that is also a runnable test), like in Python, which I find very useful when looking at some package source.
So maybe Gleam as an easier way to integrate JS and Erlang / Elixir?
(Elixir is strongly typed btw)
Elixir is essentially the functional python.
"Elixir is a dynamically typed language"
Plenty of use-cases are made easy by the stack, from regular web work, API (https://transport.data.gouv.fr/swaggerui) to proxying (https://github.com/etalab/transport-site/tree/master/apps/un...) to real-time maps (https://transport.data.gouv.fr/explore), clustered maps (https://transport.data.gouv.fr/explore/gtfs-stops), XML queries building (https://transport.data.gouv.fr/tools/siri-querier?endpoint_u...)...
The maintenance story is very good too.
With ML being added (Nx/Axon etc), and mobile apps being in the works (LiveViewNative), it has become my everyday language & stack.
Why I love it:
- The mental model just clicks for me. The syntax is really simple and the semantics are consistent. There is no abstraction. It's all about processing data.
- The REPL (IEX) is way more than a REPL. It's "you inside your running program". You can poke around your code, draft it, debug it, right there. You can fire it on prod to understand a bug. Or use LiveBook, think of it like the Elixir version of Jupyter notebooks that can connect to your application.
- Real life performance is great, not because of speed but because of concurrency.
- The whole developer experience is great. Mix (the build tool, dependency manager, etc.) is simple, awesome. Dependencies are really rarely an issue.
- It's rock solid. In 3 years, I never had one downtime.
- LiveView is a god send. Not having to switch language for UI work is amazing, performance is great, and it's server side HTML which is amazing for SEO. My website is 99 on lighthouse without any crazy work.
- You need heavy computation and performance on some parts? Use rust, via rustler.
- You need to scale to multiple servers? It is distributed. Already. Just make sure to not have anti-patterns in your code.
- But the real kicker it's in its power due to the OTP platform. I think it's quite complex to grasp how much it's powerful when you haven't experienced it. Need to batch insert statements or rate limit api calls to a 3rd party service who can only accept one call per second per channel? A working simple solution is only 20 lines of code. Need to launch many different workflows running concurrently, keeping their state, recovering when crashing? 100 lines.
The exciting developments:
- Elixir NX ecosystem (NX, Bumblebee, etc): running and training AI models directly in Elixir, in a distributed way.
- Liveview Native and Elixir Desktop: two big initiative to bring Elixir to Desktop and Mobile applications.
- Gradual Typesystem. Jose Valim, the creator of Elixir, is working on that right now. I really liked the approach of set-theoretic types and the pragmatism of the approach. Hopefully it will be released in the not too distant future.
The "to improve":
I have the feeling that the platform (OTP) being the killer app per se of Elixir, the whole marketing of the ecosystem if 100% targeted towards developers. Which is good in many ways. But for the ecosystem to grow I think more initiatives towards business-type applications would be welcome. By example, there is only a few payment gateways libraries existing which is for me a sign of the lack of business audience.
Conclusion: Elixir made me a better developer, but most importantly a really productive one.
The tricky part is that Elixir is compiled, and liveview templates are not "files" but functions.
From here you have two choices:
- You create a set of primitives (blocks), and the layout/arrangement of these primitives is configurable via a config map.
- Or you do a hot-code reload: Call an api, check if new code is available, fetch it, compile it (Code.compile_string), load it (:code.load_binary ), swap it for long running processes ( GenServer.code_change callback by example ), and remove old code ( :code.purge )
The first solution is safer. The second solution is a kind of black magic and you would need a way to authenticate de source code before compiling.
I obviously have lots of questions but biggest one that relates to other convos I had on HN recently: did you use an off-the-shelf ecomm solution or roll your own?
So I started with a PHP solution ( Craft CMS + Commerce ) that was flexible so I can just focus on front end part.
But then Covid happened and our business boomed super quickly ( in a month we went: from 2 to 10 people and 2 to 450 deliveries ). I needed to build features quickly to handle our fulfillment processes and built a separate stack of tools in Elixir, initially for packaging lists, routing calculations, sticker printing , etc... I still have some very fond memories of these crazy times.
Then I needed to redo our front end. We went from like 50 products to 1000+ products. I needed great search, smarter default sorting, recommendations, etc... and I needed the whole stack to be fast. I rebuilt everything with Phoenix and Liveview, building a weird layer to map Ecto to the super complicated Craft db schema.
We are using Craft just for the cart and payment now. The only pages served by Craft are the payment details and thank you page.
But I'm working slowly on building a complete E-Commerce stack ( a very opinionated one ) that I'd like to open source once clean and proven.
Looking forward to hearing about a new ecomm lib :)
I'm also rolling my own as it's built for a specific use case. Not sure I will ever open source, that isn't on my mind at this point.