Support for Realtime - Server Side Updates and Streams like LiveView and PhoenixSync

Hi @bartblast I am super excited to use Hologram and started building my application in Hologram.

For pages that I dont need realtime updates, I am able to use Holograms but for ones that need realtime updates i am unable to use hologram am I missing something here.

Is there support for Realtime serverside updates like LiveView Updates, Phoenix Sync and support for Live Data Streams from Server side like in LiveView ?

Use-case for live chat applications and live dashboards update.

If the support isnt there yet what would you suggest for such usecases ?

2 Likes

PhoenixSync is built on top of ElectricSQL, which parses the Postgres WAL and streams subsets out to clients. You can use the Electric client directly to stream updates. I don’t know Hologram’s APIs well but I believe there is some way to push those updates down to the client. Then you can materialize the view yourself client-side.

There are also some (me) working on databases that natively support partitioned WAL streaming, so that will be an option someday!

3 Likes

Hi @krezicoder!
Great to hear you’re building with Hologram!

You’re not missing anything - currently, data from the server can only be sent in response to commands. Server-initiated updates aren’t supported yet, but there are two features on the roadmap that directly address your use cases:

  1. Server-Triggered Actions - Enable triggering actions from server code outside of commands, such as background jobs (e.g. Oban) or scheduled tasks.
  2. Local-First Support - Implement local-first architecture with offline-capable local database, auto-sync, and conflict resolution for resilient applications.

Initially I planned to provide a PubSub API, but after deep exploration and Time to Implement Pub/Sub in Hologram - Looking for Your Input!, I shifted toward a local-first auto-sync approach - along the lines that @garrison advocated in that thread. The key insight: using PubSub for data sync forces you to manually wire up message passing and maintain synchronization yourself. This leads to bugs and tangled imperative synchronization code. A local-first layer gives you declarative, automatic sync - data just appears in your components when it changes, no manual routing needed. You want top-down data flow from a central source of truth, not component-to-component messaging.

We’d still have Server-Triggered Actions for ephemeral events (notifications, typing indicators) where they make sense - playing a role similar to cast (fire-and-forget, no delivery guarantee).

The local-first space is enormous and I’ve been analyzing it for some time, hence the silence on this topic.

What you can do today

With some creativity, you can support your use cases now with a combination of these:

  • Polling command - a command that periodically checks for updates from the server using the delayed param
  • Phoenix Channel alongside Hologram - store channel events in a global JS object, then read from it via Hologram.JS.exec/1 in a polling action
  • Hologram.JS.exec/1 for general but basic JS interop

The polling is needed because proper JS interop (Time for Hologram <> JavaScript Interop - What Would You Like to See?) hasn’t been implemented yet.

Hope this helps!

7 Likes

I’m all for syncing but what if you’re not using ElectricSQL (f.e. because you don’t have Postgres).
I’m looking into hologram for ‘kanban’ like board where each task is separate Genserver.
Initially I’ve had this written with Postgres+Electric+Phoenix+Separate FE, but after giving it some thought I’ve dropped PG in favor of SQlite (because I’ll usually be running it locally anyway) so that means I’ve lost access to PhoenixSync and its’ niceties.
Generally polling won’t be good enough for this usecase in a long run so it would be nice to have some recommended solution that wouldn’t necessarily tie you into syncing (I guess in my particular case I wouldn’t care if Phoenix Sync had SQlite backend ;-:wink:
EDIT. depending on how this ‘sync’ layer would be implemented, if we could plug into it ourselves somehow (so I can write code responsible for ‘syncing’ Genserver) that would work for me too.

1 Like

Hi @nxy7! I can’t say yet exactly how it will look because this part of Hologram is still in the research phase. It may use ElectricSQL under the hood, or it may have its own low-level solutions - Elixir is very well suited for writing this kind of orchestration, by the way.

Regardless of how it’s implemented in the end, the goal is to be declarative - you won’t have to do all this plumbing yourself, that will be Hologram’s job. You’ll only define some rules and the rest will be done automatically. For example, you’ll be able to use the data client-side, save it, and it will auto-sync with the central data source (or be kept client-only if you define it that way).

One thing worth considering: having “each task as a separate GenServer” sounds like you might be thinking in the LiveView paradigm, where state lives server-side. Hologram works differently - code executes client-side in the browser. Your tasks would typically be client-side data structures that sync with a central source of truth, rather than individual server processes. The sync layer will handle keeping everything consistent across clients.

Well in my case it’s a requirement for tasks to be executed server side and for clients to just have a view of the data (that’s why I was thinking of it in terms of syncing server state into clients).
In my app once the task is moved into some column it starts execution pipeline which happens on the server (start some processing/send notifications/etc etc, all of that ideally with realtime notifications, streaming text in task details and showing current processing status).
I know that Hologram is focused on the client more, but I hope that it’ll also have some answer to keeping server state in sync with client too :slight_smile: In truth I was thinking that Hologram isn’t even ‘client centric framework’ but ‘isomorphic framework’ that’s aiming to be able to work with both ‘server heavy’ and ‘client heavy’ approaches.

I love that you’re going for declarative approach everywhere btw.

I’m really hoping that you’ll manage to think of more generic solution that won’t require Postgres (but that’s just my selfish wish, even if it does I can make use of it in other projects) because that might open up some interesting opportunities and maybe could avoid some bottlenecks associated with Electric (depends on single DB and might not be suitable for ephermal data as far as I’m aware).

1 Like

Sync is not trivial; there are certain guarantees the database has to be able to make.

In addition to ElectricSQL (for Postgres) there is also @jstimps 's work on EctoFDB Sync, and then there’s my work on Hobbes which is more foundational (implementing a DB from scratch).

I would suggest @bartblast try to come up with a design that is general enough to support any backend, but that may actually be somewhat difficult. For example, Postgres can be incrementalized (ElectricSQL does this with WAL parsing) but FoundationDB on the other hand cannot. This concern leaks into the framework layer because if Hologram runs on the client you want to maintain the view on the client, so it can’t be oblivious.

My hope is that Hobbes can provide an Elixir-native database primitive here (with proper incremental support) so that framework designers like Bart do not have to worry about database correctness stuff (which can get pretty in-the-weeds I’m afraid). I am making good progress on that front. Soon!

4 Likes

I wasn’t suggesting that sync is trivial, it’s obviously super hard to get right. I just hope that making ‘generic’ API for sync layer is easier than sync itself so that hologram would allow for pluggable backends :slight_smile:
My initial thought was that it might be easy to narrow the problem down and start with keeping some server process state in sync with client and then multiple backends can sync state in there, so how that backend process is being kept in sync with it’s source is someone else problem and Hologram would just take care of syncing this process with client (this would immediately allow usage of ElectricSQL using it’s Elixir API).
It obviously has drawbacks (like maybe you don’t want to keep this state materialized on the server) but that’s something that would be perfect for my usecase (I actually want to have tasks running on backend and show their state in real time in hologram without much effort - right now I’m doing it using Phoenix PubSub).
EDIT. just read through hobbes README and it sounds super interesting for my usecase since I’m already using embedded DB. I’ll keep an eye on it :slight_smile:
EDIT2. now that I think about it having a way to keep BE state in sync with FE without involving DB at all is actually super relevant for something like Hologram. Since Elixir is used for a lot of realtime stuff it seems reasonable to want to use realtime things from existing ecosystem (like Presence) and it seems to be smaller problem to tackle than going all the way to keep FE in sync with DB state.

1 Like

You could come at it from the perspective of “how do I avoid the DB”, or you could come at it from a perspective of “how do I design a DB that works well here”. In practice many (though not all) of the things you might think belong outside of the DB actually should be inside of it, but because of the way RDBMS are designed they add a lot of friction.

There have been many attempts to fix this over the years. Probably the most promising was Firebase, but unfortunately it was acquired by a company that hates building products.

Having spent a lot of time on this topic, I’m just not sure this is actually the case. For deeply technical reasons the sync part is actually quite difficult to separate from the database. I think a more promising path is to design a database that supports sync with a very open-ended data model. I believe I can make it work, but it’s hard to say for sure.

I think that no matter what there will be need for some sync/realtime communication primitive not involving DB because otherwise you’d not be able to work with big part of the ecosystem (like Presence) and existing tech.
Even if this primitive would not give as many capabilities as some ‘blessed’ solution (like some nice DB integration) right?
Because otherwise we’d be saving things to DB just to get sync and not persistance. I’ve seen some of your posts and know that very likely You have this thought out better than me so I’ll ask - do you think that with good enough DB solution (maybe something built on top of Hobbes that you’re developing) it would be practical to push everything you want to sync into DB? Presence / cursor positions / realtime stock prices etc etc.
All of those are very simple data (not derived data or some aggregates), so some solution that would just be synchronizing state kept on the server with client would suffice but as you’ve said my intuition is heavily influenced by RDBMS so I might have completely wrong idea about it and maybe DB might be a good place for ephermal data too.

2 Likes

You are not the first person to ask me this question; Bart asked me almost exactly the same thing in a past thread. My answer is still no, I would not persist cursor positions to the database. I would, however, probably persist more things to the database than you would think. That pipeline stuff you were talking about sounds like prime database territory to me (though of course I don’t know your case in detail).

I believe there is a need for a realtime communication primitive that is not sync, and I think that’s what you’re getting at. However, I think that primitive is message passing.

Sync, to me, implies certain guarantees about consistency. You do not want events to arrive out-of-order, for example, or for some events to have gone missing. The trickier one is that it’s important that everything that is supposed to be “available” at a given timestamp is actually there or you will get very nasty bugs. This is difficult to achieve without essentially inventing a database from first principles (and that is exactly how I ended up here).

So, to be clear, I am not disagreeing with you. I’m just not calling “that thing” sync. I think what you would want is an extremely broad escape hatch that mirrors message passing, i.e. something very close to the raw WebSocket connection. A push_event or similar. I believe there are plans for Hologram to have such an API.

If the state is small and performance can be manged by a single GenServer, do you still need a database? I agree true state syncing is much more interesting and useful than message passing.

1 Like

This very much depends on context. There are tons of things for which a GenServer is exactly what I would recommend, e.g. for the “realtime cursors” example.

But if you have transactions over application data things start to get tricky. It’s very important that the “log” of events you’re consuming is designed properly. If you can observe transactions out of order, or partially, things go off the rails quickly. The more stuff you do outside of the database, the more room there is for weird consistency bugs.

OTP is very powerful, but it’s also pretty old and lacks good primitives for building distributed systems with strong consistency. Hobbes basically only uses message passing, local name registration, and ETS. Tools like :mnesia, :global, and even monitors are useless in practice.

Also, for a lot of stuff you actually want persistence, and even just safely writing data to disk is so much harder than you probably think. That’s why I’m trying to build a primitive that can take care of that stuff. I think I can lower the barrier to entry substantially.

I don’t want a log of events. As you hinted, thing go off rails quickly when network is misbehaving. I want state deltas:

  1. producer advertise new state version
  2. consumer poll for state updates
  3. producer compute state delta and reply to the consumer poll
  4. consumer apply received state delta locally

This should be more robust in real world network.

1 Like

You can implement state sync over message passing this way, and it’s fine for many things. But the polling increases the latency, so eventually you will start to get ideas about how you could push the state updates to the client directly and you will reinvent the log from first principles as well :slight_smile:

Producing a delta is also not trivial in practice. You would need to diff the current version against a past version, which means your state is now a persistent data structure. Now you need to invent garbage collection. And how are these objects versioned? Are you guaranteeing linearizability? Etc.

1 Like

All of the above is not particularity hard to solve in a small enough scale.

It’s true that EctoFDB itself doesn’t attempt to incrementalize FDB state, but I’m not convinced we can say that it cannot be done. FDB can take consistent backups, both snapshot (backup agent) and real-time (DR agent). Someone may be able to tap into that system in a novel way. I tried, but ran out of time and focus, and abandoned that effort. It does require reverse engineering the data stream, which is not documented, and could change in any new version. Maybe that is enough to call it impossible. :slight_smile:

On the other hand, according to my favorite ghost in a jar (Gemini), Postgres does officially support low level WAL parsing, so kudos to them for that.

Will Hobbes incrementalization be based off of a read version? e.g. the client is notified of the content of a change at a particular version stamp, and the client is responsible for understanding the full state at that precise version stamp so that it can consistently process the delta? Or will the communicated delta provide a from-versionstamp and a to-versionstamp, allowing the client to evolve their local state to the new versionstamp?

Am I on the right track here?

1 Like

You can definitely modify FDB and add a new role to parse WAL segments (this has been done before for streaming backups). Parsing the backup output from the DB itself is an interesting idea but I would expect there to be a lot of latency. I could be wrong.

There’s also the fact that mutations are spread semi-randomly onto TLogs due to FDB’s frankly unhinged replication policy. If you check the Hobbes repo I actually just wrote a new RFD about that because I’m finally moving to copysets. Gonna be some messy diffs today lol.

My idea is to allow a subscription to a key or range on a given Storage server. The Storage server already applies its mutations in perfect version order, so it can simply check the list of subscriptions as it goes.

It’s important that you can perform a read+subscribe without missing anything in between, and we can support that by performing the subscribe at the same read_version as the reads. At the time the subscription is opened the Storage server can read the multiversion store for that range and send logs for any keys that were modified after read_version immediately. I assume FDB watches must do something similar.

I don’t see any reason to send “from” versions. The primitive is “give me all changes for this range and do not miss any”. As long as you actually fulfill that promise, you can always reconstruct a valid view of the range.

There is one more edge case: you have to send nacks to the client when there are no mutations at a version. Otherwise you can’t maintain a view across shards because the frontier won’t advance.