Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When do race conditions actually occur? #28

Open
thexpand opened this issue May 25, 2023 · 5 comments
Open

When do race conditions actually occur? #28

thexpand opened this issue May 25, 2023 · 5 comments

Comments

@thexpand
Copy link

Based on the information provided in the README:

❗️ This package is experimental. Generally it should work well, you might run into race conditions when your Client Component is still rendering in SSR, and already making overlapping queries on the browser.
...
If you do not use suspense in your application, this will not be a problem to you.

When would such a scenario with race conditions happen exactly? Only when using @defer? Also, what do you mean if we're not using suspense? What will happen if I decide to wrap a component using a suspense boundary <Suspense> or by using the loading.tsx provided by the new Next.js app directory?

What problems might occur?

@phryneas
Copy link
Member

phryneas commented May 26, 2023

Doing SSR in the App directory means essentially that your browser already starts executing your application while it is still running SSR.
Every time a suspense boundary finishes rendering on the server, it is flushed on the client and starts rendering there.

So imagine the following scenario:

SSR: a part of your application (C1) finishes rendering. That part contained an Apollo Client instance. It will be created on the server.
Browser: C1 rehydrates on the client. This will create an Apollo Client in the Browser, too.

Now we have two Apollo Clients existing in parallel on the server (SsrAC) and in the browser (BrowserAC).

Now we have a child component in a suspense boundary further down in the tree - that component contains a form to udpate the current user's first name. (C2) That data is also fetched using useSuspenseQuery, so the first name is stored in the Apollo Cache.

SSR: C2 renders
SSR: Query results and C2 are transported to the browser
Browser: merges query results from SSR in
Browser: C2 renders

We also have another component in another suspense boundary (C3). That component uses the useSuspenseQuery hook to retrieve a lot more user information. The user's first name is part of that.

SSR: C3 renders. It suspends for 5 seconds because this is a very slow query
Browser: nothing renders yet, we wait for the server
Browser: the user is bored, changes their name in the form and triggers a mutation
Browser: the BrowserAC updates the new first name
SSR: the query finally arrives (with the old first name, maybe because that was the first information the server got out of the database)
SSR: C3 finishes rendering
SSR: C3 and the query result are streamed to the browser
Browser: the query result is applied to the cache. The old first name overrides the new first name
Browser: C3 now renders, with the old name.
Browser: C2 also updates with the old first name.


Of course, this is not an everyday scenario. It requires that the server renders an extremely slow query, and that the client does a request for overlapping & updated data in the meantime (probably triggered by a user interaction). But at the same time, this is a possible scenario, and we have to warn about it.

@thexpand
Copy link
Author

Oh, I get it now. Thank you for such a detailed explanation, @phryneas! Have we explored the possibility to use web sockets to sync the browser instance and the SSR instance of Apollo Client? Is this even possible with Next.js, React, and RSC? If it were, we would be able to have a synced cache between them.

@phryneas
Copy link
Member

Unfortunately, that's not an option - we don't have a lot of control over Next.js here.

The only thing we can do is push more data into the stream, and that is also only synced to the browser whenever a suspense boundary "flushes" to the client - there is a React feature request for a useStream hook that would at least allow us to get data to the browser faster and not have to wait for suspense boundary completion, but we'll have to see if (and when) the React team picks that up.

@GeKorm
Copy link

GeKorm commented May 26, 2023

It sounds like keeping a last updated timestamp would solve this. This issue and associated PR in the main repo might be relevant?

apollographql/apollo-client#9502
apollographql/apollo-client#9503

@phryneas
Copy link
Member

@GeKorm Yes, having a typestamp could solve this, but it won't be as easy as the issue/PR you linked - as Apollo Client is a normalized cache, we would have to keep that timestamp metadata for every single field in the store independently, as an incoming response might be 50% "newer" than existing data, and 50% "older" at the same time.

It is something we do consider, but implementing it would get close to a full rewrite of Apollo Client.
We keep it in mind for discussions around our next major release, when we plan to generally introduce bigger changes, but right now we can't do that spontaneously.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants