serverless-appsync-plugin icon indicating copy to clipboard operation
serverless-appsync-plugin copied to clipboard

[FEATURE] please support cache eviction

Open AndreasAugustin opened this issue 2 years ago • 6 comments

It is possible to evict caches within AWS appsync. https://aws.amazon.com/blogs/mobile/introducing-server-side-caching-item-eviction-for-aws-appsync/

It would be super nice if this plugin supports those evictions.

AndreasAugustin avatar Jun 01 '22 07:06 AndreasAugustin

Thank you @AndreasAugustin This will be supported in v2

See documentation

bboure avatar Jun 01 '22 12:06 bboure

@bboure thanks a lot for super fast answer! Just a question. Your link leads to the documentation for flush-cache. If I understand the docs right it is similar to the aws cli comman [aws appsync flush-api-cache]( (https://docs.aws.amazon.com/cli/latest/reference/appsync/flush-api-cache.html) which is great out of development perspective.

My feature request refers to request cache eviction which will evict caches for client requests to the appsync api during run time.

To implement this behaviour the underlying request mapping template must be adjusted. Will this feature also be implemented within v2 (at least I did not find a note within your docs)? would be super nice!

Remark

It is already possible to do this with a combination of substitutions and request/response mapping template. But not super convenient. Built in into this plugin would be awesome. Please get back to me if I can support in any way

AndreasAugustin avatar Jun 01 '22 12:06 AndreasAugustin

Got it!

I have not used that feature yet, but if I'm not mistaken, this is fully controlled on the VTL level as you mentioned.

You already have to write your own VTL templates, so I am not sure what else this plugin could do?

Let me know if you have any idea

bboure avatar Jun 01 '22 12:06 bboure

We currently use 2 major VTL templates:

  • response
  • request

For different datasources we use your substitution feature to inject different variable values used in those templates. This reduces our amount of code and it is much more easy to test and debug. (debugging velocity templates is not nice).

For the cache eviction we use smth like:

#set($responseCachingEvictions=${responseCachingEvictions})

#if($responseCachingEvictions.size() > 0)
  #foreach($cachingEviction in $responseCachingEvictions)
    #set($cachingKeys = {})
    #foreach($key in $cachingEviction.keys)
      $util.qr($cachingKeys.put("$key.name", "$key.value"))
    #end
    $extensions.evictFromApiCache("$cachingEviction.graphQlType", "$cachingEviction.functionName", $cachingKeys)
  #end
#end

what need an object susbitution with following schema (JSON.stringify the object):

export interface CachingEvictionVelocity {
  keys: {value: string; name: string}[];
  graphQlType: GraphQLType;
  functionName: string;
}

Thats ugly but working.

It would be much nicer if there would be config parameters within your plugin like

- dataSource: accountsListAccountsDataSource
  type: mutation
  field: updateNode
  caching:
    ttl: 200
    keys: []
  cachingEvictions:
    - queryType: Query
       function: getNote
      cachingKeys:
      - context.argumtens.id
      - context.identity.sub
    - ...
  substitutions:
    ....

which you could use in you velocity template like

...
${cachingEvictions}
...

That would be substituted to smth like

...
#set($cachingKeys = {})
$util.qr($cachingKeys.put("context.arguments.id", $context.arguments.id))
$extensions.evictFromApiCache("Query", "getNote", $cachingKeys)
...

Out of a development perspective this would be much nicer and much more convenient.

Furthermore it would be super if the plugin would in addition provide some sanity checks

  • is the function defined
  • is the caching key used within the function

That would in addition avoid many runtime errors of the app.

Remark Another idea of for the schema: define the cachingEvictions already within the caching part. Smth like:

- dataSource: accountsListAccountsDataSource
  type: mutation
  field: getNode
  caching:
    ttl: 200
    keys: [context.argumtens.id, context.identity.sub]
    evictions:
      - updateNote
      - deleteNote
  substitutions:
    ....

That means that the functions updateNote. deleteNote would evict this cache

AndreasAugustin avatar Jun 01 '22 13:06 AndreasAugustin

Thanks for the insights.

I like the idea. I see several things we could try to do as you suggested.

  • try to infer the caching keys from caching config
- dataSource: accountsListAccountsDataSource
  type: Query
  field: getNote
  caching:
    ttl: 200
    keys: [context.argumtens.id, context.identity.sub]
    evictions:
      - updateNote
      - deleteNote

This one can be tricky though because we cannot necessarily predict what part of $ctx.arguments can be used. e.g. updateNote might receive the id from $ctx.arguments.note.id if the updated note is passed as an input object (this is a pattern I often use)

Trying to "guess" or use defaults might result in unexpected results.

The other way around sounds like a better approach in my opinion. But we might need a map because of the above-mentioned use-case. e.g.

- dataSource: accountsListAccountsDataSource
  type: Mutation
  field: updateNode
  cachingEvictions:
    - queryType: Query
       function: getNote
       cachingKeys:
         context.argumtens.id: $ctx.arguments.note.id
         context.identity.sub: $context.identity.sub

This way, we are being explicit about what needs to be edited and how. It's less of a "black box"

I am not sure though we'd need a "substitution" pattern like

${cachingEvictions}

We could inject it at the top or bottom of the response VTL. (but a way to override with an explicit placeholder could be useful)

I have no experience with cache eviction yet (I know what they are, I have just never used them yet). I am wondering what would happen in case of resolvers errors. e.g. if a mutation fails due to a DynamoDB update operation conditionExpression or a Lambda resolver fails. I would expect not to evict the cache. So maybe here the placeholder might make more sense after all to control that. Conditions will vary depending on use-case

#if($ctx.error && $ctx.error.type == "DynamoDB:ConditionalCheckFailedException")
    $util.error(...)
#else
  ${cachingEvictions}
  $util.toJson(...)
#end

Thoughts?

bboure avatar Jun 02 '22 17:06 bboure

I have no experience with cache eviction yet (I know what they are, I have just never used them yet). I am wondering what would happen in case of resolvers errors. e.g. if a mutation fails due to a DynamoDB update operation conditionExpression or a Lambda resolver fails. I would expect not to evict the cache. So maybe here the placeholder might make more sense after all to control that. Conditions will vary depending on use-case

Yes, I also (like you said) think this highly depends on the use case. In some error cases it makes sense to evict the cache (e.q. persistent data changed) to avoid presenting old data to client due to cache, in other cases the underlying data has not changed (e.q. input data or AUTHZ validation failures). In those cases maybe the cache does not need to evicted. Due to that reason my first idea was to give the developer the decide. Maybe there is a better (more convenient) solution, but currently I think the substitution way is the nicest way. (Ofc the developer must know what she/he is doing :smiling_imp: )

AndreasAugustin avatar Jun 02 '22 20:06 AndreasAugustin