Configuring server-side caching and API payload compression in AWS AppSync - AWS AppSync GraphQL

Configuring server-side caching and API payload compression in AWS AppSync

AWS AppSync's server-side data caching capabilities make data available in a high speed, in-memory cache, improving performance and decreasing latency. This reduces the need to directly access data sources. Caching is available for both unit and pipeline resolvers.

AWS AppSync also allows you to compress API responses so that payload content loads and downloads faster. This potentially reduces the strain on your applications while also potentially reducing your data transfer charges. Compression behavior is configurable and can be set at your own discretion.

Refer to this section for help defining the desired behavior of server-side caching and compression in your AWS AppSync API.

Instance types

AWS AppSync hosts Amazon ElastiCache (Redis OSS) instances in the same AWS account and AWS Region as your AWS AppSync API.

The following ElastiCache (Redis OSS) instance types are available:

small

1 vCPU, 1.5 GiB RAM, low to moderate network performance

medium

2 vCPU, 3 GiB RAM, low to moderate network performance

large

2 vCPU, 12.3 GiB RAM, up to 10 Gigabit network performance

xlarge

4 vCPU, 25.05 GiB RAM, up to 10 Gigabit network performance

2xlarge

8 vCPU, 50.47 GiB RAM, up to 10 Gigabit network performance

4xlarge

16 vCPU, 101.38 GiB RAM, up to 10 Gigabit network performance

8xlarge

32 vCPU, 203.26 GiB RAM, 10 Gigabit network performance (not available in all Regions)

12xlarge

48 vCPU, 317.77 GiB RAM, 10 Gigabit network performance

Note

Historically, you specified a specific instance type (such as t2.medium). As of July 2020, these legacy instance types continue to be available, but their use is deprecated and discouraged. We recommend that you use the generic instance types described here.

Caching behavior

The following are the behaviors related to caching:

None

No server-side caching.

Full request caching

If the data is not in the cache, it is retrieved from the data source and populates the cache until the time to live (TTL) expiration. All subsequent requests to your API are returned from the cache. This means that data sources aren't contacted directly unless the TTL expires. In this setting, we use the contents of the context.arguments and context.identity maps as caching keys.

Per-resolver caching

With this setting, each resolver must be explicitly opted in for it to cache responses. You can specify a TTL and caching keys on the resolver. Caching keys that you can specify are the top-level maps context.arguments, context.source, and context.identity, and/or string fields from these maps. The TTL value is mandatory, but the caching keys are optional. If you don't specify any caching keys, the defaults are the contents of the context.arguments, context.source, and context.identity maps.

For example, you could use the following combinations:

  • context.arguments and context.source

  • context.arguments and context.identity.sub

  • context.arguments.id or context.arguments.InputType.id

  • context.source.id and context.identity.sub

  • context.identity.claims.username

When you specify only a TTL and no caching keys, the behavior of the resolver is the same as full request caching.

Cache time to live

This setting defines the amount of time to store cached entries in memory. The maximum TTL is 3,600 seconds (1 hour), after which entries are automatically deleted.

Cache encryption

Cache encryption comes in the following two flavors. These are similar to the settings that ElastiCache (Redis OSS) allows. You can enable the encryption settings only when first enabling caching for your AWS AppSync API.

  • Encryption in transit – Requests between AWS AppSync, the cache, and data sources (except insecure HTTP data sources) are encrypted at the network level. Because there is some processing needed to encrypt and decrypt the data at the endpoints, in-transit encryption can impact performance.

  • Encryption at rest – Data saved to disk from memory during swap operations are encrypted at the cache instance. This setting also impacts performance.

To invalidate cache entries, you can make a flush cache API call using either the AWS AppSync console or the AWS Command Line Interface (AWS CLI).

For more information, see the ApiCache data type in the AWS AppSync API Reference.

Cache eviction

When you set up AWS AppSync's server-side caching, you can configure a maximum TTL. This value defines the amount of time that cached entries are stored in memory. In situations where you must remove specific entries from your cache, you can use AWS AppSync's evictFromApiCache extensions utility in your resolver's request or response. (For example, when your data in your data sources have changed, and your cache entry is now stale.) To evict an item from the cache, you must know its key. For this reason, if you must evict items dynamically, we recommend using per-resolver caching and explicitly defining a key to use to add entries to your cache.

Evicting a cache entry

To evict an item from the cache, use the evictFromApiCache extensions utility. Specify the type name and field name, then provide an object of key-value items to build the key of the entry that you want to evict. In the object, each key represents a valid entry from the context object that is used in the cached resolver's cachingKey list. Each value is the actual value used to construct the value of the key. You must put the items in the object in the same order as the caching keys in the cached resolver's cachingKey list.

For example, see the following schema:

type Note { id: ID! title: String content: String! } type Query { getNote(id: ID!): Note } type Mutation { updateNote(id: ID!, content: String!): Note }

In this example, you can enable per-resolver caching, then enable it for the getNote query. Then, you can configure the caching key to consist of [context.arguments.id].

When you try to get a Note, to build the cache key, AWS AppSync performs a lookup in its server-side cache using the id argument of the getNote query.

When you update a Note, you must evict the entry for the specific note to make sure that the next request fetches it from the backend data source. To do this, you must create a request handler.

The following example shows one way to handle the eviction using this method:

import { util, Context } from '@aws-appsync/utils'; import { update } from '@aws-appsync/utils/dynamodb'; export function request(ctx) { extensions.evictFromApiCache('Query', 'getNote', { 'ctx.args.id': ctx.args.id }); return update({ key: { id: ctx.args.id }, update: { context: ctx.args.content } }); } export const response = (ctx) => ctx.result;

Alternatively, you can also handle the eviction in the response handler.

When the updateNote mutation is processed, AWS AppSync tries to evict the entry. If an entry is successfully cleared, the response contains an apiCacheEntriesDeleted value in the extensions object that shows how many entries were deleted:

"extensions": { "apiCacheEntriesDeleted": 1}

Evicting a cache entry based on identity

You can create caching keys based on multiple values from the context object.

For example, take the following schema that uses Amazon Cognito user pools as the default auth mode and is backed by an Amazon DynamoDB data source:

type Note { id: ID! # a slug; e.g.: "my-first-note-on-graphql" title: String content: String! } type Query { getNote(id: ID!): Note } type Mutation { updateNote(id: ID!, content: String!): Note }

The Note object types are saved in a DynamoDB table. The table has a composite key that uses the Amazon Cognito user name as the primary key and the id (a slug) of the Note as the partition key. This is a multi-tenant system that allows multiple users to host and update their private Note objects, which are never shared.

Since this is a read-heavy system, the getNote query is cached using per-resolver caching, with the caching key composed of [context.identity.username, context.arguments.id]. When a Note is updated, you can evict the entry for that specific Note. You must add the components in the object in the same order that they are specified in your resolver's cachingKeys list.

The following example shows this:

import { util, Context } from '@aws-appsync/utils'; import { update } from '@aws-appsync/utils/dynamodb'; export function request(ctx) { extensions.evictFromApiCache('Query', 'getNote', { 'ctx.identity.username': ctx.identity.username, 'ctx.args.id': ctx.args.id, }); return update({ key: { id: ctx.args.id }, update: { context: ctx.args.content } }); } export const response = (ctx) => ctx.result;

A backend system can also update the Note and evict the entry. For example, take this mutation:

type Mutation { updateNoteFromBackend(id: ID!, content: String!, username: ID!): Note @aws_iam }

You can evict the entry, but add the components of the caching key to the cachingKeys object.

In the following example, the eviction occurs in the response of the resolver:

import { util, Context } from '@aws-appsync/utils'; import { update } from '@aws-appsync/utils/dynamodb'; export function request(ctx) { return update({ key: { id: ctx.args.id }, update: { context: ctx.args.content } }); } export function response(ctx) { extensions.evictFromApiCache('Query', 'getNote', { 'ctx.identity.username': ctx.args.username, 'ctx.args.id': ctx.args.id, }); return ctx.result; }

In cases where your backend data has been updated outside of AWS AppSync, you can evict an item from the cache by calling a mutation that uses a NONE data source.

Compressing API responses

AWS AppSync allows clients to request compressed payloads. If requested, API responses are compressed and returned in response to requests that indicate that compressed content is preferred. Compressed API responses load faster, content is downloaded faster, and your data transfer charges may be reduced as well.

Note

Compression is available on all new APIs created after June 1st, 2020.

AWS AppSync compresses objects on a best-effort basis. In rare cases, AWS AppSync may skip compression based on a variety of factors, including current capacity.

AWS AppSync can compress GraphQL query payload sizes between 1,000 to 10,000,000 bytes. To enable compression, a client must send the Accept-Encoding header with the value gzip. Compression can be verified by checking the Content-Encoding header's value in the response (gzip).

The query explorer in the AWS AppSync console automatically sets the header value in the request by default. If you execute a query that has a large enough response, compression can be confirmed using your browser developer tools.