-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Feature/garbage collect cache #4681
Closed
thomassuckow
wants to merge
2
commits into
apollographql:master
from
thomassuckow:feature/garbage-collect-cache
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -77,6 +77,9 @@ export type ExecResult<R = any> = { | |
result: R; | ||
// Empty array if no missing fields encountered while computing result. | ||
missing?: ExecResultMissingField[]; | ||
|
||
// Fields involved in constructing the result for tracking of cache utilization | ||
involvedFields: string[]; | ||
}; | ||
|
||
type ExecStoreQueryOptions = { | ||
|
@@ -254,6 +257,7 @@ export class StoreReader { | |
|
||
return { | ||
result: execResult.result, | ||
involvedFields: execResult.involvedFields, | ||
complete: !hasMissingFields, | ||
}; | ||
} | ||
|
@@ -308,7 +312,7 @@ export class StoreReader { | |
execContext, | ||
}: ExecSelectionSetOptions): ExecResult { | ||
const { fragmentMap, contextValue, variableValues: variables } = execContext; | ||
const finalResult: ExecResult = { result: null }; | ||
const finalResult: ExecResult = { result: null, involvedFields: [rootValue.id] }; | ||
|
||
const objectsToMerge: { [key: string]: any }[] = []; | ||
|
||
|
@@ -324,6 +328,8 @@ export class StoreReader { | |
finalResult.missing = finalResult.missing || []; | ||
finalResult.missing.push(...result.missing); | ||
} | ||
|
||
finalResult.involvedFields.push(...result.involvedFields); | ||
return result.result; | ||
} | ||
|
||
|
@@ -459,15 +465,19 @@ export class StoreReader { | |
...execResults: ExecResult<T>[] | ||
): ExecResult<T> { | ||
let missing: ExecResultMissingField[] = null; | ||
let involvedFields: string[] = []; | ||
execResults.forEach(execResult => { | ||
if (execResult.missing) { | ||
missing = missing || []; | ||
missing.push(...execResult.missing); | ||
} | ||
|
||
involvedFields.push(...execResult.involvedFields); | ||
}); | ||
return { | ||
result: execResults.pop().result, | ||
missing, | ||
involvedFields, | ||
}; | ||
} | ||
|
||
|
@@ -477,13 +487,16 @@ export class StoreReader { | |
execContext: ExecContext, | ||
): ExecResult { | ||
let missing: ExecResultMissingField[] = null; | ||
let involvedFields: string[] = []; | ||
|
||
function handleMissing<T>(childResult: ExecResult<T>): T { | ||
if (childResult.missing) { | ||
missing = missing || []; | ||
missing.push(...childResult.missing); | ||
} | ||
|
||
involvedFields.push(...childResult.involvedFields); | ||
|
||
return childResult.result; | ||
} | ||
|
||
|
@@ -516,7 +529,7 @@ export class StoreReader { | |
Object.freeze(result); | ||
} | ||
|
||
return { result, missing }; | ||
return { result, missing, involvedFields }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really glad you figured out this information should be returned from the |
||
} | ||
} | ||
|
||
|
@@ -598,6 +611,7 @@ function readStoreResolver( | |
fieldName: storeKeyName, | ||
tolerable: false, | ||
}], | ||
involvedFields: [], | ||
}; | ||
} | ||
|
||
|
@@ -607,5 +621,6 @@ function readStoreResolver( | |
|
||
return { | ||
result: fieldValue, | ||
involvedFields: [], | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering about reason why this is actually connected to cache.watch.
Would this be expected to remove items that are not watched currently.
Would it make sense to gc elements that are simply not used in queries - means that they have no ability to be fetched anymore?
It is common to watch for certain queries within view but after navigating to different components moving to another set of elements to watch.
Does this mean that doing gc will mean that at the moment we will need to
watch all queries
that should stay in cache?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
I'm not sure I fully understand what point you are trying to make, so I am going to answer the rest of your questions backwards.
Yes, if desired it should be possible to allow this method to take a list of queries + variables and not remove them as well.
Nothing in the cache has "no ability to be fetched anymore", if you were to do a one off query then do the same query again it would pull it from the cache. So as a compromise, I assume anything cared about is being watched. I would be interested to hear use cases where things need to remain in the cache and are accessed via non-watched queries.
That is the use case this is targeting, we have queries returning large amounts of data but once they leave the view it is unlikely the user will access the same data again. Or if they do, it is acceptable to incur the cost of hitting the server again.
This is also why i think gc should have to be called explicitly, then the app creator can decide the most ideal time to perform this operation, it could be after 2min or 20min of user inactivity in a requestidlecallback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My use case will be strongly coming from projects that offer offline support where all cache is persisted in apollo-cache-persist.
All data that lands in the cache will stay there even after the restart which obviously brings the requirement to remove items from the cache once they were removed from the query as this will mean that they are no longer needed. For example, when executing
deleteItem
mutation we know that item should no longer be there and we can remove it from query, but sadly item will still stay on the root - workarounds we have used in many projects is to write empty object with just ID.When making a query to the server we sometimes need to handle the situation when objects are removed and simply there is no way to do that now in apollo-client. While this PR is amazing it will be nice to extend it to handle rootId on evict as well so when performing cache updates we can remove that item. I have intentionally skipped code from this PR and added just idea. More info here:
#4917 and in apollo-feature-requests/issues/4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are totally right, I spaced the case where the query is refreshed and may leave dangling things in the cache.
I believe a modified version of
cleanupCacheReferences()
in my proof of concept may cover your case. Where cleanupCacheReferences is looking for missing keys and nuking the parent item, you would want to traverse the cache and find things that are not referenced at all.Edit: Your use case may be a more appropriate to the name "gc" than mine.