Skip to content
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

[Proposal] - Disabling Cache #1419

Closed
chriscartlidge opened this issue Mar 16, 2017 · 61 comments
Closed

[Proposal] - Disabling Cache #1419

chriscartlidge opened this issue Mar 16, 2017 · 61 comments

Comments

@chriscartlidge
Copy link

chriscartlidge commented Mar 16, 2017

Before getting into the issue I just wanted to say what a great job you are doing with the apollo-client here at OpenTable we really are enjoying working with it!

Situation

We are currently using the apollo-client in a number of different use cases at it's working well 👍.

However, we have one application which is consuming the apollo-client via node-js and it is very memory sensitive (for various reasons) and because of this having the cache/store caching all queries and data is not great for us.

Proposals

Because of this, we have a couple of solutions that we'd like to propose:

1 (Disabling the cache globally)

One possible way to disable caching is to specify a cachePolicy hash when creating the client within the networkInterface, with the default policy to always cache e.g.

const options = { uri: ..., cachePolicy: { query: true, data: false } };
const networkInterface = createBatchingNetworkInterface(options);

Advantages:

  • Very easy to control the cache at an application level

Disadvantages:

  • Creates a conflicting API for querying e.g. noFetch=true with caching set to false would produce no results, which could be strange if the developer didn't have the context of what the policy was set to globally.

2 (Disabling per query)

The next natural alternative to controlling the cache policy is to do this at a query level, with the default policy to always cache e.g.

const restaurant = client.query({query: ...,  cachePolicy: { query: true, data: false } });

Advantages:

  • The developer has the fine-grained control of what queries/data to cache/not to cache.
  • Avoids a counter-intuitive API with the developer needing to know a global policy.

Disadvantages:

  • If the application requires the cache to be totally disabled this is not optimal.

3 (Cache size Limit)

Instead of disabling the caching we could set a byte limit for the cache size within the networkInterface e.g.

const options = { uri: ..., cacheSize: 10000 };
const networkInterface = createBatchingNetworkInterface(options);

Advantages:

  • Can control the memory consumption of the cache for sensitive applications.

From a personal point of view, I'd like to be able to control the cache on a per-query level whilst also being able to set the size of the cache.

What are your thoughts on this?

@thebigredgeek
Copy link
Contributor

thebigredgeek commented Mar 17, 2017

@chriscartlidge are you using Apollo Client in nodejs for server side rendering? If so, cache context should be garbage collected so long as you aren't keeping references to it after the render request. If you are simply trying to make server-to-server GraphQL requests, I would advise you to use something other than Apollo Client for that use case... probably just raw graphql-js. Apollo Client is inherently designed to use with Redux for a state-persisted single page apps, not for server-to-server requests (other than the case of server sider rendering of said apps)

@stubailo
Copy link
Contributor

Thanks for filing an issue, @chriscartlidge! This is definitely an interesting thought we haven't heard much about yet!

I'd love to find out more about the use case here, and the reasons you have selected Apollo for this case. At first glance, it doesn't sound like being able to control the caching on a per-query level is necessary for what you are doing, and disabling caching entirely is sufficient.

Some specific questions:

  1. What is this Node app doing? Is it some sort of API, server side rendering, data processing, etc?
  2. How many, and which, Apollo Client features is this server using? Does it have use cases for polling, mutation handling, pagination, etc?
  3. What's the reason for selecting Apollo Client for this use case? (It's very clear why if it is server-side rendering for example, but I wonder if there are also reasons to use it with an API server, and how much we should try to improve that situation)

@phuochau
Copy link

Hi guys, so what's the best way to disable cache on ApolloClient?

@thebigredgeek
Copy link
Contributor

@phuochau I don't think disabling the cache is currently supported. I believe the intent behind apollo is to provide an opinionated graphql + caching solution for building client-side apps.

@helfer
Copy link
Contributor

helfer commented Mar 17, 2017

@chriscartlidge This is something we've been thinking about for a while. I'm curious about your specific use-case, because it might make more sense to simply fetch the GraphQL query "by hand" if you don't ever want to cache things and don't need results to be reactive (i.e. updating when newer results from overlapping queries come in).

Apollo Client currently uses the normalized cache to watch for updates to queries, which means that turning off the cache would make Apollo effectively just a fancy fetch function.

However, we have thought about this use-case, and our currently favored idea is the following:

cachePolicy can be specified per-query. The are three cachePolicy options:

  1. normal: the query result is stored in the cache indefinitely, but can be evicted if the cache grows beyond a certain size threshold.
  2. no-cache: the query result is not stored in the cache.
  3. offline-critical: the query result is stored in the cache indefinitely, and is not evicted at the normal threshold cache size. It is only evicted when the cache reaches a system resource-determined threshold, and only after all the data stored with the normal cachePolicy is evicted.

Note that the no-cache policy could only be used with query, not watchQuery unless we make some major changes to how Apollo Client delivers results.

If you're interested in this, we could get you started on implementing this in a PR! Does that sound like something you'd have time & motivation for?

@langpavel
Copy link

@chriscartlidge @phuochau Disable cache? If you spend more time reading sources then writing issue, you will discover that you can use networkLayer directly which effectively skip all caching. Yep, it is (in most cases) same as using fetch or XMLHttpRequest directly. If you want trigger new request and want it to be recorded back into Apollo store, then you should asking more precisely. But I think this is possible too.

@thebigredgeek
Copy link
Contributor

@langpavel I don't think most people read through source code in their spare time. Some of us contribute more than others. That is to be expected, and I think everyone deserves respect :)

@langpavel
Copy link

@thebigredgeek Reading sources is for me the easier way in case of Apollo :-) So sorry, I didn't want to be impolite :-)

@primetwig
Copy link

primetwig commented Mar 23, 2017

Project I'm working on has a specific data structure which makes me unable to use dataIdFromObject. Example: Updating an item (e.g. changing its status) moves it from one list(query) to another. Writing updateQueries for every mutation is kinda verbose (although I have written a service for creating some generic update functions).
Optional disabling of cache is definitely a must-have.

@chriscartlidge
Copy link
Author

@thebigredgeek @helfer @stubailo Thanks for the constructive replies and apologies for replying back to this late 👍

@stubailo Let me give you a bit of overview about our setup at OpenTable.

Overview

We currently have a front end structure where our front end is divided into self-contained microsites e.g. the homepage & search page are 2 totally different applications.

As you can expect there is a use case of sharing frontend components between these two sites e.g. headers, footers and other business related components.

For this, we are using something that we developed and open-sourced called OpenComponents which allows to build and share these components across microsites. These components are hosted on an OC-Registry and microsites can make a http request where the component will then be server side rendered by the registry and sent back to the microsite in the response as html.

Where the apollo-client comes in

We can have many components on a single page e.g. the header, restaurant-list and location picker. These components are responsible for fetching of their own data via calling our GraphQL API.

Our idea of using the apollo-client was to leverage the batching and duplication it has to reduce the calls we make to the GraphQL API and thus the underlying micro services that feed it.

This worked quite well for us at first as each instance of the registries had a singleton instance of the client which the components used to fetch their data and we had a lot of ROI on the batching/de-duplication cross components.

However, due to the amount of request that we have (double digit millions per day) for components the caching got out of hand as it kept growing and growing and thus the proposal I sent above.

We currently don't use pagination, mutation handling or polling but it is something we are very interested in doing in the coming months.


Note: We also use Apollo within our microsites for server-side render when components are not used.

Conclusion

I hope that's a decent overview of our current situation and why we have chosen the apollo-client for use with OpenComponents.

I like the suggestion that you have made @helfer and I'd like to explore helping out more on getting these supported within the client.

@thebigredgeek
Copy link
Contributor

thebigredgeek commented Mar 23, 2017

I am wondering if we could solve this, along with other requests for a more flexible caching structure, by implementing a sort of "pluggable" storage interface, as we do with NetworkInterface, for the cache. This would be in lieu of direct coupling with Redux. I need to look more into how Apollo Client returns results into integration libs like react-apollo before I can say confidently that this would work, though... because I am guessing some libs require that the entry be present in Redux before they receive a value at all, so I am not sure how we could implement something like a "No-Op" cache if that is the case. @helfer I may have mentioned this to you before?

#1432

@ghost
Copy link

ghost commented Apr 13, 2017

This would be nice. I'm working on a project using graphql and we will be doing queries with sensitive data that can't stay around in memory. Being able to disable cache for these queries would be great. I'd rather be able to use apollo for these instead of doing async calls myself.

@adamdawkins
Copy link

@thebigredgeek I was doing exactly what you said not to here: #1419 (comment)

I'm using ApolloClient on the server, just to use GraphQL, because I came to GraphQL through Apollo, so it was my default.

Presumably I don't need to use ApolloServer either, but just an executable schema?

@maximblack
Copy link

I acomplished that, using directly networkInterface:

  @bind
  loadMoreItems(offset, limit) {
    const { client, loadMoreItemsGQLQuery } = this.props;
    return client.networkInterface.query({
      query: loadMoreItemsGQLQuery,
      variables: {
        offset,
        limit
      }
    }).then((res) => {
      const { data } = res;
      _(data)
        .values()
        .first()
        .map((item, i) => {
          this.items.set(offset + i, item);
        })
      ;
    });
  }

@credli
Copy link

credli commented Jun 16, 2017

@maximblack I also managed to achieve this using a network-only fetch policy like so:

const { client } = this.props
client.query({
  query: myGQLQuery,
  fetchPolicy: 'network-only', // skip the cache
}).then(res => {
  // done
})

Use the withApollo() HOC to get a client injected into this.props.

@helfer
Copy link
Contributor

helfer commented Jun 17, 2017

@maximblack @credli you might be interested in what we're working on over here: https://github.com/apollographql/apollo-fetcher/blob/44fe1084954882039d76edbbf921882577d8fb13/SPEC.md

@stale
Copy link

stale bot commented Jul 15, 2017

This issue has been automatically marked as stale becuase it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions to Apollo Client!

@vinhlh
Copy link

vinhlh commented Jul 26, 2017

If we can set fetchPolicy to cache-and-network globally, then we don't have to worry about invalidating the caches everywhere (refetch).
Could we have an option like this

const client = new ApolloClient({
  defaultFetchPolicy: 'cache-and-network'
})

@kaladivo
Copy link

@vinhlh Yeah! That would be great!

@cport1
Copy link

cport1 commented Jul 27, 2017

https://github.com/apollographql/apollo-link

@mravey
Copy link

mravey commented Aug 16, 2017

Could you provide any help on how to use apollo-link to bypass the cache?

@sepehr500
Copy link

I am also using apollo-client with an application that has sensitive information, so would like to cache nothing on disk by default. Is there a way to do this?

@stubailo
Copy link
Contributor

Apollo Client doesn't cache on disk - it's just a JS variable, so when you close the page it's all gone.

@mhuggins
Copy link

mhuggins commented Oct 27, 2017 via email

@michaelknoch
Copy link
Contributor

is it still possible to use apollo.networkinterface directly with apollo 2.0?
@rrhvella

@rrhvella
Copy link

@michaelknoch
I actually switched to using apollo-link directly: http://apollo-link-docs.netlify.com/docs/link/

@michaelknoch
Copy link
Contributor

michaelknoch commented Nov 13, 2017

this works like charm, thanks @rrhvella

example:

const query = operation => makePromise((execute(apolloClient.link, operation)));

query({ query, variables })
    .then(result => {
        console.log(result)
    })
    .catch(err => this.setErrorState(err));

I wondered why its not possible to create a separate apolloClient without cache.
It seems that apollo-client is explicitly depending on the cache object

const clientWithoutCache = new ApolloClient({
  link: new HttpLink({ uri: 'http://api.githunt.com/graphql' }),
  cache: null
});

@JeffML
Copy link

JeffML commented Dec 8, 2017

How about a NullCache implementation? It would work akin to a mock cache, with support for fragmentMatcher and whatever else is needed, but would not actually store anything, and read operations would return NOT FOUND.

@mccallofthewild
Copy link

mccallofthewild commented Dec 20, 2017

I just overwrote the query method:

const apolloQuery = client.query.bind(client)
client.query = async (...args) => {
  await client.cache.reset()
  return apolloQuery(...args)
}

EDIT:
Replaced client.resetStore() with client.cache.reset().
Using client.resetStore() throws something like ERROR: store reset while query was in flight when queries are executed concurrently. client.cache.reset() doesn't throw any errors and, perhaps more importantly, just resets the cache instead of the entire store. 😄

@w9
Copy link

w9 commented Jan 7, 2018

@OskarKaminski Dosn't seem to work. (apollo-client: 2.0.4)

I find creating an ObservableQuery using watchQuery and then refetch much more idiomatic.

@webmobiles
Copy link

@OskarKaminski fetchPolicy globally does not work, I must to do it one by one

@boolieman
Copy link

I am also hitting a situation where the Apollo Cache seems to be far to aggressive and we are not done completing all of our mutations. Due to this we keep getting some stale data that is a property of an object causing 'bugs' currently during our refactor to utilize graphql more predominantly.

@wsfuller
Copy link

@webmobiles Your solution worked perfectly for me.

I'm running a React frontend with React-Tables that's using Server-side pagination. When I would go from the Table > Profile page > delete object > Redirect back to Table a new query was not being fired. fetchPolicy: 'network-only', worked perfectly

fetchTableData = () => {
    this.props.client.query({
      query: this.props.fetchQuery,
      fetchPolicy: 'network-only',
      variables: {...},
    }).then((res) => {...}).catch((res) => {...}); 

In the application I'm building there is the same need for cache on the frontend. It would be great to have something with the network layer to disable cache.

const client = new ApolloClient({
  link: new HttpLink({ ... }),
  cache: null,
});

That would be fantastic. I understand the importance of cache and I'm probably an edge case but would be super helpful to have something like that.

@michaelknoch
Copy link
Contributor

michaelknoch commented Apr 19, 2018

i have just read the apollo-client 2.2.2 changelog:
Add new fetchPolicy called 'no-cache' to bypass reading from or saving to the cache when making a query

has anyone tried it already?

@gabrielclima
Copy link

Im using apolloClient.cache.data.clear() here

@hwillson
Copy link
Member

As mentioned in #1419 (comment), modern versions of apollo-client now support a no-cache fetchPolicy option (using both query and watchQuery). This should address the issues outlined here, so I'll close this off. Happy to re-open if anyone disagrees. Thanks!

@bogdansoare
Copy link

@hwillson this only disables the cache per query, but we need the option to disable the cache globally

@hwillson
Copy link
Member

@bogdansoare The apollo-client constructor has a defaultOptions param, that allows you to set an application wide fetchPolicy of no-cache. Is that good enough or would you prefer something different?

@bogdansoare
Copy link

@hwillson understood. Will something like this work ?

const defaultOptions = {
  watchQuery: {
    fetchPolicy: 'no-cache',
    errorPolicy: 'ignore'
  },
  query: {
    fetchPolicy: 'no-cache',
    errorPolicy: 'all'
  },
  mutate: {
    errorPolicy: 'all'
  }
};

@hwillson
Copy link
Member

hwillson commented Jun 4, 2018

@bogdansoare That should work - let us know if it doesn't!

@paschaldev
Copy link

paschaldev commented Jun 5, 2018

@hwillson

const defaultOptions = {

	watchQuery: {

		fetchPolicy: 'no-cache',
		errorPolicy: 'ignore'
	},
	query: {

		fetchPolicy: 'no-cache',
		errorPolicy: 'all'
	},
}

Unfortunately that did't work for me. My queries return undefined although when I check the network tab, it contains the actual data.

I'm using the <Query /> component

@hwillson
Copy link
Member

hwillson commented Jun 5, 2018

@paschaldev Any chance you could put together a small runnable reproduction that shows this happening? That would greatly help with troubleshooting. Thanks!

@paschaldev
Copy link

@hwillson I'll try to. For now I'll just go back to using the cache

@ozanmanav
Copy link

ozanmanav commented Jan 11, 2019

To Disable Query Component Caching with react-apollo, you can use that;

<Query query={...} fetchPolicy="network-only" />

@sagarpatilaw18
Copy link

@maximblack I also managed to achieve this using a network-only fetch policy like so:

const { client } = this.props
client.query({
  query: myGQLQuery,
  fetchPolicy: 'network-only', // skip the cache
}).then(res => {
  // done
})

Use the withApollo() HOC to get a client injected into this.props.

Thanks its great we closed our issue

@cyrrill
Copy link

cyrrill commented Jul 18, 2019

If you are using Nuxt, one strategy can be to write a plugin which wraps the query function on the default client, to turn off cache.

This code will yield an injected function $apolloQuery() which can be used in the app.

Create a plugin as ~/plugins/apollo-wrapper.js:

export default ({ app }, inject) => {

    inject('apolloQuery', async(params) => {
        let result, errors
        params.fetchPolicy = 'no-cache'
        try {
            result = await app.apolloProvider.defaultClient.query(params)
        } catch (e) {
            if (e.graphQLErrors && e.graphQLErrors.length) {
                errors = apolloErrors(e.graphQLErrors)
            } else {
                errors = e.message
            }
            return { data: null, errors }
        }
        let data = Object.values(result.data)[0]
        return { data, errors }
    })

(...)

(implement: apolloErrors, simple function that parses exceptions)

then in nuxt.config.js:

plugins: [
    { src: '@/plugins/apollo-wrapper' },

this allows for you to call queries in your store such as:

// Contains GQL query
import GET_FOOBAR from '~/graphql/queries/getFoobar.gql'

(...)

export const actions = {

    async getFoobar(context, payload) {
        let { data, errors } = await this.$apolloQuery({
            query: GET_FOOBAR,
            variables: payload
        })
        if (data) {
            return { foobar: data.foobar, errors: null }
        } else {
            return { foobar: null, errors }
        }
    },

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests