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

Disable InMemoryCache canonization by default to prevent unexpected memory growth/sharing, with options to reenable #8822

Merged
merged 7 commits into from
Sep 23, 2021

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Sep 21, 2021

Making InMemoryCache result canonization (#7439) opt-in instead of opt-out (as it currently is) should prevent the surprises of shared object references reported in #8717, and fixes the apparent memory leak in #8784, as confirmed using @CollinMonahanLab49's reproduction.

Given that canonization was a headline feature of v3.4, shipping this PR would be something of a retraction, but I believe it's the simplest way to prevent surprise and encourage using canonization (only) where it's actually useful.

Code that benefits from canonization can continue using it! In fact, there are multiple ways to override the new default behavior, with different scopes:

// This cache will canonize everything by default, unless
// specific reads request canonizeResults:false.
const cache = new InMemoryCache({
  canonizeResults: true,
});

// Override the setting for individual cache reads.
cache.readQuery({ query, canonizeResults: true });
cache.readFragment({ id, fragment, canonizeResults: false });

// Results for this query will be canonized even if canonization
// is disabled elsewhere.
const result = useQuery(query, {
  canonizeResults: true,
});

// This ObservableQuery's results will always be canonized.
const observableQuery = client.watchQuery({
  query,
  canonizeResults: true,
});

// Set the default canonizeResults option for all watchQuery
// calls (including useQuery). Passing canonizeResults:true
// to InMemoryCache may be more reliable than this.
const client = new ApolloClient({
  defaultOptions: {
    watchQuery: {
      canonizeResults: true,
    },
  },
});

I had to update a number of tests after changing the default canonization behavior, but I was able to fix every one of them using one of the tricks demonstrated above, which is reassuring.

Though the consequences of canonization (memory usage and poor fit with certain query workloads) all have good explanations, I would rather folks engage with those tradeoffs because they want to optimize specific parts of their application, rather than inheriting the consequences by default (without asking for them).

@benjamn benjamn self-assigned this Sep 21, 2021
@benjamn benjamn added this to the v3.4.x patch releases milestone Sep 21, 2021
@benjamn benjamn marked this pull request as ready for review September 21, 2021 19:19
@benjamn benjamn changed the title Disable canonization by default to prevent unwanted memory growth, with options to reenable Disable InMemoryCache canonization by default to prevent unexpected memory growth/sharing, with options to reenable Sep 21, 2021
Copy link
Contributor

@brainkim brainkim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lessons learned!

src/core/QueryInfo.ts Outdated Show resolved Hide resolved
src/cache/inmemory/inMemoryCache.ts Outdated Show resolved Hide resolved
@KeithGillette
Copy link
Contributor

I don't have any deep insight into this, but having been bitten by the switch to default canonization, I like this change, as having to opt-in would have probably inspired the scrutiny to surface the issue (#8717) it caused us before production release. The global opt-in with individual query query opt-in/out makes it easy to take advantage of the benefits of canonization and flexible enough to avoid the errors it can cause in individual cases.

@jheysen
Copy link

jheysen commented Sep 22, 2021

Same as above, I like the new default and I could even argue that the previous implementation was a semver-major change, with this reverting the change to semver-minor. An example of this is the transition that node-postgres implemented between v7 and v8, regarding SSL cert validation.

A suggestion to analyze could be to canonize objects, but not arrays. Since object arrays are only a list of references to the objects living elsewhere on the heap, if the same object is referenced more than once in the response, the memory benefits could be largely obtained while not breaking many applications that seek to work with the arrays in the responses.

benjamn added a commit that referenced this pull request Sep 22, 2021
@benjamn benjamn force-pushed the disable-canonization-by-default branch from d66a8cd to 2d1a439 Compare September 23, 2021 15:00
@brainkim
Copy link
Contributor

LGTM

@benjamn benjamn merged commit a4b03cd into main Sep 23, 2021
@benjamn benjamn deleted the disable-canonization-by-default branch September 23, 2021 20:12
@matekb
Copy link

matekb commented Sep 27, 2021

Hello,
I too have problems with the new behavior. Do you know when a patch release containing this fix will be released?
Thanks for the good work with Apollo!
Mattias

@benjamn
Copy link
Member Author

benjamn commented Sep 27, 2021

@matekb Today!

@apollographql apollographql locked as resolved and limited conversation to collaborators Feb 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.