question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Normalization does not occur on first render when cache persisted with apollo3-cache-persist

See original GitHub issue

After I added the apollo3-persist-cache, I’m serving stale data to users without updating it after an Apollo server response. Since all my cache policies are cache-and-netwok I would actually expect an update on cache after I got an Apollo server response.

Intended outcome: After an Apollo server response, the Apollo client always should normalize and update the cache.

Actual outcome: I found that cache normalization is not happening every time the server responds, which it should. If I already have persisted cache when performing the first query to the server in the first render, the normalization will never happen. I checked this by passing a merge function that is never called. At this point, data will already be stale, because it comes from the user’s local cache. After this first render, the following requests, on the client side, most of the time the normalization occurs, but sometimes not.

How to reproduce the issue: Reproduce the bug here: https://github.com/igorthurow/apollo-client-bug Follow this steps:

1. After cloning, install all dependencies with `npm install`.
2. Start the development server with `npm start`.
3. Renders the first time and sees on the console that it didn't print an `@@merge function called`, that is, it didn't call the merge function. This is probably because I'm simulating data coming from the SSR and using `forceFetchDelay: 1000`
4. Remove the simulation of data coming from SSR on line 144 and reload the page.
5. See in the console that it didn't print an `@@merge function called`, that is, it still hasn't called the merge function as I expected, even though it doesn't have data coming from the SSR.

The main code have some commentaries about some points that can help to understand the issue.

Versions

  System:
    OS: macOS 12.0.1
  Binaries:
    Node: 12.10.0 - ~/.nvm/versions/node/v12.10.0/bin/node
    npm: 6.10.3 - ~/.nvm/versions/node/v12.10.0/bin/npm
  Browsers:
    Chrome: 103.0.5060.114
    Safari: 15.1
  npmPackages:
    @apollo/client: ^3.3.6 => 3.4.10
    apollo-link-timeout: ^3.0.0 => 3.0.0
    apollo3-cache-persist: ^0.14.0 => 0.14.0

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:3
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
hwillsoncommented, Aug 2, 2022

👋 @igorthurow - thanks for providing a reproduction. I don’t think it’s demonstrating an issue however.

  1. Renders the first time and sees on the console that it didn’t print an @@merge function called, that is, it didn’t call the merge function. This is probably because I’m simulating data coming from the SSR and using forceFetchDelay: 1000

Yes your ssrForceFetchDelay: 1000 setting is the reason why the merge function is not called. When you set ssrForceFetchDelay to anything other than 0 (and you’re using a cache-and-network fetch policy), Apollo Client toggles the fetchPolicy internally to cache-first for the number of milliseconds passed to ssrForceFetchDelay. So in your case you’ve preloaded the cache with Person:4 - Person:6, then when the query runs, it returns those people from the cache instead of attempting to run the query as that all happens before the 1000 ms mark.

  1. Remove the simulation of data coming from SSR on line 144 and reload the page.
  2. See in the console that it didn’t print an @@merge function called, that is, it still hasn’t called the merge function as I expected, even though it doesn’t have data coming from the SSR.

Keeping in mind my explanation of 3 above, when you remove the cache.restore(ssrExtractedCache) call from your reproduction and the page re-renders, you’re still getting back the IndexedDB stored cache data that you’ve already hydrated. This will behave exactly as it does for 3 - because of the ssrForceFetchDelay: 1000 setting you’re going to get back the hydrated cache data (since Apollo Client toggles to cache-first), and won’t hit the merge function because the network request didn’t happen.

1reaction
igorthurowcommented, Aug 2, 2022

@hwillson Hey bro! Thank you for the answer!

I understood. I figured it was something along those lines.

However, in my real scenario, I only perform a few queries in my SSR stream, and those I don’t want to repeat on the client side, because it would be duplicated and we would considerably increase the throughput, even more in such an accessed product. But we would like to use cache persistence to improve our performance and resiliency.

These points then seem to be in conflict: If I make my requests in the SSR stream I shouldn’t repeat on the client side, so I use ssrForceFetchDelay, but I would need to make the requests for what didn’t come from the SSR, which might have come from the cache and then they would skip. It makes sense?

To me, the ssrForceFetchDelay seemed to be closely related to the SSR stream (including the option name), and as such, the data coming from the SSR is guaranteed to be absolutely fresh. As this option actually just skips requests as long as you have something in cache, both coming from SSR and persistence, it is not safe to use both together because certainly the data delivery will eventually be stale and will only update when we redo the requests from client side.

The option I have then is either repeating the requests I just made in the SSR, on the client side, or not using cache persistence, is that it?

I was hoping to be able to make some requests in the SSR stream, merge with the cache overwriting what came from the SSR, and redo the client-side requests when they didn’t occur in the SSR stream, does that make sense? So I guarantee that I’m up to date, that I go on the network to update the cache on the first render and still get the benefit of the resiliency and performance that the cache provides.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Demystifying Cache Normalization - Apollo GraphQL Blog
It can automatically cache and normalize new data in query responses. ... to add duplicate data or refer to objects that no longer...
Read more >
Provide quick app start-up times by syncing the Apollo cache ...
In this lesson we'll look at using the "apollo3-cache-persist" package, to set-up a syncing mechanism that automatically saves any changes to the Apollo...
Read more >
Apollo Caching 1on1 - Niebardzo's Blog
From the penetration testing perspective is not important to know how the ... Apollo3-cache-persist works with all Apollo caches. This is ...
Read more >
[apollo-cache-persist] purged cached data - Stack Overflow
This is the code I have used for cache persistance using 'apollo3-cache-persist', seems to have automatically purge the cached data after ...
Read more >
Configuring the Cache – Angular - GraphQL Code Generator
v3 (latest); Caching; Configuration. Configuring the Cache. Apollo Client stores the results of its GraphQL queries in a normalized, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found