“Woe unto you for using a Websocket” < interesting post, any takes from the Phoenix community?

I found this post very interesting and pretty convincing! It’s got me wondering about the advisability of building my next app using sockets for server sync.

I’m wondering what the perspective of the Phoenix community is on this? Concerns valid? Does Elixir/Phoenix have any particular advantages on these areas?

Hey @danlucraft, at least in my reading the majority (although not all) of this post boils down to: “It’s different than stateless request response, and your programming language / stack may not support that well”. Specifically:

Problem: The world is dominated by request-response
Problem: Messaging stacks come with pain
Problem: Your socket will last a while; you need to think about time differently.
Problem: This creates a hard programming model
Problem: This is not PHP land

These are all variations on that theme, and that isn’t really a theme that’s all that relevant to Elixir. Elixir has stateful processes built in and a bunch of tooling to help make that ergonomic. In short, Elixir is already different and already presents a different programming model, it is already not PHP.

Problem: Don’t count on stickiness

Phoenix doesn’t.

Problem: Growing pains

Phoenix scales quite well.

Problem: The cattle behind the proxy

This is a legitimate concern, although depending on where you deploy your orchestrators may already have incremental draining. If not, that was just added to Phoenix.

Conclusions

I think overall perhaps the author has in mind a different use of websockets than Phoenix promotes (LiveView). I also think that the author’s critiques blend concerns introduced by the idea of messaging and state with concerns specific to websockets. That is to say, packet loss happens to HTTP requests too, and if you’re using those HTTP requests to transmit messages to a client then you’ll still need to think about idempotency, “at least once” semantics and so forth.

That isn’t to say there aren’t areas that I think could mature more as time goes on. LiveView doesn’t really offer a built in way to do deployments that change the user front end in a breaking way. But on the whole though the process management tooling you get with Elixir / Erlang gives you a lot of tools and certainly a lot of patterns to address this and other isssues.

21 Likes

Exactly. It’s all great advice for languages that aren’t on the BEAM.

When LiveView and came out, it wasn’t a big deal because it was impossible in other languages. There are plenty of attempted clones.

It was a big deal because you can’t do it WELL in other languages because they simply aren’t built for it. You need layers of technology in between, right down to how the views are generated.

2 Likes

@benwilson512 said it all for this particular article.

IT seems to move as a pendulum*. Every 10 years we embrace a ‘new thing’ which in fact is an ‘old thing’ deprecated 10 years ago.

There are many reasons but one is very relevant to counter this blog post: there is no one-solution-fits-all and ‘blog hypes’ are easily formed based on a matrix.

Just have a look how hard ‘unicorn combo’ React+Node.js is struck the last few months

  • Take X as is solves A!
  • Everything is done with X
  • X doesn’t do B very well
  • Take Y as is solves B!
  • Everything is done with Y
  • Y doesn’t do A very well
    … rinse repeat…

So depending on how you want to present yourself you make a combination for your blog post (assuming start of X+A period)

The matrix
‘Thought leader’ : X + A (good) and Y + A (bad)
Too late to be ‘TL’ : X + B (bad)
‘Visionair’: X + B (bad) and Y + B (good)
Too late to be ‘visionair’: Y + A (bad)

Not blogging dev in his fifties: using X + A and Y + B mixed in perfect harmony.

Ps. Rust and Static typing today, in about 8 years we will scream “wow, garbage collectors are magic!” and “typing solves not all and slows me down”

* was ‘circles’

3 Likes

Exactly. It’s either circles, or as Bruce Tate described it in his 2015 keynote at ElixirConf, a pendulum, that swings back and forth between “server-rendered is the best” and “we must do everything client-side”: ElixirConf 2015 - Keynote: The Pendulum by Bruce Tate - YouTube

We should do both! PHP4 (cause less strict) + jQuery! Now what should we add to have a fancy stack name so I can register it as domain name?

1 Like

Seems like a pretty dumb article

Lists 12 “Problems” and ends with a “Solution” that starts

Solution: Focus on data

Given all these problems, should you abandon the benefits of WebSocket and join the critics?

They listed some of the benefits within the problems, but never bothered to highlight or dedicate space to them so it’s a pretty one sided take imo.

Also I thought this was funny as a complaint

Your code must work damn near perfectly.

As if alternatives to websockets don’t also need to do this lol

1 Like

Thanks for the views everyone :slight_smile:

I’m still very new to WebSocket programming and Phoenix programming.

Some aspects that I’m thinking about in particular:

  • With current SaaS deployment options, how well do they handle WebSockets? Things like: do I need to worry about how sticky are connections to the same server?
  • In Phoenix is it normal to maintain server side state, or is that just a LiveView thing? If yes, does Phoenix make it easy to restore mutual state when a connection is broken?
  • What kind of data transfer model to use? I’m looking at doing something basically like event sourcing, to make broken connections, or offline work followed by sync, easier to handle. But I’m wondering what is normal in Phoenix-land for this kind of thing.

I agree with the pendulum in general but - in this case - I believe React and Phoenix LiveView have innovated enough for this swing to be considerably different.

My first interactive server-side application was built in Rails, back in 2007-2008, using RJS, which was a Ruby to JavaScript. At the time, people were very critical of Ruby to JavaScript, focusing mostly on how limited having Ruby generate JavaScript was. Then we started emitting chunks of jQuery from the server.

The issue is that you mostly ended-up with a “spooky action at distance” application. First you rendered the page, then if the user pressed an action, you had to go and say “find this ID on the page and do X to it”. In other words, I had to write how to patch the page.

As your application grew, it was hard to keep those different parts in sync. Sometimes the ID structure would change. Sometimes the styles would differ. Sometimes it would even surface silly issues: a page rendered with 0 items was different than the same page rendered with 1 item which was then deleted. In general, it was hard to reason how my page would look like after 5 spooky actions.

Since this was brittle, developers would lose confidence in the codebase, leading them to adopt more and more browser-testing frameworks, which would increase CI times (often to 1h+), and hurt developer productivity, going into a vicious cycle. I saw this happening so many times and the lack of answers in this space was pretty much what shut me away from web development back then.

The JavaScript community was going through similar troubles and the React approach became the canonical answer to this problem: instead of the developer telling how the page should be patched, the developer change the state, render the page again, and the framework will compute the patch. This is a great programming model because all you need to do is: change the state. A page rendered with 0 items goes through the exact same process as a page rendered with 1 item which is then deleted.

There are several variations of the React model in how they generate the patch but the concept behind them is the same: the developer change the state and the framework compute the patch from that.

Now - I don’t know enough GUI development to say for certain if this approach existed - but AFAIK LiveView was the first to apply this concept at the web server level. Furthermore, it does not only implement the “render it again” idea, but it does so efficiently by computing minimal patches. And there are two important reasons for this:

  1. The Erlang VM. As others mentioned in this thread, efficiently generating a patch, requires a stateful connections (which for web means WebSockets) and being able to manage them at scale

  2. Elixir and functional programming. It is easier to compute patches and know what to send to the server if your data structures are immutable (in fact, LiveView approach is closer to Svelte in the sense we compute the patch from the template rather than comparing DOMs)

So, while there is a resurgence in server-side rendering, I don’t believe they are all equivalent. Some are still based on the flawed “the developer is patching the page”, others implement the “render it again” but without patches (they always submit the whole page on every interaction which causes a huge impact on latency and UX), and very few - pioneered by LiveView - do both “render it again + minimal patches”.

12 Likes

LiveView is used for interactivity and reactivity - but usually not for keeping persistent state. If you need something to exist once the user reloads the page, you must store it in the database.

So to answer your first two questions: they both are handled in the same way. Once you deploy or reconnect due to a crash, you fetch the necessary state from the database and continue from where you were. Also check this guide: phoenix_live_view/guides/server/deployments.md at v0.18.18 · phoenixframework/phoenix_live_view · GitHub :slight_smile:

4 Likes

Hey, that’s my article.

@brightball is right; other languages make bi-direction streams a living nightmare.

For context, I built a large system using streams and it’s very hard in languages like C++, Java, and JavaScript for a lot of different reasons. The way I think about it is that we have focused, as an industry, on specific idioms due to their operational nature. For example, with request/response, it’s surprising how many bugs can emerge when building the transport which require a timeout to clean things up. The timeout is useful because the code is so bad that an error can cause the entire request to silently fail. For example, failing to set a promise!

What made my life difficult was having 5 proxy layers between my infrastructure and the mobile devices, so I had to come up with a way to trap the layer that was failing. The infrastructure I built was massive scale publish subscriber, and a broken/bugged out subscription stream behaves exactly like a working stream that never gets any data. (@GazeIntoTheAbyss the blast radius of imperfect code in a request response world versus a stream can be catastrophic. For example, failures in a request response world manifests in upstream failures via a timeout; in a stream, an unhandled problem kills the entire experience which may last for days).

I’m building my own platform around WebSocket, and it follows a similar model to LiveView except more as a data provider. I think using a stream like Adama or LiveView is the only sane way to do anything with streaming.

@danlucraft The moment you need stickiness is the moment you start building a data store; it is that hard to do well. You should check and play with Adama as it is a SaaS that lets you build reactive backends that behave like a spreadsheet. I’m taking a different approach as I’m building my own language, distributed system, and everything as a vertically integrated concierge engineering company.

2 Likes