Skip to content

Conversation

@rohandhruva
Copy link
Contributor

When using @typePolicy and @fieldPolicy, I noticed a few cache misses because the keyFields and keyArgs were not in the same order. When generating a CacheKey in the CacheResolver, the generated key was bar+foo, whereas the type policy generated it as foo+bar.

This PR uses deterministic sorting in both the keyFields of a type policy and keyArgs of a field policy.

@rohandhruva rohandhruva force-pushed the rdhruva/deterministic_sort branch from 1c57060 to f837f3b Compare October 22, 2025 19:04
@rohandhruva rohandhruva force-pushed the rdhruva/deterministic_sort branch from 3cc1e5e to cd0be1f Compare October 22, 2025 19:56

internal data class TypePolicy(
val keyFields: Set<String>,
val keyFields: SortedSet<String>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can just use List. Technically SortedSet makes more sense, but I'd rather avoid the JVM-only type.

Note: the equivalent TypePolicy in com.apollographql.cache.normalized.api also needs to be a List instead of a Set (that's a breaking change but that's all right at the moment).

addStatement("keyFields = setOf(")
withIndent {
typePolicy.keyFields.forEach { keyField ->
typePolicy.keyFields.sorted().forEach { keyField ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we use a List I think it'd make more sense to keep the user-defined order set in @typePolicy.

if (keyArgs.values.isEmpty()) {
return DefaultCacheResolver.resolveField(context)
}
val keyArgsValues = keyArgs.entries.sortedBy { it.key }.map { it.value }
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this works only if the key arguments of a field have the same names as the key fields of the type. So for example:

extend type User @typePolicy(keyFields: "firstName lastName")
extend type Query @fieldPolicy(forField: "user", keyArgs: "firstName lastName")
{
  user(firstName: "john", lastName: "smith") {
    firstName
    lastName
  } 
}
  • The CacheKeyGenerator will return User:john+smith
  • The CacheResolver will return User:john+smith

But

extend type User @typePolicy(keyFields: "firstName lastName")
extend type Query @fieldPolicy(forField: "user", keyArgs: "a b")
{
  user(b: "john", a: "smith") {
    firstName
    lastName
  } 
}
  • The CacheKeyGenerator will return User:john+smith
  • The CacheResolver will return User:smith+john

In the end I think it would make sense to use the same orders as defined in @typePolicy and @fieldPolicy. But that isn't currently possible because that information is lost in compilation! Instead, for the key args, we use the order in which they are passed to the field. So in the example above we must select user(firstName: "john", lastName: "smith") and not user(lastName: "smith", firstName: "john").

But this soon will be possible, because for the key args, I intend to not use CompiledField but a Map generated in the Cache class. With that we'll have the order information.

@BoD
Copy link
Collaborator

BoD commented Oct 27, 2025

Thanks a lot for opening this. In summary:

  • using Set for @typePolicy is indeed wrong. Let's use List
  • I don't think we can really sort, because that assumes fields and args have the same names. Instead the order in which the args are passed is used... which is definitely not great but we'll be able to fix that a bit later.

@rohandhruva
Copy link
Contributor Author

Thanks a lot for opening this. In summary:

  • using Set for @typePolicy is indeed wrong. Let's use List
  • I don't think we can really sort, because that assumes fields and args have the same names. Instead the order in which the args are passed is used... which is definitely not great but we'll be able to fix that a bit later.

Thanks for the review and explanation.

If I am understanding it right, did you want me to close out this PR and then open a new one that changes it from a Set to a List?

@BoD
Copy link
Collaborator

BoD commented Oct 27, 2025

If I am understanding it right, did you want me to close out this PR and then open a new one that changes it from a Set to a List?

I think this can be done in this PR - or a new one if you prefer :)

@BoD
Copy link
Collaborator

BoD commented Oct 29, 2025

Thanks again for having opened this. In the end, I needed to change the Sets to Lists in #250 so this one can be closed.

@BoD BoD closed this Oct 29, 2025
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