Comment by syspec
17 hours ago
Whole blog post is basically: Make a mutation in the clientside, assume it worked, and save in the background.
17 hours ago
Whole blog post is basically: Make a mutation in the clientside, assume it worked, and save in the background.
Works for Linear because the tab stays open, and worse case if tab is closed you can recover later when the tab is opened again and deal with conflict resolution. Won't work if:
1. user clicks a button and closes the tab thinking transaction is done and it's important that transaction is done
2. conflict resolution is difficult or impossible in future client wake up
The user clicks the button, the mutation is stored in local storage. The user closes the tab, but it's not a problem.
A background worker picks up the mutation and sends it to the remote backend. It takes time, retries, etc.
Similarly, any errors reported by the background worker go to local store, and the next time the UI tab is loaded / activated, they are shown. A service worker can show a notification outright to let the user easily load the main UI. Normally this would be a rare occasion.
1. What if the browser gets closed/killed? 2. Error messages around syncing issues are notoriously worse than those of a sync request to the backend that failed. So the UX in the end is worse.
More generally: You can't circument the trade-offs of a distributed database, which such products are, conceptually.
Yeah this pattern can be made to work fine.
Main downside is it significantly complicates the front end code compared to just waiting for FE to sync with BE before updating
2 replies →
Expose the sync queue to end user and train them to understand if they attempt to close the tab with a pending queue they will get the ugly prompt warning them
Still works if you use beacon requests, they survive tab close
Bear in mind that beacon is something of a hail Mary, though. No way to tell if it was successful or not or react if it wasn't.
Yes and for linear (if you break it down strictly in a theoretical CS sense, is roughly equivalent to TodoMVC in terms of application complexity assuming non-collaborative text editing) they have clearly defined states for most items and few if none truly destructive actions. The hardest part is anything text related.
I agree. To each their own, but the UI updating automatically doesn't really add much value to me. I would prefer that the view I am seeing is a snapshot in time of what the ground truth server was, not some mixed state that forces me to consider the possibility that seeing my request go through on the screen doesn't actually mean it went through and has been sent to the server.
The goal is - and I think they have achieved it - is that you don't have to think about it. They handle sync, and they do it reliably.
Then they have solved one of the fundamental hard things in computer science.
Well that "make a mutation client-side" phrase is doing a lot of work.
Make a mutation to what?
The classic server rendered web-app doesn't have any data to make a mutation to. You could try to patch the UI but that would be a huge pita and not really a scalable (in effort) solution.
If you have an SPA, you still don't really have data on the client-side. You have a bunch of cached query responses. You can update those, but (a) it will be a pita to do correctly, (b) you'll have to do it to every possibly affected query, and (c) you have to remember to undo it at the right time (way more subtle than it appears - think it through!).
A sync engine creates the client-side normalized datastore that allows you to "do a mutation client side". In fact, you're kind of right that once you have a sync engine, just doing a mutation is really easy. The real challenge is all the infra required to enable you to do so.
I think whole generations are constantly discovering that the client is really, really fast.
so.. optimistic ui? like upvotes on hacker news since forever?
Indeed. I have to say, I hate this. Suppose you are in a meeting, you update something and you see the result, but the rest of the team does not. Ok, a couple of hundred ms does not play into this but if the update does not make it through? And yes, it happens.
Changes go through and synced to everyone on your team in almost realtime. If there's a conflict on the server and your change cannot be applied (almost never happens), your change is rolled back on your client, again, almost in realtime. If servers cannot be reached, we will show you a syncing badge within 4 seconds to tell you that you have made changes that haven't been sent to others yet.
Strange that we can be so be polar opposites on this. You hate it, I would never write an app in any other way, ever again.
(curious) What if a user closes it before 4 seconds? Ctrl+enter, it optimistically locally updates within 1 second. I close ctrl+w. But my wifi goofed and it didn't reach the server.
4 replies →
As a user, I like when things appear to sync instantly and perfectly, such as in Google Docs.
As a developer, I hated the article and many of the comments I read thus far because:
- Having clients and a server properly sync and not lose data in the event of a network failure amounts to having a consistent distributed system which is not easy to do, and the commenters don't seem to have understood that
- I hate having written a long document and then losing it because the sync code is buggy, so the previous point becomes even more important.
So reading many of the things here has been mildly infuriating.
That being said, none of these people are likely affiliated with Linear, and given the overall quality of the product I'm pretty sure it works properly.
2 replies →
For native apps this is less of an issue since they have access to persistent storage but with browsers there's no guaranteed persistence.
There's guaranteed persistence, but there's no guarantee that the host will be up anytime soon. E.g.: I might leave a final reply with all the details on an issue before going on vacation (or maybe I don't work the next day but my colleagues abroad do!). I see that it's properly posted and close the laptop.
The reply with be delayed by days or weeks, but the UI indicated that it had been properly saved.
> There's guaranteed persistence
There's not. Browsers can delete "persistent" storage at any time.
https://developer.mozilla.org/en-US/docs/Web/API/Storage_API...
5 replies →
AKA what Relay does out of the box haha
Though its depressing how few actually use it to its full extent. My team is one of the few where I work; heavy declarative mutation directives with optimisticResponse (and optimisticUpdaters because some of our APIs are not very Relay-compatible, annoyingly)
> AKA what Relay does out of the box haha...
Relay does optimistic updates well. However, frustratingly, Relay does not do any persistent caching to disk, like Linear does. This means, first page load will always have to fetch data from the server.
I think that’s an acceptable trade off, personally… but! You can implement persistent caching if you’d like :) you create the record store when you stand up the environment, so it’s possible to have that hydrated with data from somewhere else
But I’ve only done that in toy examples not prod, I’m sure there’s something I’m missing haha
I believe this is called “eventual consistency”.
No, because if a transaction fails it still needs to be handled by the user in many cases.
E.g. if you buy a book, but it turns out the book was already sold, then you will first get a message "Your book is on its way!" and then "Oops, sorry, the book was already sold to someone else".
Eventual consistency is just a property of the database.