The BEAM marches forward
2020-10-26Underjord is a tiny, wholesome team doing Elixir consulting and contract work. If you like the writing you should really try the code. See our services for more information.
The BEAM is the virtual machine that Erlang and Elixir runs on. It is widely cited as a battle-tested piece of software though I don’t know in which wars it has seen action. It has definitely paid its dues in the telecom space as well as globally scaled projects such as Whatsapp and Discord. It is well suited to tackle soft-realtime distributed systems with heavy concurrency. It has been a good platform chugging along. And with a small team at Ericsson responsible for much of its continuing development it has been managed in a deeply pragmatic way. Erlang has always been a bit of a secret and silent success. Almost no-one uses it if you look at market shares. But among the ones that use it there seems to be a very positive consensus. And then Elixir came and caused a bit of a boom. I think the BEAM has benefited from Elixir and Elixir wouldn’t exist without the BEAM. With that bit of background I’d like to shine a light on some cool developments that I think makes the BEAM more interesting or even uniquely interesting in the future.
The JIT is here (soon, OTP 24)
With OTP 24 landing sometime next year we are going to get the a JIT for the BEAM. Based on the project AsmJit this will mean that some BEAM code will be translated to native instructions. It will not be the kind of warm-up-for-performance-gains JIT that I’ve heard of in PyPy but rather significantly simpler. The goal of the project was to introduce a JIT that could give performance gains for some cases but would not cause any performance regressions. A pragmatic and laudable approach. Considering this made the Jason JSON-library (written in Elixir) beat the Jiffy JSON-library (written as a C NIF) in some tests I think this has the potential to obviate the need for some NIF implementations. Avoiding reaching out to the lower level code that is more capable but more dangerous is a good win.
Anyone running RabbitMQ should look forward to the update as measurements indicate 30-50% increased message throughput. Which is a nice thing to get for no code changes at all.
Pushing the performance of the BEAM closer to native is magnificent. To be clear the BEAM is already quite a good performer. I would put Erlang and Elixir at the abstraction level of languages like Python/Ruby/Node.js. Python and Ruby are poor performers. The Python ML stuff all goes into C++ or similar for performance. I’ve worked a bunch with Python and the things I hear from the Ruby world makes them sound quite equivalent in performance. They are a bit slow and can only do one thing at a time. Node.js is a bit different. It can do multiple things at a time, if you append asterisks and squint. It does it largely the same way Python + Gevent does it. This approach is incredibly susceptible to CPU-bound work causing head-of-line blocking. It becomes the single most important consideration for building a performant application “get to IO, don’t compute”. V8 that Node.js runs on is heavily optimized and fast for such a dynamic language. I think the BEAM provides a better approach that can deliver comparable results without as many footguns (opportunities for shooting yourself in the foot). But getting better at the raw crunching is a big gain with this JIT implementation and I look forward to the release.
Lumen - Static compilation & WASM
The Lumen project is a huge effort by a gang of open source developers and DockYard to implement a compiler (and more) that can take Elixir and Erlang into the browser. By solving that they end up solving static compilation for Erlang and Elixir as well. So this isn’t compiling and shipping the BEAM to the browser. This is a faithful reimplementation of the BEAM functionality in a way that allows it to be compiled statically. It uses LLVM and requires quite a bit of effort both in development and in wrangling the Web Assembly work group process stuff to make sure that the standard is not entirely run by Object-Oriented Programming needs.
I don’t think Lumen will replace the BEAM. The BEAM has a brilliant track record for long-running services and distributed computing that the Lumen project do not even attempt to achieve right now. Instead the Lumen project will allow Elixir and Erlang to move into spaces where the BEAM might be a bit too heavy and still provide the same guarantees. Typically I see it being good for command line tools, web frontends (super interesting to consider the Actor model going there), serverless/edge computing and potentially with WASM competing with Docker as a delivery mechanism for code in Kubernetes, using something like Krustlet (good podcast episode on WASM/Krustlet). It’s probably Cloud Native or something. Who knows.
What gives Lumen the potential to be a better fit in these circumstances is that it can optimize for filesize (by cutting out hot code updates) and it is likely able to start much faster. Lumen is written in Rust. Which seems to be the popular choice around Web Assembly from what I’ve seen. Lumen is still an early release project and not fit for production. But it is beeing actively pushed forward.
Nerves - An IoT platform with minimal suck
The Nerves project is fantastic. I’m a hardware hobbyist, not an IoT dev but I’ve worked a fair bit with Nerves and it is so, so good. What Nerves gives you when working with a Raspberry Pi for example is a way to let your code run all of the device. The BEAM is basically your operating system on top of a minimal Linux installation. The Linux you have is based on the solid foundation of Buildroot so it is quite feasible to modify it as you see fit. The big idea is that if you are running a Linux-level SBC already you might as well build on something that gives you the guarantees of the BEAM.
Beyond that the default setup encodes a lot of good embedded practices by default so that you avoid bricking devices with firmware updates, you get easy support for pushing firmware over the network or USB and much, much more.
There are a ton of good libraries for sensors and assorted hardware, as well as the common protocols like GPIO/SPI/I2C/UART. Networking support is well considered and has been reworked since I first started using Nerves a few years back (and it worked well then too). BLE is getting more and more good support recently.
The project also created NervesHub which is a solution for managing a fleet of devices by securely providing firmware updates, allowing the switching on of a remote console on devices if that’s a need on your product. I think the most recent stuff is a UI revamp and some serious work on binary diffed patches to minimize firmware update sizes for data-constrained deployments.
This is very much a production project and people are shipping hardware with Nerves. It keeps marching forward.
The BEAM can be your entire application
Saša Jurić, author of the much-acclaimed Elixir in Action book has produced a library called site_encrypt. It allows you to handle LetsEncrypt configuration without a separate webserver or actually using certbot.
Now this library is good and meaningful in its own right but the underlying idea is why I bring it up. The BEAM can be your entire application. This is something I’ve realized over time. Where in Python you would reach for Gunicorn to run you Django app and Nginx to protect Gunicorn from the big bad world.. The BEAM is made for this. Introducing an intermediate layer of Nginx (or another HTTP server) might actually be detrimental in that you now have two things you need to configure correctly and two pools of multi-core processing workers that care about this request/response cycle and can independently screw it up.
The BEAM was always built for this. OTP has a lot weird corners where you find interesting libraries such as wx
for WxWidgets (window management) and ssh
for both SSH client and server work I believe. Because it is meant to be delivered as a full solution. It can run and manage multiple different types of work inside of it. Gracefully. It doesn’t replace Kubernetes for the large deployment or polyglot environments. But it might very well mean you don’t actually need to go there early. Or you can reduce how much Ops you need in your Dev. If your entire stack is Elixir or Erlang front to back I think you have empowered your developers significantly.
There is already a move towards this where tools are converging that give us a lot of things out of the box that we’d otherwise need to move outside our application for. These are pragmatic 80-90% solutions. The normal solutions are still all there if you need to reach for them. But maybe you don’t. I see these as moves in the same vein:
- LiveView - We can reduce the amount of frontend we need to build that isn’t BEAM code (Elixir or Erlang), in some cases get rid of it entirely.
- Live Dashboard - Application insights right in your application stack instead of pushing them out to another solution.
- Phoenix PubSub - Distributed PubSub without requiring coordination via something like Redis.
- Phoenix Channels - Distributed PubSub over WebSockets using the above PubSub to coordinate delivery.
- Phoenix Presence - Distribute Presence. A CRDT-powered thing for maintaining information about if someone is connected to a channel or not, like chatrooms and online/offline. Using Channels.
Lowering complexity by keeping the solutions in a system you understand well is potentially very powerful. At some point many projects will need to pick up external dependencies such as Nginx, Redis or whatever. But I think there is something compelling about building your application inside a system that can do all of it quite well. Elixir and Phoenix already have significant mind-share in the startup world. I wouldn’t be surprised if this ends up being a very popular solution for startups. No frontend-specific code for the MVP, no New Relic or Mixpanel bill we make do with the Live Dashboard. Distribution is Erlang distribution + Swarm/Horde/Libcluster or something like that when we need it. And if we need “real-time” functionality we are well and truly covered.
I think this is a pragmatic approach that aligns with what Erlang has always strived to be, what Elixir has turned out to be and what many in the community want. I look forward to seeing if it grows the way I hope. We discuss this further in an upcoming episode of Elixir Mix (the podcast) with Saša Jurić. So keep an eye out for that.
I think this might be a future topic on its own. I am not done with this.
As easy as anything else
While the BEAM is incredibly capable, Elixir and Erlang are not difficult. There is usually a bit of a hump going from Object-oriented programming to Functional programming. But beyond that you are mostly working at the abstaction level of something like Python while not needing to be as mindful of class hierarchies. I find it easier to reason about. It isn’t trivial. No programming is at the start. But you aren’t working in some mad realm of math and abstract thought. At least not that you need to care about. Much like “being good at math” is not required for programming. You don’t need to be some kind of hardcore whizz to get stuff done in Elixir or Erlang. Elixir looks like Ruby if that helps. Or so they say. I never did Ruby.
It is definitely easier to reason about than Node.js and the event loop ever was to me.
Conclusion
I have a strong confidence in the BEAM as a runtime/VM to build my software on. There are certainly places where it fits poorly. I primarily work in web service development and there it fits perfectly.
If you have thoughts, feelings or even “more of a comment really” feel free to reach out at lars@underjord.io or via my Twitter @lawik. My newsletter is available below if you have interest in further writing.
Underjord is a 4 people team doing Elixir consulting and contract work. If you like the writing you should really try the code. See our services for more information.
Note: Or try the videos on the YouTube channel.