Skip to content

Load all the records once instead of 3 times in garbageCollect() #73

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

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public final class com/apollographql/cache/normalized/GarbageCollectionKt {
public static final fun garbageCollect-SxA4cEA (Lcom/apollographql/cache/normalized/api/NormalizedCache;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;J)Lcom/apollographql/cache/normalized/GarbageCollectResult;
public static synthetic fun garbageCollect-SxA4cEA$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;JILjava/lang/Object;)Lcom/apollographql/cache/normalized/GarbageCollectResult;
public static synthetic fun garbageCollect-SxA4cEA$default (Lcom/apollographql/cache/normalized/api/NormalizedCache;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;JILjava/lang/Object;)Lcom/apollographql/cache/normalized/GarbageCollectResult;
public static final fun getReachableCacheKeys (Lcom/apollographql/cache/normalized/api/NormalizedCache;)Ljava/util/Set;
public static final fun getReachableCacheKeys (Ljava/util/Map;)Ljava/util/Set;
public static final fun removeDanglingReferences (Lcom/apollographql/cache/normalized/ApolloStore;)Lcom/apollographql/cache/normalized/RemovedFieldsAndRecords;
public static final fun removeDanglingReferences (Lcom/apollographql/cache/normalized/api/NormalizedCache;)Lcom/apollographql/cache/normalized/RemovedFieldsAndRecords;
public static final fun removeStaleFields-SxA4cEA (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;J)Lcom/apollographql/cache/normalized/RemovedFieldsAndRecords;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cach
final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/store(com.apollographql.cache.normalized/ApolloStore, kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/store|store@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.ApolloStore;kotlin.Boolean){}[0]
final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/allRecords(): kotlin.collections/Map<kotlin/String, com.apollographql.cache.normalized.api/Record> // com.apollographql.cache.normalized/allRecords|allRecords@com.apollographql.cache.normalized.api.NormalizedCache(){}[0]
final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/garbageCollect(com.apollographql.cache.normalized.api/MaxAgeProvider, kotlin.time/Duration = ...): com.apollographql.cache.normalized/GarbageCollectResult // com.apollographql.cache.normalized/garbageCollect|garbageCollect@com.apollographql.cache.normalized.api.NormalizedCache(com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.time.Duration){}[0]
final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/getReachableCacheKeys(): kotlin.collections/Set<com.apollographql.cache.normalized.api/CacheKey> // com.apollographql.cache.normalized/getReachableCacheKeys|getReachableCacheKeys@com.apollographql.cache.normalized.api.NormalizedCache(){}[0]
final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/removeDanglingReferences(): com.apollographql.cache.normalized/RemovedFieldsAndRecords // com.apollographql.cache.normalized/removeDanglingReferences|removeDanglingReferences@com.apollographql.cache.normalized.api.NormalizedCache(){}[0]
final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/removeStaleFields(com.apollographql.cache.normalized.api/MaxAgeProvider, kotlin.time/Duration = ...): com.apollographql.cache.normalized/RemovedFieldsAndRecords // com.apollographql.cache.normalized/removeStaleFields|removeStaleFields@com.apollographql.cache.normalized.api.NormalizedCache(com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.time.Duration){}[0]
final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/removeUnreachableRecords(): kotlin.collections/Set<com.apollographql.cache.normalized.api/CacheKey> // com.apollographql.cache.normalized/removeUnreachableRecords|removeUnreachableRecords@com.apollographql.cache.normalized.api.NormalizedCache(){}[0]
Expand All @@ -372,6 +371,7 @@ final fun (com.apollographql.cache.normalized/ApolloStore).com.apollographql.cac
final fun (com.apollographql.cache.normalized/ApolloStore).com.apollographql.cache.normalized/removeStaleFields(com.apollographql.cache.normalized.api/MaxAgeProvider, kotlin.time/Duration = ...): com.apollographql.cache.normalized/RemovedFieldsAndRecords // com.apollographql.cache.normalized/removeStaleFields|removeStaleFields@com.apollographql.cache.normalized.ApolloStore(com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.time.Duration){}[0]
final fun (com.apollographql.cache.normalized/ApolloStore).com.apollographql.cache.normalized/removeUnreachableRecords(): kotlin.collections/Set<com.apollographql.cache.normalized.api/CacheKey> // com.apollographql.cache.normalized/removeUnreachableRecords|removeUnreachableRecords@com.apollographql.cache.normalized.ApolloStore(){}[0]
final fun (kotlin.collections/Collection<com.apollographql.cache.normalized.api/Record>?).com.apollographql.cache.normalized.api/dependentKeys(): kotlin.collections/Set<kotlin/String> // com.apollographql.cache.normalized.api/dependentKeys|dependentKeys@kotlin.collections.Collection<com.apollographql.cache.normalized.api.Record>?(){}[0]
final fun (kotlin.collections/Map<kotlin/String, com.apollographql.cache.normalized.api/Record>).com.apollographql.cache.normalized/getReachableCacheKeys(): kotlin.collections/Set<com.apollographql.cache.normalized.api/CacheKey> // com.apollographql.cache.normalized/getReachableCacheKeys|getReachableCacheKeys@kotlin.collections.Map<kotlin.String,com.apollographql.cache.normalized.api.Record>(){}[0]
final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/normalize(#A, com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/CacheKeyGenerator, com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ..., kotlin/String): kotlin.collections/Map<kotlin/String, com.apollographql.cache.normalized.api/Record> // com.apollographql.cache.normalized.api/normalize|normalize@com.apollographql.apollo.api.Executable<0:0>(0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider;kotlin.String){0§<com.apollographql.apollo.api.Executable.Data>}[0]
final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/readDataFromCache(com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache, com.apollographql.cache.normalized.api/CacheResolver, com.apollographql.cache.normalized.api/CacheHeaders, com.apollographql.cache.normalized.api/FieldKeyGenerator = ...): #A // com.apollographql.cache.normalized.api/readDataFromCache|readDataFromCache@com.apollographql.apollo.api.Executable<0:0>(com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.ReadOnlyNormalizedCache;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.CacheHeaders;com.apollographql.cache.normalized.api.FieldKeyGenerator){0§<com.apollographql.apollo.api.Executable.Data>}[0]
final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/readDataFromCache(com.apollographql.cache.normalized.api/CacheKey, com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache, com.apollographql.cache.normalized.api/CacheResolver, com.apollographql.cache.normalized.api/CacheHeaders, com.apollographql.cache.normalized.api/FieldKeyGenerator = ...): #A // com.apollographql.cache.normalized.api/readDataFromCache|readDataFromCache@com.apollographql.apollo.api.Executable<0:0>(com.apollographql.cache.normalized.api.CacheKey;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.ReadOnlyNormalizedCache;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.CacheHeaders;com.apollographql.cache.normalized.api.FieldKeyGenerator){0§<com.apollographql.apollo.api.Executable.Data>}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import com.apollographql.cache.normalized.api.receivedDate
import kotlin.time.Duration

@ApolloInternal
fun NormalizedCache.getReachableCacheKeys(): Set<CacheKey> {
fun NormalizedCache.getReachableCacheKeys(roots: List<CacheKey>, reachableCacheKeys: MutableSet<CacheKey>) {
val records = loadRecords(roots.map { it.key }, CacheHeaders.NONE).associateBy { it.key }
fun Map<String, Record>.getReachableCacheKeys(): Set<CacheKey> {
fun Map<String, Record>.getReachableCacheKeys(roots: List<CacheKey>, reachableCacheKeys: MutableSet<CacheKey>) {
val records = roots.mapNotNull { this[it.key] }
val cacheKeysToCheck = mutableListOf<CacheKey>()
for ((key, record) in records) {
reachableCacheKeys.add(CacheKey(key))
for (record in records) {
reachableCacheKeys.add(CacheKey(record.key))
cacheKeysToCheck.addAll(record.referencedFields() - reachableCacheKeys)
}
if (cacheKeysToCheck.isNotEmpty()) {
Expand All @@ -45,7 +45,12 @@ fun NormalizedCache.allRecords(): Map<String, Record> {
* @return the cache keys that were removed.
*/
fun NormalizedCache.removeUnreachableRecords(): Set<CacheKey> {
val unreachableCacheKeys = allRecords().keys.map { CacheKey(it) } - getReachableCacheKeys()
val allRecords = allRecords()
return removeUnreachableRecords(allRecords)
}

private fun NormalizedCache.removeUnreachableRecords(allRecords: Map<String, Record>): Set<CacheKey> {
val unreachableCacheKeys = allRecords.keys.map { CacheKey(it) } - allRecords.getReachableCacheKeys()
remove(unreachableCacheKeys, cascade = false)
return unreachableCacheKeys.toSet()
}
Expand Down Expand Up @@ -79,10 +84,18 @@ fun NormalizedCache.removeStaleFields(
maxAgeProvider: MaxAgeProvider,
maxStale: Duration = Duration.ZERO,
): RemovedFieldsAndRecords {
val allRecords: Map<String, Record> = allRecords()
val allRecords = allRecords().toMutableMap()
return removeStaleFields(allRecords, maxAgeProvider, maxStale)
}

private fun NormalizedCache.removeStaleFields(
allRecords: MutableMap<String, Record>,
maxAgeProvider: MaxAgeProvider,
maxStale: Duration,
): RemovedFieldsAndRecords {
val recordsToUpdate = mutableMapOf<String, Record>()
val removedFields = mutableSetOf<String>()
for (record in allRecords.values) {
for (record in allRecords.values.toList()) {
var recordCopy = record
for (field in record.fields) {
// Consider the client controlled max age
Expand All @@ -103,6 +116,11 @@ fun NormalizedCache.removeStaleFields(
recordCopy -= field.key
recordsToUpdate[record.key] = recordCopy
removedFields.add(record.key + "." + field.key)
if (recordCopy.isEmptyRecord()) {
allRecords.remove(record.key)
} else {
allRecords[record.key] = recordCopy
}
continue
}
}
Expand All @@ -116,6 +134,11 @@ fun NormalizedCache.removeStaleFields(
recordCopy -= field.key
recordsToUpdate[record.key] = recordCopy
removedFields.add(record.key + "." + field.key)
if (recordCopy.isEmptyRecord()) {
allRecords.remove(record.key)
} else {
allRecords[record.key] = recordCopy
}
}
}
}
Expand Down Expand Up @@ -160,6 +183,10 @@ fun ApolloStore.removeStaleFields(
*/
fun NormalizedCache.removeDanglingReferences(): RemovedFieldsAndRecords {
val allRecords: MutableMap<String, Record> = allRecords().toMutableMap()
return removeDanglingReferences(allRecords)
}

private fun NormalizedCache.removeDanglingReferences(allRecords: MutableMap<String, Record>): RemovedFieldsAndRecords {
val recordsToUpdate = mutableMapOf<String, Record>()
val allRemovedFields = mutableSetOf<String>()
do {
Expand Down Expand Up @@ -254,10 +281,11 @@ fun NormalizedCache.garbageCollect(
maxAgeProvider: MaxAgeProvider,
maxStale: Duration = Duration.ZERO,
): GarbageCollectResult {
val allRecords = allRecords().toMutableMap()
return GarbageCollectResult(
removedStaleFields = removeStaleFields(maxAgeProvider, maxStale),
removedDanglingReferences = removeDanglingReferences(),
removedUnreachableRecords = removeUnreachableRecords()
removedStaleFields = removeStaleFields(allRecords, maxAgeProvider, maxStale),
removedDanglingReferences = removeDanglingReferences(allRecords),
removedUnreachableRecords = removeUnreachableRecords(allRecords)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class ReachableCacheKeysTest {
)

apolloClient.query(query).fetchPolicy(FetchPolicy.NetworkOnly).execute()
var reachableCacheKeys = store.accessCache { it.getReachableCacheKeys() }
var reachableCacheKeys = store.accessCache { it.allRecords().getReachableCacheKeys() }
assertContentEquals(
listOf(
CacheKey("QUERY_ROOT"),
Expand All @@ -147,7 +147,7 @@ class ReachableCacheKeysTest {

// Remove User 43, now Repositories 5 and 6 should not be reachable / 7 should still be reachable
store.remove(CacheKey("User:43"), cascade = false)
reachableCacheKeys = store.accessCache { it.getReachableCacheKeys() }
reachableCacheKeys = store.accessCache { it.allRecords().getReachableCacheKeys() }
assertContentEquals(
listOf(
CacheKey("QUERY_ROOT"),
Expand All @@ -169,7 +169,7 @@ class ReachableCacheKeysTest {
CacheKey("Repository:500"),
RepositoryFragment(id = "500", __typename = "Repository", starGazers = emptyList()),
)
reachableCacheKeys = store.accessCache { it.getReachableCacheKeys() }
reachableCacheKeys = store.accessCache { it.allRecords().getReachableCacheKeys() }
assertContentEquals(
listOf(
CacheKey("QUERY_ROOT"),
Expand Down