Phoenix LiveDashboard
github.com
github.com
I haven’t seen any large projects built with it yet, though for small ones it sure feels like magic.
For those that don’t know what they are, they basically keep a websocket connection open between the client and server to allow the client to call Elixir and C# functions instead of javascript frameworks that call some rest api endpoint. You can write HTML click listeners that are in the server language rather than js.
It’s powerful, but there is a latency trade off since most UI events end up being handled by the server, the client might notice this latency in the UI. Blazor specifically has issues with connectivity, which means your client is completely locked out of the UI until a manual refresh.
It’s true that this is certainly a paradigm shift, and maybe just a stepping stone to web assembly web apps written in a server language.
I haven’t used LiveView, but I can imagine it’s better than .NET at scaling since it runs on BEAM rather than the CLR, which means it’s likely the better choice if you will need to maintain a lot of client connections in your app.
https://stackoverflow.blog/2020/02/26/whats-behind-the-hype-...
I’d guess that most any web service for less than, say, 10k total users LV would be fine. Given a user base that size or smaller, the productivity tradeoffs outweigh most user inconvenience. As in, I can make a SaaS/SPA to serve a small market significantly quicker than using a bifurcated server/js-frontend. Especially if the SPA requires or benefits from live streaming updates (graphs, dashboards, stats apps, etc). No contest in my opinion, except for maybe a Clojure stack (?).
And iirc I believe LiveView will have all the state necessary client-side to be able to reconstruct the session when reconnecting the socket. I’m not near my development machine to make sure on that one though.
Liveview is built on top of channels, so worth reading up about them: https://hexdocs.pm/phoenix/channels.html
1. It includes live navigation, which updates the URL as you interact with the app. For example, if you try the live scaffold generator in Phoenix v1.5.0-rc.0, you will see that once you click the "New Post" modal, the URL changes to "/posts/new". So if users reconnect by any reason (poor connection, new deployment, etc), they will go back to the same page with the same modal.
2. For forms, we automatically submit the current form state on reconnections, allowing the server and client to sync up.
For complex applications, think a chat app or a game, then you have to persist messages, the game state, and what not on the server. But that would be necessary regardless if you are using LiveView or not.
It's good to note that using the live navigation you can pass parameters via the URL. That's my main method for graphing / data view pages.
On a LAN you really don't feel it 99% of the time. However in the game I have a countdown that I initially updated each seconds with LiveView (probably with send_after/4) and I couldn't make it totally reliable, sometimes the updates would queued up, so in the end I "faked it" in javascript with hooks.
Over the internet there is an added latency obviously. I feel it most in the game part because the button has a instant feedback. On the other parts, forms etc, this does not perform worse than a typical SPA.
So I guess it would be very dependent on the kind of application you use it for. Nobody said it would replace all SPAs and javascript. Also the official demo runs an animation at 60 or 100fps IIRC, so there's that.
Fun anecdote, my devops colleagues tried to ddos my app, they failed and all along people were playing the game without any hiccups.
I wonder though how it performs on mobile networks. Aren't some of them terrible for websockets, when changing towers etc? Would the reconnect and session restoring be too much noticeable?
[1] Demo On a free Gigalixir instance: https://every-weak-tapaculo.gigalixirapp.com
On a Raspberry4 behind my FTTH: https://nameguess.funkybits.fr
Productivity was extremely high, and even better, you could restart clients and since all app state was on the server, they would start up wherever they had left off. I stored the state in LMDB transactionally, so I could also bounce the server any time. Clients would reconnected with their WebSocket while it was being used and no one was the wiser. Did that dozens and dozens of times during the event.
I used Blossom (SproutCore derivative) for the UI and statecharts to make that happen. All actions were sent to the server (written in Node.js, with LMDB as the persistent storage). We also had iOS apps for the back office and customer support reps that worked identically (but in Objective-C).
Entire project took 5 weeks with myself as the only developer, replicated a (beautiful) iPhone app Nike made called SNKRS entirely in Blossom and ran it on these huge 30inch touch screens inside a full screen Chrome instance.
Very fun project, and we sold ~1M in shoes (through Stripe) in four days. Freaking cold though, February in NY!
Link to the event: https://dunksandjordans.wordpress.com/2015/02/13/nike-pop-up...
I have seen in Elixir an implementation of functional programming concepts that might prove useful: https://github.com/witchcrafters/witchcraft. It hasn't been updated since last year so I'm not sure if it still works.
These days I've just started using Rust and actix-web instead, seems a lot faster due to no VM, with static typing, and moreover, algebraic data types. I wonder if I could use another language without ADTs anymore.
There is no best way. Fortunately the Elixir core libraries are annotated with typespecs, which gets you like 30% there. But dialyzer sucks, and the typespec syntax itself, as well as its expressiveness, leave a lot to be desired. It's clearly an afterthought.
One day I'm gonna make a TypeScript for Elixir.
Note: There's eg Gleam a statically typed language for BEAM, but it's quite unlike Elixir and it has a much smaller community. https://github.com/gleam-lang/gleam. I doubt using eg Phoenix on Gleam is possible, or if it is, whether it's fun. It looks promising though, especially if you're into Ocaml/Haskell-style "hard" FP.
Not sure what you mean by 'Phoenix on Gleam', but you could write core code in Gleam and use it in an Elixir/Phoenix app, no probs
Like, quite some of the more popular Elixir libraries (eg Phoenix and Ecto) depend quite heavily on macros, which I assume sortof rules out using them from other languages. Like Elixir itself, I think Gleam will only really shine once a community builds its own libraries for non-trivial messy stuff such as DB layers and web frameworks for it.
The interop is be great for simple functional stuff though, like unicode converters, JWT libraries and AWS wrappers.
For how young we are I think we're doing a good job of building a community, there are more familiar faces in IRC and on GitHub every day.
My goal is for Gleam to be easier to use than Elixir, and in many ways we are taking inspiration from Elm which excels here. Hopefully in the future Gleam will meet your needs! :)
Since we got you here, can you tell us what's your plan with static type checking for message passing semantics? From what I understand, that's the hard part about producing a static type checker for BEAM systems.
As for things like gen_server, is the idea currently that you simply give type definitions to callbacks, allowing the gen_server to model its state with types?
At first glance, I also don't understand yet how parametric polymorphism works. Is it implemented at language level?
I'm working on a statically typed programming language in the Erlang VM called Gleam. It's very young but perhaps one day it will meet your needs! https://github.com/gleam-lang/gleam
AFAIK, the closest we have right now is
- typespecs
- guard clauses
- structs w/ required keys (cannot specify types)
- ecto embedded schemas (e.g. structs with "typed" fields)
When I get a `Point` struct, I have no guarantee that the wrapped lat/long are actually valid. After 15 years of static typing this is just so stressful :)
So yeah typesecs and guard clauses cover a lot of that (and the ever fun dialyxir) but it's more cumbersome than just typing your function parameters and variables.
And funnily enough in the end what I miss the most is the type hinting when I write function calls. I use Intellij and the elixir plugin doesn't support that AFAIK, it's a real productivity killer.
I spent a day learning and playing with Elixir around a year ago, and immediately liked it a lot.
But more main dev language is C#, and I'm very used to static typing - as much as I enjoyed Elixir, the lack of static typing was a problem for me, so unfortunately I chose not to take it any further.
This tweet thread was where I stumbled into it: https://mobile.twitter.com/slashdotdash/status/1238513622189...
The distributed nature and ability to send calls to other nodes that can be running completely different code would throw it for a loop unless you had a way to get the types from the other nodes as well. And even if you could there are a number of synchronization problems that come from it.
You’ll end up getting the static benefits on your single node, but beyond that those guarantees will be gone just like interacting with a REST API.
Especially doing embedded work, and without doing proper mockups and test driven dev, the turnaround time with uploading code, rebooting the device, and waiting for a runtime error is quite long, and more compile-time error checking could help.
However, I also feel like types are more important to JITs and compilers optimizing high-performance numerical code, and dropping to C to do that with Elixir's FFI is thankfully simple enough.
I'm quite ok with Duck-Typing, as it does help me to iterate fast, even if it doesn't help the computer go fast :)
Yet there are more brilliant things ahead like OTP and LiveView.
There are many other outstanding functional languages, and I like some of them more than Elixir, but none of them have any framework as solid and straightforward as Phoenix.
Awesome work by the folks behind it - now I just need a project idea for which I can try this out :)
The live dashboard only captures information at the moment. There is no persistence or advanced filtering capabilities. Meaning, if you received an error notification 5 minutes ago but didn't already have the dashboard open, you couldn't just load live dashboard and check it out there. You would have to troll through your own logs or use a 3rd party logging tool just like before.
Where live dashboard seems to shine is if you already have it open beforehand, you can see various metrics about your app in real time. Although other logging tools are pretty close to real-time too.
But it is cool that something like this is included by default. I hope future releases expand on what it allows you to do.
I would agree! But I'd also say that I believe Elixir has moved past this part of the curve and is seeing serious adoption amongst companies.
I first got interested in 2014 and back then it was definitely still early days. 6 years later and I'm still yet to regret the decision to invest time Elixir and BEAM ecosystem.
For those of you working in Rails, you will be excited to know that we have StimulusReflex:
https://docs.stimulusreflex.com/
SR was inspired by LiveView. It's built on ActionCable, CableReady and morphdom. It is fast like a demon and works well with AnyCable. You can see demos here:
StimulusReflex is built on top of Stimulus, which is designed to work arm-in-arm with Turbolinks. We think that the combination of Rails + Stimulus + Turbolinks + StimulusReflex + solid Russian doll caching is the greatest thing since sliced bread.
I'm not a Rails programmer, but I'm keen to learn about all the different approaches being used & tried, to see what I can make fit with my language.
To me, that's like "When I ordered beef, I didn't know you meant a cow!" lol
Seriously, help me help you. Where did I fuck this up?
The very first paragraph of the Quick Start page:
"A great user experience can be created with Rails alone. Tools such as UJS remote elements, Stimulus, and Turbolinks are incredibly powerful when combined. Could you build your application using these tools without introducing StimulusReflex?"
https://docs.stimulusreflex.com/quickstart
What is the thing you could have read and understood right away? I really appreciate whatever you can suggest.
Hook into telemetry is also really cool because any library can be instrumented.
The multi node cluster aspect allows easier monitoring of the full cluster
* For any other language it means trying to replicate the project from scratch (also unlikely to be of any use on php, python, ruby etc)
* For non-phoenix elixir projects you could make it work but you'll rewrite half of Phoenix framework anyway so i don't see the point
[1] https://www.techempower.com/benchmarks/#section=data-r18&hw=...
[2] https://elixirforum.com/t/techempower-benchmarks/171
EDIT: Thanks everyone for the responses.
Elixir is pretty slow for simple singlethreaded CPU bound tasks.
It's incredibly good at multiprocessing, which is really useful in the websphere since your workload is much more likely to resemble thousands of simultaneous communications that need to not block each other rather than calculating a 13042345235223452 digit prime number.
Right so the benchmark I linked to was TechEmpower Web Framework Benchmarks, and Phoenix managed 186,774 requests/sec on a 14C/28T Xeon to do basically nothing (echo Hello World) and ASP.NET Core managed ... seven million.
Plus, 186k/sec is still a hell of a lot.
https://medium.com/@alexhultman/millions-of-active-websocket...
The answer you are looking for is: much slower than .NET core.
The "being good at multiprocessing" of the BEAM VM people are bringing up is not about raw speed at it: it is about giving you the tools to actually being able to do multiprocessing in a sensible, stable manner.
For instance, what happens if a bug in your program blocks threads for a long time in .NET core? What if one thread crashes? Do you have any guarantees that one thread cannot affect others in any way? Etc.
Some developers think that giving up raw performance for guarantees in these areas makes sense. Other developers think they can deal with those issues without nice guarantees from the platform and thus prefer the speed. There's no definitely right answer...
See the architecture of Phoenix Channels (which powers LiveView and other websocket-based solutions in Phoenix): https://hexdocs.pm/phoenix/channels.html
To expand on your workload description: things like waiting for a database query or an API result are smoothly handled in this paradigm; the process goes to sleep for a bit whle another request is processed.
If your workflow is like a delivery truck which makes a lot of stops, "faster trucks" doesn't help much with throughput. "More trucks" is better.
(This is not to say you can’t tune BEAM to the needs of other workloads; those are just the defaults. Though, because that is the default assumed workload, it’s also the one most optimization effort goes into.)
BEAM is also pure-functional in a way that a lot of VMs hosting pure-functional languages aren’t; things like mutable atomic memory handles were only introduced recently, and there’s no plan to rewrite core operations in terms of them, because these primitives are less predictable in their time costs than equivalent functional primitives.
You can go fast under BEAM, but the people who want to do so, often decide that it’s too hard to go fast while also retaining soft-real-time guarantees they began using a BEAM language to attain; so they instead use one of BEAM’s many IPC bridges to hook up a fast native “port program” written in another language to the BEAM node, ensuring that any hiccups or crashes in the native code are isolated to its own address space and execution threads.
This last consideration means that very few people are really driving BEAM’s performance forward, because so many instead take the “escape hatch” of low-overhead IPC.
Taken as whole systems, though, you’ll find BEAM in a lot of services that go very fast indeed—you just won’t usually find BEAM itself handling the performance-critical part!
> It's incredibly good at multiprocessing, which is really useful in the websphere
So on that benchmark I linked, if you switched to the latency tab Phoenix clocks in at 14.6ms average latency with a standard deviation of 23ms and a max of 460.7ms. The best looking ASP.NET Core result from a standard deviation and max point of view is 1.5ms average latency, 1.4ms standard deviation and 49ms max.
So again it's hard for me to tell whether it's BEAM or Phoenix that's responsible for the relative performance hit.
There’s also the fact that Elixir and even Erlang don’t take full advantage of the possibilities of BEAM-bytecode whole-program optimization. For example, any list you walk using Erlang’s lists module or Elixir’s Enum module is going to generate a back-and-forth of remote (symbolic) calls, rather than resulting in the body of that function of lists/Enum being inlined and fused into the caller. Which in turn means, even if you use HiPE, you’ll be thunking in and out of native code so much that the overhead will eat any potential benefit; and each side will be opaque to the static analysis of the other, so even a wholesale native recompilation of the runtime won’t save you. (Same with gen_servers: remote callbacks through library code, rather than fused module-local receive statements; ergo, 10x optimization opportunity lost.)
You’ll see real optimization in code generated by e.g. Erlang’s lexer-generator library leex; or in specific parts of each language’s stdlib, like Elixir’s Unicode-handling functions. But such optimized code emission is thin-on-the-ground compared to “naive” coding in Erlang/Elixir. (And I don’t blame the language devs: the languages are fast enough for almost everything people try to do; and for the rest, you can ignore the language-as-framework and write entirely self-contained modules that only rely on BEAM primitives.)
I thought it's exactly the other way around.
I Always assumed BEAM is built for concurrent, consistent low latency IO. The benchmarks seem to vaguely reflect that.
1. that an actor that wants to run is never de-scheduled for too long (= 99th percentile latency);
2. that every actor that wants to issue an IOP to the kernel gets to do so efficiently (= whole-system throughput)†;
3. that any individual actor makes actual appreciable progress (= 50th percentile TTFB latency.)
This is basically the definition of a soft-realtime system. If latency always came first, it'd be a hard-realtime system; if throughput always came first, it wouldn't be a realtime system at all.
† BEAM is actually quite bad about this for disk IO in the normal case, because all disk IO by default goes through the bottleneck of sending messages to a single file-server actor. This has the advantage of virtualizing the filesystem, allowing for e.g. diskless BEAM nodes that boot by using a peer's file server as their own. But it's not very good for filling up your disk's IO queue. Luckily, you can bypass the file server and use file-IO ports/BIFs directly. Or, if you're working against network storage anyway, just skip your own OS kernel and have BEAM speak iSCSI to the disk server directly (like Erlang-on-Xen does.) That approach will likely give you more disk IOPS than you could achieve even in C, presuming the C program is relying on OS syscalls to talk to the remote disk through the OS VFS layer.
However, I meant that I always assumed that BEAM prioritized low consistent latency at the expense of high throughout (but without providing hard real-time guarantees). And the mechanism being that preemptive scheduling and counting those reductions so no single process is able to hog the entire system's resources for expensive computations.
While these benchmarks may not be reflective of the true performance it seems that Phoenix has a smaller throughput but lower latencies than most of its peer group in them.
One actor queuing up a heavy amount of IOPS through many different ports, can actually cause the BEAM node to generate a visible latency spike, as it always tries to schedule all the ready ports assigned to the given OS scheduler-thread per scheduler-timeslice, before trying to schedule any user-defined actors; and so will find itself with very little time left in that timeslice to schedule the execution threads of actual actors. A single actor can accomplish this “uneven” loading because BEAM’s reduction scheduling means that just queuing IO ops through ports in a linear block of code can never cause an actor to be descheduled, since the port BIFs are not function calls per se, and therefore have no ability to trigger scheduler yield-checking.
Mind you, this is a highly-unusual usage pattern. Actors don’t usually batch up huge binary payloads to send and then blast them all through at once into many different ports in an unrolled loop. And, usually, one actor doesn’t even usually hold multiple ports; the idiomatic approach is for each port to have its own separate “owner” actor (because this means that these “owner” actors will be scheduled semi-synchronously in response to activity on their respective ports, sort of like Go with goroutines getting scheduled in response to activity on channels.) In an idiomatic usage pattern, you could say that the node doesn’t become IOPS-bound but rather actor-scheduling-bound, where a scheduler-saturating number of ready ports can’t be brought about, because as ready ports per timeslice increase, the amount of the scheduler-timeslice available to actors decreases, and so fewer actors that would generate IOPS are getting run, meaning that more of the next timeslice will be available. In other words, with idiomatic use of ports, this is a self-limiting problem.
But, well, we’re talking about how the BEAM is built, rather than about what Erlang/OTP systems do in practice. The BEAM itself has these priorities, even if, in practice, they’re papered over with other ones :)
For a plain-text benchmark, you are ultimately measuring the ability of sitting on top of a socket and reading/writing to that socket as fast as you can.
However, Phoenix/Cowboy, whenever there is an HTTP connection, spawns a VM light-weight process to own that connection, and then each individual request runs in its own light-weight process too. This comes with its own guarantees in terms of state isolation, reasoning about failures, and you get both I/O and CPU concurrency for free - if you were to do any meaningful work on those requests.
Even though the VM processes are cheap to spawn and lightweight, it is overhead compared to something that is just directly reading and writing to the socket. I actually wrote a proof of concept where we just sit on top of the socket without spawning processes and it performs quite well - although I don't think it has any practical purpose. There is also an interesting article from [StressGrid](https://stressgrid.com/blog/cowboy_performance_part_2/) that shows how removing the per-request process speeds it up by ~60%. But once you are going through long requests (i.e. 1ms because you need to talk to a database or API, encode JSON, etc), these differences tend to matter less and the concurrency model starts to give you an upper hand.
> But once you are going through long requests (i.e. 1ms because you need to talk to a database or API, encode JSON, etc), these differences tend to matter less and the concurrency model starts to give you an upper hand.
If you look at the other tabs in the benchmark there are "heavier" loads that include database access and the most favourable one I can find is "Fortunes" which puts Phoenix at 17.8% of the relative performance of the best ASP.NET Core one.
On the plain text one, the difference between frameworks is at most 6x. You would have to yank Phoenix and sit directly on top of a socket, as per my previous comment, if you want to compare both VMs.
The aspcore platform benchmark does some cool trickery based on whether the test even requires a database using compiler #if directives. Though it seems to be flagged as "realistic approach" Dunno if I agree with that. I think I'm looking at the right code. https://github.com/TechEmpower/FrameworkBenchmarks/blob/mast...