From ffa2b2a6af6c06281923e14bd3d53bf54ec33792 Mon Sep 17 00:00:00 2001 From: Jochem Date: Thu, 11 Jan 2024 03:26:06 +0100 Subject: [PATCH] Optimize cache subscriptions (#1249) --- .changeset/three-comics-greet.md | 5 ++ .../houdini/src/runtime/cache/subscription.ts | 85 +++++++++++-------- 2 files changed, 53 insertions(+), 37 deletions(-) create mode 100644 .changeset/three-comics-greet.md diff --git a/.changeset/three-comics-greet.md b/.changeset/three-comics-greet.md new file mode 100644 index 000000000..514633d81 --- /dev/null +++ b/.changeset/three-comics-greet.md @@ -0,0 +1,5 @@ +--- +'houdini': patch +--- + +Optimize cache subscriptions diff --git a/packages/houdini/src/runtime/cache/subscription.ts b/packages/houdini/src/runtime/cache/subscription.ts index 8d908e416..c082c3f35 100644 --- a/packages/houdini/src/runtime/cache/subscription.ts +++ b/packages/houdini/src/runtime/cache/subscription.ts @@ -22,16 +22,21 @@ export class InMemorySubscriptions { this.cache = cache } - private subscribers: { - [id: string]: { [fieldName: string]: FieldSelection[] } - } = {} - private referenceCounts: { - [id: string]: { [fieldName: string]: Map } - } = {} + private subscribers = new Map< + string, + Map< + string, + { + selections: FieldSelection[] + referenceCounts: Map + } + > + >() + private keyVersions: { [key: string]: Set } = {} activeFields(parent: string): string[] { - return Object.keys(this.subscribers[parent] || {}) + return Object.keys(this.subscribers.get(parent) || {}) } add({ @@ -137,14 +142,23 @@ export class InMemorySubscriptions { type: string }) { const spec = selection[0] + // if we haven't seen the id or field before, create a list we can add to - if (!this.subscribers[id]) { - this.subscribers[id] = {} + if (!this.subscribers.has(id)) { + this.subscribers.set(id, new Map()) } - if (!this.subscribers[id][key]) { - this.subscribers[id][key] = [] + + const subscriber = this.subscribers.get(id)! + + if (!subscriber.has(key)) { + subscriber.set(key, { + selections: [], + referenceCounts: new Map(), + }) } + const subscriberField = subscriber.get(key)! + // if this is the first time we've seen the raw key if (!this.keyVersions[key]) { this.keyVersions[key] = new Set() @@ -153,21 +167,15 @@ export class InMemorySubscriptions { // add this version of the key if we need to this.keyVersions[key].add(key) - if (!this.subscribers[id][key].map(([{ set }]) => set).includes(spec.set)) { - this.subscribers[id][key].push([spec, selection[1]]) + if (!subscriberField.selections.some(([{ set }]) => set === spec.set)) { + subscriberField.selections.push([spec, selection[1]]) } - // if this is the first time we've seen this key - if (!this.referenceCounts[id]) { - this.referenceCounts[id] = {} - } - if (!this.referenceCounts[id][key]) { - this.referenceCounts[id][key] = new Map() - } - const counts = this.referenceCounts[id][key] - // we're going to increment the current value by one - counts.set(spec.set, (counts.get(spec.set) || 0) + 1) + subscriberField.referenceCounts.set( + spec.set, + (subscriberField.referenceCounts.get(spec.set) || 0) + 1 + ) // reset the lifetime for the key this.cache._internal_unstable.lifetimes.resetLifetime(id, key) @@ -293,7 +301,7 @@ export class InMemorySubscriptions { } get(id: string, field: string): FieldSelection[] { - return this.subscribers[id]?.[field] || [] + return this.subscribers.get(id)?.get(field)?.selections || [] } remove( @@ -346,18 +354,16 @@ export class InMemorySubscriptions { reset() { // Get all subscriptions that do not start with the rootID - const subscribers = Object.entries(this.subscribers).filter( - ([id]) => !id.startsWith(rootID) - ) + const subscribers = [...this.subscribers.entries()].filter(([id]) => !id.startsWith(rootID)) // Remove those subcribers from this.subscribers for (const [id, _fields] of subscribers) { - delete this.subscribers[id] + this.subscribers.delete(id) } // Get list of all SubscriptionSpecs of subscribers const subscriptionSpecs = subscribers.flatMap(([_id, fields]) => - Object.values(fields).flatMap((field) => field.map(([spec]) => spec)) + [...fields.values()].flatMap((field) => field.selections.map(([spec]) => spec)) ) return subscriptionSpecs @@ -368,12 +374,16 @@ export class InMemorySubscriptions { // checking reference counts let targets: SubscriptionSpec['set'][] = [] + const subscriber = this.subscribers.get(id) + const subscriberField = subscriber?.get(fieldName) + for (const spec of specs) { + const counts = subscriber?.get(fieldName)?.referenceCounts + // if we dont know this field/set combo, there's nothing to do (probably a bug somewhere) - if (!this.referenceCounts[id]?.[fieldName]?.has(spec.set)) { + if (!counts?.has(spec.set)) { continue } - const counts = this.referenceCounts[id][fieldName] const newVal = (counts.get(spec.set) || 0) - 1 // decrement the reference of every field @@ -387,8 +397,8 @@ export class InMemorySubscriptions { } // we do need to remove the set from the list - if (this.subscribers[id]) { - this.subscribers[id][fieldName] = this.get(id, fieldName).filter( + if (subscriberField) { + subscriberField.selections = this.get(id, fieldName).filter( ([{ set }]) => !targets.includes(set) ) } @@ -397,17 +407,18 @@ export class InMemorySubscriptions { removeAllSubscribers(id: string, targets?: SubscriptionSpec[], visited: string[] = []) { visited.push(id) + const subscriber = this.subscribers.get(id) // every field that currently being subscribed to needs to be cleaned up - for (const field of Object.keys(this.subscribers[id] || [])) { + for (const [key, val] of subscriber?.entries() ?? []) { // grab the current set of subscribers - const subscribers = targets || this.subscribers[id][field].map(([spec]) => spec) + const subscribers = targets || val.selections.map(([spec]) => spec) // delete the subscriber for the field - this.removeSubscribers(id, field, subscribers) + this.removeSubscribers(id, key, subscribers) // look up the value for the field so we can remove any subscribers that existed because of a // subscriber to this record - const { value, kind } = this.cache._internal_unstable.storage.get(id, field) + const { value, kind } = this.cache._internal_unstable.storage.get(id, key) // if the field is a scalar, there's nothing more to do if (kind === 'scalar') {