Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Phoenix Dev Blog – Streams (fly.io)
254 points by losvedir on March 4, 2023 | hide | past | favorite | 88 comments


On one hand I've been waiting for LiveView to stabilize and get to its 1.0, on the other hand every release is more awesome than the previous one, and I'm speaking as someone that has been using it since its first public release.

Thanks Chris and all the contributors for your hard work.


I have been a Live View skeptic for years. Recently in 30 days we were able to build an MVP of a complex tool (ingesting ML models, time series + regular databases, complex FE interactions, live notifications, more) that blew the minds of the higher ups and got a ton of buy in building using it from the org as a whole after the success. There were a few bumps, but as a whole, it’s largely been a _delightful_ experience and I found myself pleasantly surprised as a whole.

I would have little fear diving in at this point. We are not a small org and it was a big bet that has paid off.


It’s reasonably stable and when there are bumps, it’s usually easy to deal with them in my experience. We’re all in for most new applications where I work.


LiveView is a great pattern, but it's worth noting that it is not universally useful or better. If latency of UI change is more important than the latency of data update itself, doing the UI update on the client will always make more sense and the data can slide in shortly after.

There is no silver bullet, and sometimes doing a UI update on the client is still a better UX.

I can foresee a future where everyone goes down the LiveView rabbit hole, only to improperly blame it for causing too much UI update latency in some cases.

When it all goes well, it's very clean. If you can't make sure the server is evenly loaded amongst all your clients and close to them, it will probably feel quite poor in comparison to some client side update code.


On this note I've been developing LiveSvelte these last 2 weeks. It's not finished yet but it's promosing. Takes a way a lot of the UI update issues with LiveView as you can have local state very easily while still maintaining reactivity with LiveView wherever you need it.

LiveSvelte: https://github.com/woutdp/live_svelte/



Thank you, haven't seen this before. Similar to LiveState[0] also

[0] https://github.com/launchscout/live_state


I think you’re directionally correct, but LiveView has a JS api that can fire off front end events. You can tie in React, Vue, Alpine, or whatever if you want.


That’s definitely true, but in my experience the data update is the 80% use case.


If the UI change requires no network calls, it's better to use a JS hook or/and Alpine in my opinion.


The parent is talking about a scenario where the network action may take longer, and you want the UI to remain responsive. Consider a save button that goes through state transitions dirty->saving->saved.


Streams in LiveView are a game-changer for long-lived data-heavy apps like news feeds/readers or analytics dashboards that update steadily over time. We've been using LiveView in production for the last couple of years and it keeps getting better. Great post and continued updates from Chris & community. :-)


Until the linked post is up again, here‘s another source: https://phoenixframework.org/blog/phoenix-1.7-final-released


How imprortant is elixir in all this? Is it merely the frontrunner of a useful new pattern? E.g., do erlang, BEAM or functional programming play a critical role or will other stacks effectively replicate this and how are they doing so far, which would be the next more mature?


Live View is well integrated with the lightweight process/actor model that is central to Erlang and Elixir, where state and asynchronous events much are easier to code and troubleshoot than other languages.

So while you can replicate Live View everywhere, it's never going to be as convenient or expressive as the real thing.

On the project I am working on I have a Live View dashboard that's getting updated in real time from distributed async events coming from any server in the cluster with less than a dozen lines of code, and using nothing more fancy than standard Phoenix libraries (Phoenix.PubSub) and BEAM primitives (pg2, nodes, mailboxes, etc.). Porting Live View to Go, Python, Rust, Javascript is the easy part. You'll still miss the BEAM dearly.


I would like to study that code - wold you like to put that code on github or can you link to any similar example? Thank you very much!


Part of the “magic” of LiveView is having a server-side process per connected user, to keep state and enable fast re-renders. This is where Elixir’s runtime (BEAM/OTP) really shines. To my knowledge, no similar technology has accomplished this so far, instead they put state in Redis or another database and need to re-load it from there every time an event needs to be processed server-side.


it is very hard for other languages to pull this off

as Erlang OTP uses a shared nothing architecture

each green thread has it own GC

communication is only via message passing

it is ok to fail over as the threads are easy to replicate as they are cheap

it is possible Golang via ergo as golang gets concurrency right, but doesnt have evolution systems and BEAM

Haskell via cloud haskell might replicate live view... but they are not the sensible defaults as in erlang/elixir/otp

Python never as their async concurrency parallelism story is ill-thought out w GIL limitations. Rust very very hard as their async concurrency design is extremely complex...


But you never know with rustaceans as there is a strong enduring culture to rewrite in rust


I've never used LiveView. Why is an asynchronous programming model required to pull it off?


It's all about a persistent connection between the client and server, with the server pushing updates to the client as new data/events come in.

If you don't have a good async model, you're down to a thread/process per connection, which can get very expensive since you will have a thread for every user on your site.

You need something like Erlang's lightweight internal "processes", golang's coroutines, or maybe even Node.js evented I/O async model to handle this well.


all client state is replicated server-side. that means spinning up and down completely isolated threads very quickly. only a stack with immutability to the core and VERY efficient processes (such as OTP threads) can guarantee this while also providing nearly instantaneous spawning from any state (thanks to immutable data structures). Every client that has a page open to your site has a tiny thread running in OTP (it does not use OS-level threads).

No other popular language can pull this off.


I've been using Hotwire[0] for a while now (Turbo + Stimulus) which works with any back-end, it's been great. It builds on everything you already know and doesn't require rewriting your app. Everything is treated as progressive enhancements.

It's not the same exact model and I wouldn't classify it as a replication of LiveView, it's more of an extension of the Turbolinks model from 2016 with a decent story for adding in frontend JS where needed.

I think it's comparable from a bottom line point of view of it being a solution that lets you send snippets of HTML over the wire (literally the tagline of Hotwire) to very quickly build nice feeling web apps with minimal to no Javascript.

Instead of going all-in with state and Websockets like LiveView, Hotwire sticks to a stateless HTTP model and sprinkles in Websockets only when necessary such as when broadcasting an event to any connected clients. I've used it in Flask before and it took a few minutes to get going. For the broadcasting bits server side, it works with Rails out of the box since this toolset was extracted from Hey and Basecamp. Other frameworks have their own ports tho.

[0]: https://hotwired.dev/


> solution that lets you send snippets of HTML over the wire

I haven't touched rails in a long time but isn't that how it all used to be done way back when? I feel like my original rails apps were making requests and I was returning HTML partials to be (crudely) diffed into the DOM?


Yes this is how it works. This framework was created by rails people - 37signals


> I feel like my original rails apps were making requests and I was returning HTML partials to be (crudely) diffed into the DOM?

They weren't diff'd into the DOM client side. Optionally, there was SJR (Server-generated JavaScript Responses) from 10 years ago[0] that you could do to get similar behavior but now it's much easier and much more maintainable with Hotwire.

In your original Rails apps you could make a request, render a template that has 0 ore more partials and the HTTP response from Rails by default would be all of the HTML, from <html> to </html>. Your browser would parse all of that and produce the page.

Then Turbolinks came into play around 2016 and it's a 1 line of JS drop-in solution so the above still happens, except now instead of <html> to </html> being rendered by your browser, only the <body> to </body> gets rendered. This is a massive improvement for page load speeds because now all of your JS / CSS in your <head> doesn't get parsed on every page load. Even with caching this is a big win since your browser still needed to parse it. It intercepts your links and makes them ajax calls transparently. It worked for form submissions too but you had to do a little bit of work on the back-end to make this work (Rails did it by default). This works with any tech stack tho.

Then Hotwire came along:

- Now you can swap the whole body with Turbo Drive in the same way as Turbolinks did in the past except it also works for form submissions now too with no backend modifications. It's IMO the single biggest bang for your buck that you can do on a site to make it feel fast with no effort at all.

- You can also use Turbo Frames to only swap a portion of the page instead of the whole <body>. It's 1 new HTML tag, you just wrap your content into a frame tag and you're done. Everything "just works". Only that frame's HTML gets sent over the wire, the other content on the page remains untouched. For example if you had a main content area and a sidebar you could have the main content be in its own frame and as you click links and do things in that area the sidebar will never get re-rendered unless you purposely break out of the frame by setting a target on the link.

- Then there's Turbo Streams which works for non-GET requests by default. So if you posted a new comment on the bottom of a blog post you can tell the stream to append it to a div (or prepend, update, delete, etc.) so you end up only sending a tiny bit of HTML over the wire for that single comment's markup. Optionally you can also instruct that to be broadcast over a websockets channel. In Rails this is seamless, it's 1 extra line of code and now you have live updates broadcast to anyone connected to that channel. You can also use streams to update multiple areas of a page if you wanted to, such as append the comment and add a (1) notification to your nav bar or page's title.

You as an end user don't need to write much JS to do all of the above. Then there's the Stimulus part of Hotwire for when you do need to write JS for more client side interactivity. All of this works with any back-end tech stack.

[0]: https://signalvnoise.com/posts/3697-server-generated-javascr...


And here I thought you spent most your time on Flask!


I do a mix of Flask and Rails personally. Hotwire Turbo works with Flask out of the box minus the websockets bits which you could make work but you'd need to handle the broadcasting server side bits since Turbo is a client side library.

With contract work I get exposed to a number of different tech stacks too.


wow that looks fun and useful - thanks!


There is a port for Go: https://github.com/jfyne/live . However, the API is not nearly as clean and nice, and it's probably not as efficient as Phoenix either. But overall it's really made a number of applications much easier to build and it has allowed me to avoid mountains of JS.


People will replicate LiveView in other languages but fall short in some areas. Golang might pull it off. Rust might in another 5 years.


No one is mentioning that in Elixir/Erlang (and a bunch of other FP langs) a string is implemented as an immutable linked list of integers. linked lists have the property of its nodes being all stored separately in memory, which means we don’t really need to worry about space/time complexity while adding/deleting nodes in the list.

For example, A -> B -> C -> D is a linked list, but so is B -> C -> D, C -> D and so on, and each linked list is just a sub structure of another linked list.

This is interesting, because now we can confidently say that A -> B -> C -> D is a different list from B -> C -> D (even though one recursively contains the other one). Because we have that guarantee (along with the fact that a list CAN'T change), we don't have to define the same data twice, and we can reuse existing linked lists! This is called Structural sharing. Adding to this list as an extremely low memory cost.

If we go back to our html snippets that we are sending down the wire, you can see why all of this would have an advantage over how an imperative lang might represent strings under the hood.


Your explanation of structural sharing makes sense but there's a lot of nuance there:

1. Elixir strings are Erlang binaries (byte arrays), not charlists. Charlists are becoming rare nowadays even in Erlang because they're so memory-inefficient.

2. Comparing two lists for equality requires traversing them, you can't just look at the first node. If they were built independently they won't share structure.

3. HTML templating is (reasonably) efficient because of iodata, not charlists.


Just to add to your explanation, Lisp cons cells works that way as well, and are implemented as linked list.

The great and often unmentioned pro of cons cells/linked lists is that they are a very simple form of persistent data structure, that make immutability and memory sharing very easy, so are an excellent primitive to build a garbage-collected immutable and shared-nothing language upon like Erlang, or Clojure.


I'm working on a VDOM in Ruby using Haml kinda like JSX is used in React. It performs pretty well but I don't know how it scales. The syntax is probably the best part. Memory usage not so much, however I haven't done any optimizations.


There's a TypeScript port that works rather well: https://github.com/beenotung/ts-liveview


How is the link down given that Fly.io is an cloud/edge hosting service?


Today they rolled some kind of upgrade and my backend become unreachable for hours, this is not my first problem with them. I assembled a quick docker-compose.yml and moved to Hetzner. Fly is pushing a lot of blog worthy stuff but it is not production ready.


A plumber's house always has a dripping tap.


https://status.flyio.net/ currently shows a partial outage.


The two are not mutually exclusive, it's probably not an issue with horizontal scaling.


I have been developing with React/Node for 7+ years now and I have started to look for something else. Perhaps this would be something to try. I'm specialized in developing web portals/backoffice systems, would this be suitable for this? What limitations are there?


RTT is the big limiting factor.

It has ways to indicate "things are in flight" but for distant deployments you probably still need to write JS to manage some in-page elements for reasonable feeling UX.

This isn't hard, it has support for interop with plain JS or existing libraries and a somewhat limited built in system for basic show/hide/set/remove attributes (somehow lacks a "toggle_class" and "toggle_attribute" function though so you can write those yourself...). You can dispatch any custom JS event from it to cover .. anything you need.

If you're exclusively deploying to on-site/one-site/geographically-constrained systems, then you actually skip that issue...

You also have to re-think about how write code as Elixir is immutable, though I see that as a bonus.

Phoenix (and by extension LiveView) are actually a lot smaller than you might think coming from Rails or the like, mostly leveraging existing tools.

Elixir/Erlang/BEAM make building systems incredibly pleasurable. It ships with many tools to make and connect complicated things together, in a reliable way. Depending on what you make you may find it very liberating after working with Node.


> It has ways to indicate "things are in flight" but for distant deployments you probably still need to write JS to manage some in-page elements for reasonable feeling UX.

Most JS UIs behave like this: open a list view, watch a spinner until data is fetched from the server. There is practically no difference between that and showing a spinner first and then the list.


Yes but my point is if you don't do anything in LV, you click the button and it sits there seemingly doing nothing, then patches the DOM with the update. Possibly the default "topbar" will show depending on its timeout configuration.

So you need to pay some attention to applying-css/hooking-into-the-tooling (phx-click-loading) to show that a button was indeed clicked and is working on fetching data.

Some things such as flipping between tabs can feel quite bad if you click and the tab-button just puts a spinner there or the button just "fades a bit". Its much nicer to replace the content pane with some mock-blocks to show "yes, the tab swapped and its loading data" which is where you need JS to temporarily hide/show some things.

And once you start doing that you should really be measuring the avg rtt on the socket because showing the mock-content for 1ms then swapping the real content is also bad, so you need JS again to figure the best UX for that client.

Or you simply render all the tabs in one go and again, duck into JS to perform the local show/hide.


> And once you start doing that you should really be measuring the avg rtt on the socket because showing the mock-content for 1ms then swapping the real content is also bad, so you need JS again to figure the best UX for that client.

I don't remember the exact circumstances from when I last dabbled with LiveView, but I recall that using a combination of CSS delay (so that fast connections wouldn't see a useless spinner) and an opacity transition style (to reduce jarring elements popping up suddenly) produced a nice effect, all with plain CSS and a phx-loading attribute.


> which is where you need JS to temporarily hide/show some things

You do not need to write that JS though. LiveView comes with a few helpers to assist in client-only features.


I even wrote that in my original comment :)


There’s a big difference from a UX perspective between clicking a button and having nothing happen and clicking a button and seeing something immediately happen, regardless of how long the data takes to retrieve.


LiveViewJS[1] might be of interest. Same protocol as Phoenix LiveView with a JS/TS server implementation.

I was looking for something else after years in React ecosystem and came across Phoenix LiveView but I wanted to write in Typescript.

(Note: Am author)

[1] - https://LiveViewJS.com


I think this is a great use case. From my experience, having everything in one language is a huge plus. You can pull data from the database and just inject it into the view. The closest I've gotten to this in the JS/TS world is a Prisma + tRPC + SvelteKit for E2E type safety, but there's a huge cost in complexity and language server performance and some extra boilerplate.

The main limitation is likely offline apps, LiveView requires a persistent connect to the server. I doubt this is something you'll encounter for your use case.


I'm planning on adding tRPC to the Prisma + Nest + Next stack so can you elaborate on "language server performance"?


For back-office stuff, these days you can be really productive with Django/HTMX or Hotwire, both in the same broad paradigm as Phoenix LiveView.


Streams are a fundamentally underappreciated data structure because they are generally not implemented optimally. Streams correspond to coalgebras and that whole field is like the missing half of programming.


If streams are the coalgebras, what do you consider the normal algebras of programming?


Structs. Regular objects. If you haven't heard the term "algebraic data types", searching it will probably give you an idea of how the "algebra" part fits in.


Lists.


Anyone have a good example of an app, in production, that's publicly accessible, that uses liveview? I'd like to check out the experience for myself.


Sure!

- Here is a thread on ElixirForum: https://elixirforum.com/t/phoenix-liveview-in-production-exa... - Here is a neat little aggregator: https://liveviewdemos.com/, and here is the code for it: https://github.com/leandrocp/phoenix_live_view_collection


I had to wait 10s for that list to load :/


Blame api.twitter.com :)


Maybe a little off topic, but for a .NET dev, what benefits does LiveView offer over Blazor Server Side?


website seems down?


it does


HTTP ERROR 502 for me


I'm not sure what's exactly problems which liveview tech solves.

In real world applications, it's always "polling", because you're not sure when server finished your request.

Or liveview is for toy application ?

Note: You seems mislead the point here, to build interactive apps, of course you can just use websockets. What's more liveview solve ? I guess liveview = html + websocket.


"What problem does React solve? You can just use HTML and jQuery to build websites."

To me LiveView is not about interactive apps so much as it is a way to build applications, period. You can build in a way that may bring you more joy, performance, and dev speed than you could otherwise.

Interactivity is a side effect, and it's a great one. But I wouldn't recommend LiveView if someone only wanted interactivity. I'd recommend LiveView if someone wants a different way to build than the React+API status quo.


React solves the composition of components, which lacks in HTML technologies.

What about liveview ? That's the question. They're different.


"you just need to write better code to have composition with jQuery"

To be clear I don't believe that, but that's analogous to what I hear when I see this.

LiveView has composition of components, so there's an immediate answer to your question. You've already answered another piece with built-in soft real-time.

In all honesty, if you have looked at LiveView and don't understand what it brings then I'm not sure if you looked sincerely.


I know what it does. Instead of boilerplate to declare channels to handle live websocket, now you can just bind the real data to a DOM html. Isn't it ? Is there any more to bring ?

React and similar technologies actually not just scrap your boilerplates, it's real abstraction (forget the DOM API) with state and componentation. It's not like you scrap your jquery boilerplate).

I see people mess with DOM Api in their application, which is not the right way, because it's mixing different abstractions together to create a mess.


LiveView is also state and components. + It has a view of the DOM to optimize the data it sends over wire.

It's a complete way to build complex apps. I've personally built fairly decent complexity SaaS (18mo of dev) completely on LiveView + some react when it made sense.

Saying it's just HTML pushed over WebSocket is like saying React is just virtual DOM with components. There's much more to it when you get into it.

Plus it's enjoyable. I had significantly more fun and development speed building on it than the typical React+API approach.

That said it's still situational and I don't blindly recommend it to everyone. I think Elixir has one of the best graphql implementations (Absinthe), so I still recommend React+GQL as well.


> I see people mess with DOM Api in their application, which is not the right way

I found people who's stuck in DOM abstraction like React keeps reinvesting what platform is already capable of. Some abstract is so nasty e.g. forwardRef .. like really? Real Event bubbling is so much better, it visit every damn node you got the ref for free.


It’s much more than that.


You can compose components in recent versions of Live View as well.


Liveview solves the dynamic areas in a easy way, that's the differentiating factor.


No ? I can easily use a React <LiveComponent websocketUrl="" /> to have a dynamic live area.


Can you point me the react API page for that? I've never seen that.


He is trolling us. That does not exist.


Liveview solves the "write a fully dynamic and interactive application with next to zero Javascript that you need to write by hand" or, if you will, "server-driven UI apps".

And it's hard to overstate, but to also properly explain, how important this is and how it feels like magic when you do it. Because it lets you tap into everything Elixr (and Erlang VM) have to offer on the backend without ever having to think of "how the hell do I bring all that to the client".

My use-cases are: a big chunk of data is being processed, and while its being processed regular updates are sent over PubSub. Most actions a user initiates are long-ish-running and their updates are also sent over PubSub. Things that other users are doing are sent over PubSub and possibly need to be reflected in the UI.

In "traditional" JS-on-the-client-whatever-on-the-server model you have a plethora of questions of how to get that to UI: which endpoints to query, which updates to send over the wire, the formats, the frequence, authorisation, auth...

With LiveView your "client" is an Elixir view living on the server which just updates the view based on those PubSubs (or timers, or Kafka streams, or user-initiated actions, or...) and LiveView takes care of upating the DOM/HTML in the browser.

"Build a real-time Twitter clone in 15 minutes with LiveView and Phoenix 1.5" will give a good overview of this: https://www.youtube.com/watch?v=MZvmYaFkNJI

[1] Built-in in Phoenix, https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html


In realworld apps you have a lot of "dynamic" areas which are just "click there, ask the server for the changes and reflect that on the web page", this is where liveview is great.

Another case are dynamic elements based on external factors not based on user actions (like for example the message counter on a social network), those are pretty annoying without liveview because you need to build a whole frontend event system just to change a number.


Are you sure Facebook or Twitter is using liveview for their social feed ?


Facebook or Twitter using LiveView wasn't implied by the comment. It's just an example use-case.


I'm piking a need that is common even for small websites. I even had to implement a similar concept for a small job board.


Not all websockets are equal. If you've ever tried PhoenixChannel + PubSub .. you will know why. Also LiveView + Presence built on top of that with all niceties. And if you have more than 1 node, these also work in distributed fashion.


Huh? LiveView is for interactive apps. It's not finished until the user leaves the page. It's not polling.


It is not polling - it relies on Websockets.


liveview is similar, in spirit, to how X11 apps had a client side library and a server side library (ultimately Xlib on one end), and were connected by a socket (bidirectional) over which minimal-ish data would fly back and forth to keep the client side displaying what the server wanted, and the server aware of events from the client side.

liveview is also using a socket, whose existence springs to life via the "websockets" modernization where the client is a browser. and the traffic bidirectional over the socket again keeps the server side library aware of events, and the browser-side aware of UI alterations to display.





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: