Using Rust to Scale Elixir for 11M Concurrent Users (2019)
blog.discord.com
blog.discord.com
It would mean that insertion at beginning is worst case scenario (split + need to move all buckets), but timing of insert is actually dominated by adding size of the buckets to calculate final index.
Spending a little bit of time on research, finding https://en.wikipedia.org/wiki/Order_statistic_tree and just using G++ implementation would probably yield better result and less code to support.
G++ has __gnu_pbds which add O(log n) "find_by_order" and "order_of_key" to trees, e.g. [2].
[0] https://github.com/discord/sorted_set_nif/blob/master/native...
[1] Technically O(n/max_size + max_size) but we can assume that max_size is selected to be ~sqrt n
1: https://github.com/wltsmrz/nimler 2: https://github.com/ityonemo/zigler
That's because a crashing NIF can take down the whole Erlang VM. If fault-tolerance is what brought you to Erlang/Elixir and OTP to begin with, as it is for many, having the server crash could be a major issue.
Wikipedia says Discord was released 5 years ago. Am I missing something?
Makes sense to me, its users would be running games alongside the software, and would be quite cross with the software shitting itself in the middle of a co-op game. Somewhat good performance and behaviour is (or at least was originally) absolutely necessary to getting traction, a bunch of checkboxes would not suffice.
Currently working at a place where at least one, if not two, teams use Discord for daily chatting. Official meetings are mostly handled over Zoom but I believe their intra-team stuff tends to happen over Discord.
Whereas Slack is successful because it understood it's usecase well (including how to sell to enterprises).
Their skiplist description is a little funny — typically a skiplist has one element per leaf node, and you should never need to move items between buckets. Also skiplists have logarithmic layers in the number of elements, so inserting and searching are always bounded by O(log n).
Hum, can someone talk more about these NIF and what special thing Rustler brings?
What's different from just any kind of native interop, like say Java JNI ?
Ports are the typical one for simpler things. You start an external command and talk to it via stdin/stdout.
NIFs are potentially much faster and are traditionally in C. But they carry the risk of crashing the VM. Rustler+Rust removes most, likely not all, but most of the risks for screwing up and killing the VM.
So NIFs are usually not recommended simply because the resilience and reliability of the BEAM VM relies on a bunch of cool strategies inside the VM and NIFs side-step all of that. The Rust ones should be far less dangerous.
This is anathema to some...i dunno..philosophies? expectations? of Erlang/Elixir.
NIFs written in Rust (via Rustler) should never be able to crash the VM. Unlike NIFs written in C.
Rustler brings other quality of life improvements, like easier interop (decoding/encoding values to and from) and managing the cleanup of objects that live in Rust but are referenced by Elixir.
Is there a panic handler that prevents a crash and returns a magic value that makes sense to the caller?
> Note that this function may not catch all panics in Rust. A panic in Rust is not always implemented via unwinding, but can be implemented by aborting the process as well. This function only catches unwinding panics, not those that abort the process.
It’s not quite possible to have no unsafe code in JNI with Rust (in my experience) but it is very easy to isolate to very small areas of your code.
NIF is native interop. There's a bunch of provided functions for interfacing with Erlang terms and other things like that. But, it's native interop, so you can also do whatever crazy stuff you want, even if it's a bad idea.
The BEAM VM cannot and does not protect you from bad NIFs; but if you follow the guidelines and are careful not to crash, you can have a good time. Often, the scope of the native work ends up so small, it's easyish to keep it safe. For larger scope, it's more common to use a Port or a C-node; a Port is just what Erlang calls running your other code in separate OS process communicating with stdin/stdout, and a C-node is your other code running separately, possibly on another machine, connected via network sockets, acting like a dist node. For this application though, it makes more sense to use a NIF to avoid copying the data to and from a separate OS process.
I haven't used Rustler, but my understanding is it's mostly just a framework of sorts for writing NIFs in Rust. Maybe making it easier to use Erlang terms, and setting up the compile settings etc.
You may have heard about how Erlang with the BEAM is a fantastic implementation of the Actor programming model. This involves lots of processes (green threads) holding a little bit of state and communicating with messages. It's both a great concurrency model and a great programming model. The standard library (OTP) then also provides excellent tools for managing these processes in supervision trees.
Ports act like another actor in a supervision tree. This actor happens to be a C program and the messages are sent and received by stdout/stdin, but otherwise it behaves like a process. You can manage its runtime state with supervision trees and all the other excellent tools that BEAM/OTP give you.
NIFs on the other hand are best used to replace something that could be a pure function in Erlang, but is a lot faster in something compiled (or because you want to leverage some existing codebase that does that).
Discussed extensively last year: https://news.ycombinator.com/item?id=22238335