Détection et résolution des conflits dans AWS AppSync - AWS AppSync

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Détection et résolution des conflits dans AWS AppSync

Lorsque des écritures simultanées ont lieu avec AWS AppSync, vous pouvez configurer des stratégies de détection et de résolution des conflits pour gérer les mises à jour de manière appropriée. La détection des conflits détermine si la mutation est en conflit avec l'élément écrit réel dans la source de données. La détection des conflits est activée en définissant la SyncConfig valeur du conflictDetection champ surVERSION.

La résolution des conflits est l'action qui est effectuée en cas de détection d'un conflit. Ceci est déterminé en définissant le champ Gestionnaire de conflits dans le SyncConfig. Il existe trois stratégies de résolution des conflits :

  • OPTIMISTIC_CONCURRENCY

  • AUTOMERGE

  • LAMBDA

Les versions sont automatiquement incrémentées AWS AppSync pendant les opérations d'écriture et ne doivent pas être modifiées par les clients ou en dehors d'un résolveur configuré avec une source de données compatible avec les versions. Cela modifiera le comportement de cohérence du système et pourrait entraîner une perte de données.

Concurrence optimiste

Optimistic Concurrency est une stratégie de résolution des conflits qui AWS AppSync fournit des sources de données versionnées. Lorsque le résolveur de conflits est défini sur Simultanéité optimiste, si une mutation entrante est détectée comme ayant une version différente de la version réelle de l'objet, le gestionnaire de conflits rejette simplement la demande entrante. Dans la réponse GraphQL, l'élément existant sur le serveur qui a la dernière version sera fourni. Le client doit alors gérer ce conflit localement et réessayer la mutation avec la version mise à jour de l'élément.

Autofusions

Automerge fournit aux développeurs un moyen facile de configurer une stratégie de résolution de conflits sans écrire de logique côté client pour fusionner manuellement les conflits qui n'ont pas pu être gérés par d'autres stratégies. Automerge respecte un ensemble de règles strictes lors de la fusion de données pour résoudre les conflits. Les principes d'Automerge tournent autour du type de données sous-jacent du champ GraphQL. Ce sont les suivants :

  • Conflit sur un champ scalaire : scalaire GraphQL ou tout autre champ qui n'est pas une collection (par exemple List, Set, Map). Rejetez la valeur entrante pour le champ scalaire et sélectionnez la valeur existante dans le serveur.

  • Conflit sur une liste : le type GraphQL et le type de base de données sont des listes. Concaténez la liste entrante avec la liste existante dans le serveur. Les valeurs de liste dans la mutation entrante seront ajoutées à la fin de la liste dans le serveur. Les valeurs dupliquées seront conservées.

  • Conflit sur un ensemble : le type GraphQL est une liste et le type de base de données est un ensemble. Appliquez une union de jeu à l'aide de l'ensemble entrant et de l'ensemble existant dans le serveur. Cela respecte les propriétés d'un ensemble, ce qui signifie qu'il n'y a aucune entrée en double.

  • Lorsqu'une mutation entrante ajoute un nouveau champ à l'élément ou est effectuée sur un champ dont la valeur est denull, fusionnez-le avec l'élément existant.

  • Conflit sur une carte : lorsque le type de données sous-jacent dans la base de données est une carte (c.-à-d. un document clé-valeur), appliquez les règles ci-dessus lors de l'analyse et du traitement de chaque propriété de la carte.

Automerge est conçu pour détecter, fusionner et réessayer automatiquement les demandes avec une version mise à jour, ce qui évite au client de devoir fusionner manuellement les données en conflit.

Pour montrer un exemple de la façon dont Automerge gère un conflit sur un type scalaire. Nous utiliserons le dossier suivant comme point de départ.

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

Maintenant, une mutation entrante peut essayer de mettre à jour l'élément, mais avec une version plus ancienne puisque le client n'a pas encore été synchronisé avec le serveur. Elle se présente ainsi :

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

Notez la version obsolète de 2 dans la requête entrante. Pendant ce flux, Automerge fusionnera les données en rejetant la mise à jour du champ 'jersey' sur '55' et en conservant la valeur sur '5', ce qui entraîne l'enregistrement de l'image suivante de l'élément dans le serveur.

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

Compte tenu de l'état de l'élément indiqué ci-dessus dans la version 5, supposons maintenant une mutation entrante qui tente de muter l'élément avec l'image suivante :

{ "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 }

Il y a trois points d'intérêt dans la mutation entrante. Le nom, un scalaire, a été modifié, mais deux nouveaux champs « intérêts », un Set et « points », une Liste, ont été ajoutés. Dans ce scénario, un conflit sera détecté en raison de l'incompatibilité de version. Automerge respecte ses propriétés et rejette le changement de nom parce qu'il est un scalaire et il procède à l'ajout sur les champs non conflictuels. Il en résulte que l'élément qui est enregistré dans le serveur s'affiche comme suit.

{ "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 }

Avec l'image mise à jour de l'élément avec la version 6, supposons maintenant qu'une mutation entrante (avec une autre incompatibilité de version) essaie de transformer l'élément en ce qui suit :

{ "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 }

Ici, nous observons que le champ entrant pour « intérêts » a une valeur en double qui existe dans le serveur et deux nouvelles valeurs. Dans ce cas, puisque le type de données sous-jacent est un ensemble, Automerge combinera les valeurs existantes dans le serveur avec celles de la requête entrante et supprimera les doublons. De même, il y a un conflit sur le champ « points » où il y a une valeur en double et une nouvelle valeur. Mais puisque le type de données sous-jacent ici est une liste, Automerge ajoutera simplement toutes les valeurs de la requête entrante à la fin des valeurs déjà existantes dans le serveur. L'image fusionnée résultante stockée sur le serveur apparaîtrait comme suit :

{ "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 }

Supposons maintenant que l'élément stocké dans le serveur apparaît comme suit, à la version 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 }

Mais une demande entrante essaie de mettre à jour l'élément avec l'image suivante, encore une fois avec une incompatibilité de version :

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

Maintenant, dans ce scénario, nous pouvons voir que les champs qui existent déjà dans le serveur sont manquants (intérêts, points, jersey). En outre, la valeur de « ppg » dans la carte « stats » est en cours d'édition, une nouvelle valeur « rpg » est ajoutée et « apg » est omise. Automerge conserve les champs qui ont été omis (remarque : si les champs sont destinés à être supprimés, la demande doit être réessayée avec la version correspondante), ce qui signifie qu'ils ne seront pas perdus. Il appliquera également les mêmes règles aux champs dans les cartes et donc le changement de « ppg » sera rejeté alors que « apg » est conservé et « rpg », un nouveau champ », est ajouté. L'élément résultant stocké dans le serveur apparaîtra désormais sous la forme suivante :

{ "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 }

Lambdas

Il existe plusieurs stratégies de résolution Lambda parmi lesquelles choisir :

  • RESOLVE: remplace l'article existant par un nouvel article fourni en réponse à la charge utile. Vous ne pouvez réessayer la même opération que sur un seul élément à la fois. Actuellement pris en charge pour DynamoDB PutItem et UpdateItem.

  • REJECT: Rejette la mutation et renvoie une erreur concernant l'élément existant dans la réponse GraphQL. Actuellement pris en charge pour DynamoDB PutItem, UpdateItem & DeleteItem.

  • REMOVE: Supprime l'élément existant. Actuellement pris en charge pour DynamoDB DeleteItem.

Requête d'appel Lambda

Le résolveur AWS AppSync DynamoDB appelle la fonction Lambda spécifiée dans le. LambdaConflictHandlerArn Il utilise le même service-role-arn que celui configuré sur la source de données. La charge utile de l'appel a la structure suivante :

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

Les champs sont définis comme suit :

newItem

L'élément d'aperçu, si la mutation a réussi.

existingItem

L'élément réside actuellement dans la table DynamoDB.

arguments

Arguments de la mutation GraphQL.

resolver

Informations sur le AWS AppSync résolveur.

identity

Informations sur l'appelant. Ce champ est défini sur null en cas d'accès par API clé.

Exemple de charge utile :

{ "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" } }

Réponse à l'appel de Lambda

Pour PutItem et la résolution des conflits UpdateItem

RESOLVE la mutation. La réponse doit être au format suivant.

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

Le champ item représente un objet qui sera utilisé pour remplacer l'élément existant dans la source de données sous-jacente. La clé primaire et les métadonnées de synchronisation seront ignorées si elles sont incluses dans item.

REJECT la mutation. La réponse doit être au format suivant.

{ "action": "REJECT" }

Résolution des conflits DeleteItem

REMOVE l'élément. La réponse doit être au format suivant.

{ "action": "REMOVE" }

REJECT la mutation. La réponse doit être au format suivant.

{ "action": "REJECT" }

L'exemple de fonction Lambda ci-dessous vérifie qui effectue l'appel et le nom du résolveur. S'il est créé par jeffTheAdmin REMOVE l'objet pour le DeletePost résolveur ou le conflit avec le nouvel élément pour RESOLVE les résolveurs Update/Put. Sinon, la mutation est 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; }

Erreurs

Vous trouverez ci-dessous une liste des erreurs susceptibles de se produire lors d'un processus de résolution de conflits :

ConflictUnhandled

La détection de conflits détecte une incompatibilité de version et le gestionnaire de conflits rejette la mutation.

Exemple : résolution de conflits avec un gestionnaire de conflits Simultanéité optimiste. Ou, le gestionnaire de conflits Lambda retourné avec REJECT.

ConflictError

Une erreur interne se produit lors de la tentative de résolution d'un conflit.

Exemple : le gestionnaire de conflits Lambda a renvoyé une réponse mal formée. Ou, ne peut pas appeler le gestionnaire de conflits Lambda car la ressource fournie LambdaConflictHandlerArn est introuvable.

MaxConflicts

Le nombre maximal de tentatives de résolution de conflit a été atteint.

Exemple : Trop de demandes simultanées sur le même objet. Avant que le conflit soit résolu, l'objet est mis à jour vers une nouvelle version par un autre client.

BadRequest

Le client tente de mettre à jour les champs de métadonnées (_version, _ttl, _lastChangedAt, _deleted).

Exemple : le client essaie _version de mettre à jour un objet avec une mutation de mise à jour.

DeltaSyncWriteError

Échec de l'écriture de l'enregistrement de synchronisation delta.

Exemple : la mutation a réussi, mais une erreur interne s'est produite lors de la tentative d'écriture dans la table de synchronisation delta.

InternalFailure

Une erreur interne s’est produite.

UnsupportedOperation

Opération non prise en charge 'X'. Le versionnement des sources de données prend uniquement en charge les opérations suivantes (TransactGetItems,PutItem,BatchGetItem, Scan, Query,, GetItem DeleteItemUpdateItem, Sync).

Exemple : utilisation de certaines transactions et opérations par lots avec la détection/résolution des conflits activée. Ces opérations ne sont actuellement pas prises en charge.

CloudWatch Journaux

Si un journal AWS AppSync API a activé CloudWatch les journaux avec les paramètres de journalisation définis sur Journaux au niveau du champ enabled et le niveau de journalisation pour les journaux sur le champ défini surALL, les informations de détection et de résolution des conflits AWS AppSync seront transmises au groupe de journaux. Pour de plus amples informations sur le format des messages du journal, veuillez consulter la documentation relative à la détection des conflits et à la journalisation de la synchronisation.