Skip to content

Conversation

@jerelmiller
Copy link
Member

@jerelmiller jerelmiller commented Nov 19, 2025

See https://community.apollographql.com/t/many-repeat-cache-watches-causing-memory-cpu-overhead/9556

This PR aims to reduce the number of created observables and reuse as many as possible when watching the same item in the cache. Previously we'd create a new observable each time we called client.watchFragment and cache.watchFragment due to the pipe usage in each of these APIs. cache.watchFragment created a chain of observables, that was then also chained by client.watchFragment to provide masking. Now we subscribe to the same observable as much as we can to reduce the amount of processing time between when the cache broadcast happens and the observable value is returned.

@changeset-bot
Copy link

changeset-bot bot commented Nov 19, 2025

🦋 Changeset detected

Latest commit: 0094d70

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 19, 2025

npm i https://pkg.pr.new/apollographql/apollo-client/@apollo/client@13026

commit: 0094d70

@apollo-librarian
Copy link

apollo-librarian bot commented Nov 19, 2025

✅ Docs preview ready

The preview is ready to be viewed. View the preview

File Changes

0 new, 6 changed, 0 removed
* (developer-tools)/react/(latest)/api/link/introduction.mdx
* (developer-tools)/react/(latest)/data/fragments.mdx
* (developer-tools)/react/(latest)/data/mutations.mdx
* (developer-tools)/react/(latest)/data/subscriptions.mdx
* (developer-tools)/react/(latest)/development-testing/reducing-bundle-size.mdx
* (developer-tools)/react/(latest)/networking/authentication.mdx

Build ID: a365b2291066c874ccd8443a
Build Logs: View logs

URL: https://www.apollographql.com/docs/deploy-preview/a365b2291066c874ccd8443a

@jerelmiller jerelmiller marked this pull request as ready for review November 19, 2025 22:04
Copilot finished reviewing on behalf of jerelmiller November 19, 2025 22:26
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR optimizes watchFragment performance by reducing the number of observables created when watching the same cache items. Instead of creating a new observable chain each time client.watchFragment and cache.watchFragment are called, the implementation now reuses observables as much as possible by:

  • Passing transform functions (for data masking) via a Symbol property rather than creating new observable chains via pipe
  • Caching observables in watchSingleFragment and reusing them across multiple calls with the same parameters
  • Moving result transformation logic inside the cached observable rather than wrapping it externally

Key Changes

  • Transform functions are now passed via Symbol.for("apollo.transform") to enable observable reuse
  • watchSingleFragment now includes transform application and result caching within the shared observable
  • Equality checks use equalByQuery during broadcasts to minimize updates while maintaining result stability

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/core/ApolloClient.ts Refactored to pass masking transform via Symbol instead of piping observables, enabling direct reuse of cache observables
src/cache/core/cache.ts Major refactoring of watchFragment and watchSingleFragment to cache and reuse observables, with transform application moved inside cached observables
.size-limits.json Updated bundle size limits to reflect small increases from the new caching logic
.changeset/tiny-pumas-compete.md Added changeset describing the performance improvement
Comments suppressed due to low confidence (1)

src/cache/core/cache.ts:547

  • Potential mutation of shared constant: When ids.length === 0, the code uses emptyArrayObservable (a shared constant) and then calls Object.assign(observable, { getCurrentResult: ... }) at line 549. This mutates the shared emptyArrayObservable constant, which could cause issues if watchFragment is called multiple times with empty arrays, as they would all try to assign different getCurrentResult implementations to the same object.

Consider creating a new observable wrapper or cloning emptyArrayObservable before assigning to it.

    const observable =
      ids.length === 0 ?
        emptyArrayObservable
      : combineLatestBatched(observables).pipe(
          map(toResult),
          tap({
            subscribe: () => (subscribed = true),
            unsubscribe: () => (subscribed = false),
          }),
          shareReplay({ bufferSize: 1, refCount: true })
        );

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot finished reviewing on behalf of jerelmiller November 19, 2025 23:13
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jerelmiller jerelmiller removed the auto-cleanup 🤖 label Nov 19, 2025
@jerelmiller jerelmiller merged commit 05eee67 into release-4.1 Dec 1, 2025
42 of 43 checks passed
@jerelmiller jerelmiller deleted the jerel/experiment-more-watch-fragment branch December 1, 2025 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants