AWS AppSyncのリゾルバーキャッシュがキャッシュキーで削除できるようになりました

2022.01.20

いわさです。

AppSyncにはキャッシュ機能が備わっています。
従来、キャッシュを更新するためには指定したTTLが経過するのを待つか、キャッシュのフラッシュ操作を行うことでキャッシュの一括削除を行う形でした。

先日のアップデートでキャッシュキーに基づく部分的なキャッシュのクリアが出来るようになりました。

この記事ではAppSyncサンプルプロジェクトの「イベントアプリ」を使って確認を行っています。「イベントアプリ」はDynamoDBをバックエンドデータソースに利用したサンプルプロジェクトです。

従来のキャッシュクリア

まず、キャッシュメニューからキャッシュ機能を有効化します。

AppSyncでのキャッシュ設定はフルキャッシュとリゾルバーキャッシュの2種類のモードがあり、今回の機能はリゾルバーキャッシュにおいてキャッシュキーを設定している必要があります。

リゾルバーとは、定義したGraphQLスキーマとAWSデータリソースを接続する構成部分で、このサンプルではGraphQLのインターフェースとDynamoDBへのアクセス部分のインターフェース差分を吸収しています。
リゾルバー毎のキャッシュ機能を有効化した場合は、Queryなど個別のリゾルバー毎にキャッシュを有効化する必要があります。

サンプルプロジェクトにはアイテムの更新処理が実装されていないのでスキーマーでupdateEventNameを追加しています。
更新処理のリゾルバーについては後述します。

ここではその更新処理を使ってキャッシュの挙動を確認してみます。

mutation MyMutation {
  updateEventName(id: "857ff625-f276-459f-9261-14d949b5be57", name: "item3") {
    id
  }
}

DynamoDBのレコードが更新されていますね。(item1 → item3)
getEventで対象アイテムを取得するとどうなるでしょうか。

query MyQuery {
  getEvent(id: "857ff625-f276-459f-9261-14d949b5be57") {
    id
    name
  }
}
{
  "data": {
    "getEvent": {
      "id": "857ff625-f276-459f-9261-14d949b5be57",
      "name": "item1"
    }
  }
}

DynamoDBは更新されていますが、キャッシュが生存しているため古い情報が参照されていますね。

キャッシュを反映させたいとなると、従来はここでキャッシュフラッシュ操作で一括クリアを行う必要がありました。試してみましょう。
ちなみに「キャッシュを削除」すると、キャッシュインスタンス自体が削除されてキャッシュ有効化の再設定が必要になるのでご注意ください。

query MyQuery {
  getEvent(id: "857ff625-f276-459f-9261-14d949b5be57") {
    id
    name
  }
}
{
  "data": {
    "getEvent": {
      "id": "857ff625-f276-459f-9261-14d949b5be57",
      "name": "item3"
    }
  }
}

最新情報が取得されました。

キャッシュキーでのクリアをやってみる

さて、今回の新機能を使うとリゾルバー毎のキャッシュを動的にかつ必要な範囲だけクリア出来ます。
試してみましょう。

便宜上、更新処理を少し変更しています。
スキーマ定義とリゾルバーのリクエストマッピングテンプレートは以下のようにしました。

type Mutation {
    # Create a single event.
    createEvent(
        name: String!,
        when: String!,
        where: String!,
        description: String!
    ): Event
    # Delete a single event by id.
    deleteEvent(id: ID!): Event
    # Comment on an event.
    commentOnEvent(eventId: ID!, content: String!, createdAt: String!): Comment
    updateEvent(
        id: ID!,
        name: String!,
        when: String!,
        where: String!,
        description: String!
    ): Event
}
{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key" : {
        "id" : $util.dynamodb.toDynamoDBJson($context.arguments.id)
    },
    "update" : {
        "expression" : "SET #name = :name, #where = :where, #when = :when, description = :description",
        "expressionNames": {
            "#name" : "name",
            "#where" : "where",
            "#when" : "when"
        },
        "expressionValues": {
            ":name" : $util.dynamodb.toDynamoDBJson($context.arguments.name),
            ":where" : $util.dynamodb.toDynamoDBJson($context.arguments.where),
            ":when" : $util.dynamodb.toDynamoDBJson($context.arguments.when),
            ":description" : $util.dynamodb.toDynamoDBJson($context.arguments.description)
        }
    }
}

参照処理のリゾルバーにキャッシュキーを設定

まずは、キャッシュを行う参照処理(ここではgetEvent)ではリゾルバーキャッシュの有効化に加えてキャッシュキーを設定する必要があります。

どうやら本日時点でマネジメントコンソール上からはキャッシュキーの設定は出来ないようなので、AWS CLIのupdate-resolverサブコマンドを使って設定を行いました。

iwasa.takahito@hoge ~ % aws appsync update-resolver --cli-input-json file://resolver-update.json
{
    "resolver": {
        "typeName": "Query",
        "fieldName": "getEvent",
        "dataSourceName": "AppSyncEventTable",
        "resolverArn": "arn:aws:appsync:ap-northeast-1:123456789012:apis/6jchp4yujvfrnlczcy34v3ia6u/types/Query/resolvers/getEvent",
        "requestMappingTemplate": "{\n    \"version\": \"2017-02-28\",\n    \"operation\": \"GetItem\",\n    \"key\": {\n        \"id\": { \"S\": \"$context.arguments.id\" }\n    }\n}",
        "responseMappingTemplate": "$util.toJson($context.result)",
        "kind": "UNIT",
        "cachingConfig": {
            "ttl": 600,
            "cachingKeys": [
                "$context.arguments.id"
            ]
        },
        "maxBatchSize": 0
    }
}

更新処理のリゾルバーにキャッシュ削除処理を追加

次に、アイテムの更新を行った際に明示的にどのリゾルバーキャッシュのどのキャッシュキーをクリアするのかを指定します。
指定はレスポンスマッピングテンプレートまたはリクエストマッピングテンプレートで、evictFromApiCacheを使うことで実現出来ます。

以下はリクエストマッピングテンプレートです。

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

$util.toJson($ctx.result)

動作確認

更新前

更新前のアイテムの値はitem7でした。

query MyQuery {
  getEvent(id: "857ff625-f276-459f-9261-14d949b5be57") {
    id
    name
  }
}
{
  "data": {
    "getEvent": {
      "id": "857ff625-f276-459f-9261-14d949b5be57",
      "name": "item7"
    }
  }
}

更新後

item8へ値を更新してみましょう。
部分キャッシュクリアが適用されると、更新処理のレスポンスにその旨の情報が含まれていますね。

mutation MyMutation {
  updateEvent(id: "857ff625-f276-459f-9261-14d949b5be57", name: "item8", description: "item8", when: "item8", where: "item8") {
    id
  }
}
{
  "extensions": {
    "apiCacheEntriesDeleted": 1
  },
  "data": {
    "updateEvent": {
      "id": "857ff625-f276-459f-9261-14d949b5be57"
    }
  }
}

query MyQuery {
  getEvent(id: "857ff625-f276-459f-9261-14d949b5be57") {
    id
    name
  }
}
{
  "data": {
    "getEvent": {
      "id": "857ff625-f276-459f-9261-14d949b5be57",
      "name": "item8"
    }
  }
}

更新処理のあとすぐに最新情報を取得することが出来ました。

さいごに

本日はAppSyncのリゾルバーキャッシュで、キャッシュキーに基づくキャッシュクリアを行ってみました。

対象キャッシュキーに紐づくキャッシュのみがクリアされるので、動的APIで悩ましいキャッシュ機能で効率的なキャッシュ戦略をたてることが出来るようになったのではないかと思います。
キャッシュを有効化が難しかったシーンや、妥協して短いTTLを設定せざるを得なかったケースなどでは、よりキャッシュを有効活用出来るようになるのではないでしょうか。