

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# での競合の検出と解決 AWS AppSync
<a name="conflict-detection-and-resolution"></a>

 AWS AppSync で同時書き込みが発生した場合は、更新を適切に処理するように競合検出と競合解決戦略を設定できます。競合の検出では、ミューテーションがデータソースに実際に書き込まれた項目と競合しているかどうかが判断されます。競合の検出を有効にするには、`conflictDetection` フィールドの SyncConfig の値を `VERSION` に設定します。

競合の解決は、競合が検出された場合に実行されるアクションです。これは、SyncConfig の Conflict Handler フィールドを設定することによって決定されます。次の 3 つの競合の解決戦略があります。
+ OPTIMISTIC\$1CONCURRENCY
+ AUTOMERGE
+ LAMBDA

バージョンは書き込みオペレーション中に AWS AppSync によって自動的に増分されるため、クライアントやバージョン対応データソースで設定されたリゾルバーの外部では変更しないでください。変更すると、システムの整合性動作が変更され、データが失われる可能性があります。

## オプティミスティック同時実行
<a name="optimistic-concurrency"></a>

オプティミスティック同時実行は、 AWS AppSync がバージョニングされたデータソースに提供する競合解決戦略です。競合リゾルバーがオプティミスティック同時実行に設定されている場合、着信ミューテーションのバージョンがオブジェクトの実際のバージョンとは異なることが検出されると、競合ハンドラーは着信リクエストを拒否します。GraphQL レスポンス内部では、最新バージョンを持つサーバー上の既存の項目が提供されます。クライアントは、この競合をローカルで処理し、項目の更新バージョンでミューテーションを再試行することが期待されます。

## Automerge
<a name="automerge"></a>

Automerge により、開発者は他の方法で解決できなかった競合を手動でマージするクライアント側のロジックを記述しなくても、競合の解決戦略を簡単に設定することができます。Automerge は、データをマージして競合を解決するときに、厳密なルールセットに従います。Automerge の理念は、GraphQL フィールドの基になるデータ型を中心に展開されています。その内容は次のとおりです。
+ スカラーフィールドでの競合: GraphQL スカラーまたはコレクションではないフィールド (リスト、セット、マップなど)。スカラーフィールドの入力値を拒否し、サーバーに存在する値を選択します。
+ リストの競合: GraphQL 型とデータベース型はリストです。着信リストをサーバー上の既存のリストと連結します。着信ミューテーションのリスト値は、サーバー内のリストの最後に追加されます。重複した値は保持されます。
+ セットの競合: GraphQL 型はリストであり、データベース型はセットです。着信セットとサーバー内の既存のセットを使用して、セットユニオンを適用します。これは、重複するエントリを持たないという、セットのプロパティに準拠しています。
+ 着信ミューテーションが項目に新しいフィールドを追加するか、値が `null` のときにフィールドに対して着信ミューテーションが実行されたときは、それを既存の項目にマージします。
+ マップ上の競合: データベースの基になるデータ型がマップ (キーと値のドキュメント) の場合、マップの各プロパティを解析して処理する上記のルールを適用します。

Automerge は、更新されたバージョンのリクエストを自動的に検出、マージ、再試行するように設計されており、クライアントは競合するデータを手動でマージする必要がありません。

Automerge がスカラー型で競合を処理する方法の例を示します。出発点として、次のレコードを使用します。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 5,
  "_version" : 4
}
```

クライアントがまだサーバーと同期していないため、着信ミューテーションが項目を更新しようとしている可能性がありますが、それは古いバージョンです。以下のようになります。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 55,
  "_version" : 2
}
```

着信リクエストの古いバージョン 2 に注意してください。このフローの間、Automerge は jersey フィールドの 55 への更新を拒否し、値を 5 のままにすることでデータがマージされます。その結果、項目の次のイメージがサーバーに保存されます。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 5,
  "_version" : 5 # version is incremented every time automerge performs a merge that is stored on the server.
}
```

上記の項目の状態がバージョン 5 で、次のイメージで項目を変更しようとする着信ミューテーションがあるとします。

```
{
  "id" : 1,
  "name" : "Shaggy",
  "jersey" : 5,
  "interests" : ["breakfast", "lunch", "dinner"] # underlying data type is a Set
  "points": [24, 30, 27] # underlying data type is a List
  "_version" : 3
}
```

着信ミューテーションには、注目すべき 3 つのポイントがあります。スカラーという名前が変更されましたが、2 つの新しいフィールドである、セットの interests およびリストの points が追加されました。このシナリオでは、バージョンの不一致が原因で競合が検出されます。Automerge はそのプロパティに準拠し、名前の変更を拒否します。これは名前がスカラーで、競合しないフィールドのアドオンであるためです。これにより、サーバーに保存された項目は次のように表示されます。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 5,
  "interests" : ["breakfast", "lunch", "dinner"] # underlying data type is a Set
  "points": [24, 30, 27] # underlying data type is a List
  "_version" : 6
}
```

項目の更新されたイメージはバージョン 6 となりました。ここで、着信ミューテーション (別のバージョンと不一致) が項目を次のように変換しようとするとします。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 5,
  "interests" : ["breakfast", "lunch", "brunch"] # underlying data type is a Set
  "points": [30, 35] # underlying data type is a List
  "_version" : 5
}
```

ここで interests の着信フィールドは、サーバーに存在する 1 つの重複値と 2 つの新しい値を持っていることがわかります。この場合、基になるデータ型はセットであるため、Automerge はサーバーに存在する値と着信リクエストの値を結合し、重複値を除外します。同様に、重複する値が 1 つと新しい値が 1 つある points フィールドには競合があります。しかし、ここで基になるデータ型はリストであるため、Automerge は着信リクエストのすべての値を、サーバーにすでに存在する値の最後に追加します。サーバーに保存されたマージ後のイメージは、次のようになります。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 5,
  "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set
  "points": [24, 30, 27, 30, 35] # underlying data type is a List
  "_version" : 7
}
```

次に、バージョン 8 で、サーバーに保存された項目が次のようになると仮定します。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 5,
  "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set
  "points": [24, 30, 27, 30, 35] # underlying data type is a List
  "stats": {
      "ppg": "35.4",
      "apg": "6.3"
  }
  "_version" : 8
}
```

しかし、着信リクエストは、次のイメージで項目を更新しようとしますが、再びバージョンの不一致が発生します。

```
{
  "id" : 1,
  "name" : "Nadia",
  "stats": {
      "ppg": "25.7",
      "rpg": "6.9"
  }
  "_version" : 3
}
```

このシナリオでは、サーバーにすでに存在するフィールド (interests、points、jersey) が欠落していることがわかります。また、マップ stats 内の ppg の値は編集中で、新しい値 rpg が追加され、apg は省略されています。Automerge は、省略されたフィールドを保持します (注意: フィールドを削除する予定の場合は、一致するバージョンでリクエストを再試行する必要があります)。したがって、それらは失われません。マップ内のフィールドにも同じルールが適用されるため、ppg への変更は拒否されますが、apg は保持され、新しいフィールド rpg が追加されます。その結果、サーバーに保存された項目は次のようになります。

```
{
  "id" : 1,
  "name" : "Nadia",
  "jersey" : 5,
  "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set
  "points": [24, 30, 27, 30, 35] # underlying data type is a List
  "stats": {
      "ppg": "35.4",
      "apg": "6.3",
      "rpg": "6.9"
  }
  "_version" : 9
}
```

## lambda
<a name="lambda"></a>

いくつかの Lambda 解決戦略から選択できます。
+  `RESOLVE`: 既存の項目を、レスポンスペイロードで提供された新しい項目に置き換えます。一度に 1 つの項目に対してのみ、同じオペレーションを再試行できます。現在、DynamoDB `PutItem` および `UpdateItem` でサポートされています。
+  `REJECT`: ミューテーションを拒否し、GraphQL レスポンスで既存の項目のエラーを返します。現在、DynamoDB `PutItem`、`UpdateItem`、および `DeleteItem` でサポートされています。
+  `REMOVE`: 既存の項目を削除します。現在、DynamoDB `DeleteItem` でサポートされています。

 **Lambda 呼び出しリクエスト** 

 AWS AppSync DynamoDB リゾルバーは、 で指定された Lambda 関数を呼び出します`LambdaConflictHandlerArn`。また、データソースに設定されたものと同じ `service-role-arn` を使用します。呼び出しのペイロードは以下の構造を持ちます。

```
{
    "newItem": { ... },
    "existingItem": {... },
    "arguments": { ... },
    "resolver": { ... },
    "identity": { ... }
}
```

各フィールドの定義は以下のようになります。

** `newItem` **  
ミューテーションが成功した場合のプレビュー項目。

** `existingItem` **  
現在、この項目は DynamoDB テーブルに存在しています。

** `arguments` **  
GraphQL ミューテーションの引数です。

** `resolver` **  
リ AWS AppSync ゾルバーに関する情報。

** `identity` **  
呼び出し元に関する情報。API キーでアクセスする場合、このフィールドは null に設定されます。

ペイロードの例：

```
{
    "newItem": {
        "id": "1",
        "author": "Jeff",
        "title": "Foo Bar",
        "rating": 5,
        "comments": ["hello world"],
    },
    "existingItem": {
        "id": "1",
        "author": "Foo",
        "rating": 5,
        "comments": ["old comment"]
    },
    "arguments": {
        "id": "1",
        "author": "Jeff",
        "title": "Foo Bar",
        "comments": ["hello world"]
    },
    "resolver": {
        "tableName": "post-table",
        "awsRegion": "us-west-2",
        "parentType": "Mutation",
        "field": "updatePost"
    },
    "identity": {
         "accountId": "123456789012",
         "sourceIp": "x.x.x.x",
         "username": "AIDAAAAAAAAAAAAAAAAAA",
         "userArn": "arn:aws:iam::123456789012:user/appsync"
    }
}
```

 **Lambda 呼び出しレスポンス** 

`PutItem` および `UpdateItem` の競合の解決

 ミューテーション を `RESOLVE` する レスポンスは次の形式である必要があります。

```
{
    "action": "RESOLVE",
    "item": { ... }
}
```

`item` フィールドは、基になるデータソースの既存の項目を置き換えるために使用されるオブジェクトを表します。プライマリキーと同期メタデータは、`item` に含まれている場合は無視されます。

 ミューテーション を `REJECT` する レスポンスは次の形式である必要があります。

```
{
    "action": "REJECT"
}
```

`DeleteItem` の競合の解決

 項目を `REMOVE` します。レスポンスは次の形式である必要があります。

```
{
    "action": "REMOVE"
}
```

 ミューテーション を `REJECT` する レスポンスは次の形式である必要があります。

```
{
    "action": "REJECT"
}
```

下記の Lambda 関数の例は、呼び出しを行うユーザーとリゾルバー名を確認します。`jeffTheAdmin` によって行われる場合、DeletePost リゾルバーのオブジェクトを削除 (`REMOVE`) するか、Update/Put リゾルバーの新しい項目との競合を解決 (`RESOLVE`) します。それ以外の場合、ミューテーションは `REJECT` です。

```
exports.handler = async (event, context, callback) => {
    console.log("Event: "+ JSON.stringify(event));

    // Business logic goes here.
    var response;
    if ( event.identity.user == "jeffTheAdmin" ) {
        let resolver = event.resolver.field;

        switch(resolver) {
            case "deletePost":
                response = {
                    "action" : "REMOVE"
                }
                break;

            case "updatePost":
            case "createPost":
                response = {
                    "action" : "RESOLVE",
                    "item": event.newItem
                }
                break;
            default:
                response = { "action" : "REJECT" };
        }
    } else {
        response = { "action" : "REJECT" };
    }

    console.log("Response: "+ JSON.stringify(response));
    return response;
}
```

## エラー
<a name="errors"></a>

以下は、競合解決プロセス中に発生する可能性のあるエラーのリストです。

** `ConflictUnhandled` **  
競合の検出によりバージョンの不一致が検出され、競合ハンドラーによりミューテーションが拒否された。  
例: オプティミスティック同時実行の競合ハンドラーを使用した競合の解決。または、Lambda 競合ハンドラーが `REJECT` として返される。

** `ConflictError` **  
競合を解決しようとすると内部エラーが発生する。  
例: Lambda 競合ハンドラーが誤った形式のレスポンスを返す。または、指定されたリソース `LambdaConflictHandlerArn` が見つからないため、Lambda 競合ハンドラーを呼び出せない。

** `MaxConflicts` **  
競合解決のための最大再試行回数に達した。  
例: 同じオブジェクトに対する同時リクエストが多すぎる。競合が解決される前に、オブジェクトは別のクライアントによって新しいバージョンに更新される。

** `BadRequest` **  
クライアントがメタデータフィールド (`_version`、`_ttl`、`_lastChangedAt`、`_deleted`) を更新しようとした。  
例: クライアントが、更新ミューテーションでオブジェクトの `_version` を更新しようとする。

** `DeltaSyncWriteError` **  
差分同期レコードの書き込みに失敗した。  
例: ミューテーションは成功したが、差分同期テーブルへの書き込み中に内部エラーが発生した。

** `InternalFailure` **  
内部エラーが発生しました。

**`UnsupportedOperation`**  
サポートされていないオペレーション '*X *'。データソースバージョニングは、次のオペレーション (TransactGetItems 、PutItem 、BatchGetItem 、Scan、Query、GetItem 、DeleteItem 、UpdateItem 、Sync) のみをサポートします。  
例: 競合の検出/解決を有効にした特定のトランザクションとバッチオペレーションの使用。以下のオペレーションは現在サポートされていません。

## CloudWatch Logs
<a name="cloudwatch-logs"></a>

 AWS AppSync API がログ記録設定をフィールドレベルログに設定`enabled`し、フィールドレベルログのログレベルを に設定して CloudWatch Logs を有効にしている場合`ALL`、 AWS AppSync は競合検出および解決情報をロググループに出力します。ログメッセージの形式については、[競合の検出と同期のログ記録に関するドキュメント](monitoring.md#aws-appsync-monitoring-conflict-detection-and-sync-logging)を参照してください。