apollo-client: Memory leak with optimistic response
When using mutations with an optimisticResponse
we found a significant increase in memory usage.
Intended outcome: No or only small increase in memory usage.
Actual outcome: Huge increase in memory usage.
How to reproduce the issue: Given an application with multiple watched queries:
- take a memory snapshot
- perform a mutation with an optimistic response (it is sufficient to update a single field of some object)
- take another memory snapshot
Alternatively use the memory profiling function of Chrome instead of taking separate snapshots - this has the advantage that we get callstacks for the allocations.
Versions
- apollo-cache-inmemory: 1.3.8
- apollo-client: 2.4.5
- apollo-link: 1.2.3
- apollo-link-context: 1.0.9
- apollo-link-error: 1.1.1
- apollo-link-http: 1.5.5
- react-apollo: 2.2.4
We suspect that this was introduced in #3394. In particular I suspect the commit 45c4169fa9bf0b4c61e3273254e851e05905a431 to be the main cause.
If InMemoryCache.optimistic
contains some entry, read
and diff
create temporary stores with that optimistic response:
https://github.com/apollographql/apollo-client/blob/48a224d504487ea480bce95685402d40c4a90eea/packages/apollo-cache-inmemory/src/inMemoryCache.ts#L137-L139
https://github.com/apollographql/apollo-client/blob/48a224d504487ea480bce95685402d40c4a90eea/packages/apollo-cache-inmemory/src/inMemoryCache.ts#L167-L169
These temporary stores are than used in the calls to readQueryFromStore
/ diffQueryAgainstStore
and should later get garbage collected. But unfortunately these store objects are internally used as key in calls to cacheKeyRoot.lookup
:
https://github.com/apollographql/apollo-client/blob/48a224d504487ea480bce95685402d40c4a90eea/packages/apollo-cache-inmemory/src/readFromStore.ts#L124-L130
https://github.com/apollographql/apollo-client/blob/48a224d504487ea480bce95685402d40c4a90eea/packages/apollo-cache-inmemory/src/readFromStore.ts#L144-L150
45c4169fa9bf0b4c61e3273254e851e05905a431 replaced the defaultMakeCacheKey
function from optimism (which internally uses a WeakMap) with the newly introduced CacheKeyNode
concept, causing these temporary stores to end up as key in one of the (strong) Maps, effectively leaking them.
The more watched queries you have, the bigger the problem because maybeBroadcastWatch
calls diff
for every single watcher. So we end up creating (and leaking) a temporary store for every single watcher for a single optimistic response (apart from the leakage, there is certainly some room for optimization here).
Not sure if this is the only problem or if there are other leaks as well. We saw a single mutation (that updates a single field) to cause a memory increase of 40-80MBs - not sure if such a huge increase can be caused by this problem alone.
@benjamn Since you were the main author of #3394, could you take a look at this? Let me know if you need more information!
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 18
- Comments: 19 (6 by maintainers)
Commits related to this issue
- Avoid DepTrackingCache for optimistic reads. This should help with #4210, since temporary optimistic ObjectCache objects will no longer be instanceof DepTrackingCache, so the result caching system wi... — committed to apollographql/apollo-client by benjamn 6 years ago
- Allow disabling InMemoryCache result caching. Although this is a rather drastic option, creating an InMemoryCache that does not attempt to cache previous results may help reduce memory usage, at the ... — committed to apollographql/apollo-client by benjamn 6 years ago
- Avoid copying entire cache on each optimistic read. Previously, the `InMemoryCache` maintained an array of recorded optimistic updates, which it would merge together into an entirely new `ObjectCache... — committed to apollographql/apollo-client by benjamn 5 years ago
Ok,
apollo-cache-inmemory@1.3.12
has just been published to npm. Closing this issue now, perhaps somewhat optimistically, because I believe the memory leaks that were specifically related to optimistic responses have been addressed. Please open new issues for any other memory problems. Thanks!O.k., tested with optimistic response. Here comes the test of clicking an option repeatedly and watching memory usage:
The difference between using optimistic response and not may be accidental, as the numbers vary. But the update definitely seems to solve the crashing when using optimistic response in my app.
As I suspected when we were originally working on this issue, optimistic cache reads could be implemented much more efficiently. This PR should improve the speed and memory consumption of optimistic reads and writes considerably: https://github.com/apollographql/apollo-client/pull/4319
@benjamn Thanks for the clarification. I saw the commit to remove the
WeakMap
, but it wasn’t clear to me why this was necessary.BTW: our tests also confirm that the memory issue is now fixed! 👍
@benjamn I was using directly InMemoryCache for a project, I was writing one big query and reading pieces using readFragments. With some very little load, my instance was quickly running out of memory because of memory spikes. With latest version, it does not.
Thanks for the quick fix on this, very very much appreciated from graphQL lovers out there
Tested this locally for Spectrum: