This is what I missed the most about Erlang when trying to use Akka's actors - the simplicity of writing a function that blocked until it received a message made for far more straight forward code.
Ok, WhatsApp does scale exceptionally well with very little resources, but those number aren't really accurate.
It is not billion messages / second, that would imply majority of the WhatsApp user were awake and send a message in the same second. But right now it may be more than a million per second. During its FreeBSD era ( They switched to Linux when Facebook bought them ) it was doing ~400K messages / sec during peak. And it is not 1000, during it FreeBSD era is was handling it with less than 150 server for Chat. And that was with a very non powerful 10-Core Ivy Bridge Servers. I often wonder if they could handle the same in 20 Servers when we have 128 Core Per Server today.
Or something else?
Imagine you took all the AWS Lambda functions of all the different Lambda users, compiled them together into one binary, and then ran that single binary on one huge vertically-scaled server, allowing all the Lambda tenants to hit that server to run requests against their own workloads in the address-space of that single binary. That's a clear "heterogenous workload": one where you've spawned a bunch of green-threads within your process, and those green-threads are all trying to do different things. It's a million little job-shops under one roof (with, importantly, only one "door" in and out, that everyone has to coordinate to share); rather than one big factory where a million workers each have a defined job and there's a big loading bay that is declared from the top to be exclusively owned-and-operated by one department.
Erlang handles the "million job-shops under one roof" use-case pretty well. Golang kinda-sorta handles it, but kinda falls over, too, because the Golang runtime (and the whole concept of reifying typed channels as objects) isn't tuned for the case where each task has spawned an ephemeral digraph of communication paths between ephemeral actors. Channels have a Golang-runtime-system overhead just for existing, a bit like processes or sockets do for the OS. So, like processes or sockets, you're not supposed to just create a channel for each little thing until you've got a million laying about; you're supposed to reuse or share them (and the goroutines they address) between multiple workloads.
Idiomatic Golang code is written in a more dataflow-y pipeline-y SIMD-y way (the "factory with a million workers" above), where you've got static "actors" sending massive amounts of work between them, perhaps tagged with the request they're "about." When this is the 'data architecture' of your code, the Golang runtime can actually beat BEAM (using an equivalent architecyural abstraction, e.g. https://hexdocs.pm/flow/Flow.html) in both throughput and latency, since it's exactly what the Golang runtime has been tuned for.
The downside of the dataflow approach is that, compared to actors-as-little-Turing-machines, dataflow is a very hard data-architecture to reason about! (And also tends to mean that a logic error in any of your static dataflow worker-actors, causes a lot of "collateral damage" when it comes down, to the point that it often may as well bring down the whole system with it. A stall in an assembly line stops the whole factory; but a stall in a job-shop doesn't stop the whole economy!)
"Concurrent applications" as in the C10M problem (http://c10m.robertgraham.com/p/manifesto.html): servers serving a lot of clients at once; usually with the added assumption of a diverse request workload (i.e. some requests are IO-bound, some are CPU-bound, some are long, some are short, etc.)
• WhatsApp used Erlang (on FreeBSD) to scale 50 engineers and ~1000 boxes to handling billions of chat-message transactions per second. (https://www.infoq.com/presentations/whatsapp-scalability/)
• A lot of modern financial infrastructure (banks, exchanges, etc.) is built on Erlang. If there's a financial system that can get away with not being hard-real-time, chances are someone's considered writing it in Erlang.
• Many massively-multiplayer games have Erlang in their backends or their networking middleware.
• And, of course, the original point of Erlang was to route (millions of concurrent) calls in Ericsson backbone telecom routers. It can be deduced from Ericsson's continued work on Erlang's "megaco" H.248 application, that they're using Erlang in their LTE IPMS gateway appliances, to route cell-subscribers' VoLTE audio- and video-call frames.
> Is it like nodejs but more performant?
Node.js is decidedly bad at this type of concurrency (although its libuv is used successfully in software like Nginx—it is rather Javascript's thread-isolation semantics that bottlenecks Node.js code, not V8 as a runtime.) A better comparison is with Golang. If you put Node and Golang on a spectrum of "ability to handle highly-concurrent heterogenous workloads", then Erlang is a point much further along that spectrum than either of them.
(Golang gets pretty far if the workload is homogenous, though. And you can get even further still on that spectrum with custom C or JVM code; but in the process, you'll likely sacrifice either 99th-percentile latency, or lock yourself into coding in terms of state machines. Erlang sits an an optimum where you can just write blocking code in actors and it does the right thing, predictably, even when you have millions of actors.)