Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Different TTL for queries and objects in the cache #88

Open
tolmachev21 opened this issue Jan 31, 2025 · 6 comments
Open

Different TTL for queries and objects in the cache #88

tolmachev21 opened this issue Jan 31, 2025 · 6 comments

Comments

@tolmachev21
Copy link

Hi guys! Thank you very much for the opportunity to use this library. I would be very grateful if you could help me with the following problem :)

Minimum context:

I want to use the following invalidationPolicy configuration settings:

invalidationPolicies: {
        timeToLive: 15 * 60 * 1000, // 15 minutes in milliseconds
        types: {
            User: {
                timeToLive: 60 * 1000, // 1 minute in milliseconds
            },
        },
    }

It turns out that queries inside ROOT_QUERY will live for 15 minutes. And the cached objects themselves will live for one minute.

Problem description (page and query names are used as an example only):

  1. I go to the /user page, which uses the getUser query. When it is executed, the query itself (i.e. getUser) and the response of this query (i.e. the User object itself) are cached.

  2. Then I wait one minute for the User object in the cache to become invalid (i.e. the TTL has expired). It is worth noting that the getUser query is still valid.

  3. I go to the page /. At this point, the getUser query remains in the cache inside ROOT_QUERY, but the cached User object is not there (it was deleted because it was invalid).

  4. I go back to the page /user within 15 minutes. And now I have no data on the page, because the ROOT_QUERY still contains broken links to objects in the cache, which are no longer in this cache!

Expected behavior:

First, I would like to clarify whether this behavior is expected?
If yes, what would you recommend me to avoid this behavior.
If not, what option is better to use:

  1. Check object references in ROOT_QUERY for null / undefined? (but as I understand - you need to go to Apollo Client with this question)
  2. Delete query objects (or reference objects themselves) when deleting entities whose TTL has expired?
@danReynolds
Copy link
Collaborator

Hey thanks for reaching out. Yea so this is expected behavior. By setting a 1min TTL on the User type, it will evict user type entities after one minute. Just because the users are no longer valid though doesn't mean that the getUser response is going to be refetched, since it is still a valid query based on your configuration above until 15 minutes expire.

The solution that I think gives you the behavior you're looking for is to have your getUsers query return a GetUsersResponse type and you can give that type a TTL of one minute. That way the query itself will be re-fetched when you try to read it after one minute. Does that make sense?

@SavelevMatthew
Copy link
Contributor

SavelevMatthew commented Feb 3, 2025

The solution that I think gives you the behavior you're looking for is to have your getUsers query return a GetUsersResponse type and you can give that type a TTL of one minute. That way the query itself will be re-fetched when you try to read it after one minute. Does that make sense?

Thanks for your feedback, but i guess its not the case.

The problem is that there's no specific GetUsersResponse. There're multiple queries returning User type

For example:

query allUsers -> [User]!
query UserById -> User
query someOtherQuery -> [User]!

My cache state is something like this:

{
  "ROOT_QUERY": {
    "allUsers": [
      { "__ref": "User:123" },
      { "__ref": "User:456" },
      { "__ref": "User:789" }
    ]
  },
  "User:123": {
    "id": "123",
    "name": "Name1"
  },
  "User:456": {
    "id": "456",
    "name": "Name2"
  },
  "User:789": {
    "id": "789",
    "name": "Name3"
  }
}

Here is what happening:

  1. During app lifecycle, your lib is clearing User cache entries, so the cache state become something like this:
{
  "ROOT_QUERY": {
    "allUsers": [
      { "__ref": "User:123" },
      { "__ref": "User:456" },
      { "__ref": "User:789" }
    ]
  }
}
  1. Then when useQuery hook triggered for allUsers, it will return Array of refs from cache, but it will not check if all refs are still presented in cache. Instead it will be forced to resolve them so allUsers will not be refetched (as expected), but instead it will return [null, null, null], because User instances was removed during invalidation mechanism

@SavelevMatthew
Copy link
Contributor

SavelevMatthew commented Feb 3, 2025

One of the possible solutions is to control all ROOT_QUERY invalidation policy like so:

invalidationPolicies: {
    allUsers: 60_000,
    UserById: 60_000,
    someOtherQuery: 60_000,
}

What we want is to simply specify invalidation policy for User, so all queries returning it (directly or as nested subfield), will be affected and refetched properly

@danReynolds
Copy link
Collaborator

Yea unfortunately the way the Apollo GraphQL queries work, a query remains valid once it's child entities, in this case users, are evicted, since an empty array still satisfies the query. For non-list queries, setting a TTL on the User type works just fine, since if it needs to return a User and the user is now evicted, then the query is not satisfied and it has to go to the network.

The easiest way to force all list-response queries to re-fetch after the TTL would be to have a list type like UserList that you return instead of returning [User] in the query, since then you could set a TTL on that type and all queries that return that type would evict it and refetch.

That can be tedious to migrate to though so in the absence of that you'll need to set the TTL like you showed above for each query individually on the ROOT_QUERY.

@tolmachev21
Copy link
Author

Yes, thank you for the suggested option. Unfortunately, it does not suit us. We would like to improve this functionality. We are ready to finish it ourselves. Will this be ok for you? And since you are in the context of your library, maybe you have some recommendations on how to overcome this problem and what should be paid attention to?

@danReynolds
Copy link
Collaborator

Yes, thank you for the suggested option. Unfortunately, it does not suit us. We would like to improve this functionality. We are ready to finish it ourselves. Will this be ok for you? And since you are in the context of your library, maybe you have some recommendations on how to overcome this problem and what should be paid attention to?

Yea of course, I hope you find a solution you are happy with. Maybe one hack would be to evict an array field if any of its entities have an expired TTL? That would work. Best of luck.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants