

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.

# Meilleures pratiques pour gérer les mises à jour simultanées dans DynamoDB
<a name="BestPractices_ImplementingVersionControl"></a>

Dans les systèmes distribués, plusieurs processus ou utilisateurs peuvent tenter de modifier les mêmes données en même temps. Sans contrôle de la simultanéité, ces écritures simultanées peuvent entraîner des pertes de mises à jour, des données incohérentes ou des conditions de course. DynamoDB propose plusieurs mécanismes pour vous aider à gérer les accès simultanés et à préserver l'intégrité des données.

**Note**  
Les opérations d'écriture individuelles, telles que `UpdateItem` les opérations atomiques, fonctionnent toujours sur la version la plus récente de l'élément, quelle que soit la simultanéité. Des stratégies de verrouillage sont nécessaires lorsque votre application doit lire un élément puis le réécrire en fonction de la valeur lue (un read-modify-write cycle), car un autre processus pourrait modifier l'élément entre la lecture et l'écriture.

Il existe deux stratégies principales pour gérer les mises à jour simultanées :
+ **Verrouillage optimiste** : suppose que les conflits sont rares. Il permet un accès simultané et détecte les conflits au moment de l'écriture à l'aide d'écritures conditionnelles. Si un conflit est détecté, l'écriture échoue et l'application peut réessayer.
+ **Verrouillage pessimiste** : suppose que des conflits sont probables. Il empêche l'accès simultané en acquérant un accès exclusif à une ressource avant de la modifier. Les autres processus doivent attendre que le verrou soit déverrouillé.

Le tableau suivant récapitule les approches disponibles dans DynamoDB :


| Approche | Mécanisme | Idéal pour | 
| --- | --- | --- | 
| Verrouillage optimiste | Attribut de version \$1 écritures conditionnelles | Faible contention, nouvelles tentatives peu coûteuses | 
| Verrouillage pessimiste (transactions) | TransactWriteItems | Atomicité multi-objets, contention modérée | 
| Verrouillage pessimiste (client de verrouillage) | Table verrouillée dédiée avec durée et battement de cœur | Flux de travail de longue durée, coordination distribuée | 

# Verrouillage optimiste avec numéro de version
<a name="BestPractices_OptimisticLocking"></a>

Le verrouillage optimiste est une stratégie qui détecte les conflits au moment de l'écriture plutôt que de les prévenir. Chaque élément inclut un attribut de version qui augmente à chaque mise à jour. Lorsque vous mettez à jour un élément, vous incluez une [expression de condition](Expressions.ConditionExpressions.md) qui vérifie si le numéro de version correspond à la dernière valeur lue par votre application. Si un autre processus a modifié l'élément dans l'intervalle, la condition échoue et DynamoDB renvoie un. `ConditionalCheckFailedException`

## Quand utiliser le verrouillage optimiste
<a name="BestPractices_OptimisticLocking_WhenToUse"></a>

Le verrouillage optimiste convient parfaitement lorsque :
+ Plusieurs utilisateurs ou processus peuvent mettre à jour le même élément, mais les conflits sont rares.
+ Réessayer une écriture qui a échoué est peu coûteux pour votre application.
+ Vous souhaitez éviter les frais généraux et la complexité liés à la gestion des verrous distribués.

Les exemples courants incluent les mises à jour de l'inventaire du commerce électronique, les plateformes d'édition collaboratives et les enregistrements de transactions financières.

## Compromis
<a name="BestPractices_OptimisticLocking_Tradeoffs"></a>

**Réessayez de surcharger en cas de forte contention**  
Dans les environnements à simultanéité élevée, le risque de conflits augmente, ce qui peut entraîner de nouvelles tentatives et des coûts d’écriture plus élevés.

**Complexité d’implémentation**  
L'ajout d'un contrôle de version aux éléments et la gestion des vérifications conditionnelles ajoutent de la complexité à la logique de l'application. Le AWS SDK for Java v2 Enhanced Client fournit un support intégré via [https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE)l'annotation, qui gère automatiquement les numéros de version pour vous.

## Conception de modèle
<a name="BestPractices_OptimisticLocking_PatternDesign"></a>

Incluez un attribut de version dans chaque élément. Voici un schéma simple :
+ Clé de partition : identifiant unique pour chaque élément (par exemple,`ItemId`).
+ Attributs :
  + `ItemId` : identifiant unique de l’élément.
  + `Version` : nombre entier qui représente le numéro de version de l’élément.
  + `QuantityLeft` : stock restant de l’article.

Lorsqu’un élément est créé pour la première fois, l’attribut `Version` est défini sur 1. À chaque mise à jour, le numéro de version augmente de 1.


| ItemID (clé de partition) | Version | QuantityLeft | 
| --- | --- | --- | 
| Bananes | 1 | 10 | 
| Pommes | 1 | 5 | 
| Oranges | 1 | 7 | 

## Mise en œuvre
<a name="BestPractices_OptimisticLocking_Implementation"></a>

Pour implémenter le verrouillage optimiste, procédez comme suit :

1. Lisez la version actuelle de l’élément.

   ```
   def get_item(item_id):
       response = table.get_item(Key={'ItemID': item_id})
       return response['Item']
   
   item = get_item('Bananas')
   current_version = item['Version']
   ```

1. Mettez à jour l'élément à l'aide d'une expression de condition qui vérifie le numéro de version.

   ```
   def update_item(item_id, qty_bought, current_version):
       try:
           response = table.update_item(
               Key={'ItemID': item_id},
               UpdateExpression="SET QuantityLeft = QuantityLeft - :qty, Version = :new_v",
               ConditionExpression="Version = :expected_v",
               ExpressionAttributeValues={
                   ':qty': qty_bought,
                   ':new_v': current_version + 1,
                   ':expected_v': current_version
               },
               ReturnValues="UPDATED_NEW"
           )
           return response
       except ClientError as e:
           if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
               print("Version conflict: another process updated this item.")
           raise
   ```

1. Gérez les conflits en réessayant avec une nouvelle lecture.

   Chaque nouvelle tentative nécessite une lecture supplémentaire. Limitez donc le nombre total de tentatives.

   ```
   def update_with_retry(item_id, qty_bought, max_retries=3):
       for attempt in range(max_retries):
           item = get_item(item_id)
           try:
               return update_item(item_id, qty_bought, item['Version'])
           except ClientError as e:
               if e.response['Error']['Code'] != 'ConditionalCheckFailedException':
                   raise
               print(f"Retry {attempt + 1}/{max_retries}")
       raise Exception("Update failed after maximum retries.")
   ```

Pour les applications Java, le AWS SDK for Java v2 Enhanced Client fournit un support de verrouillage optimisé intégré via [https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE)l'annotation, qui gère automatiquement les numéros de version pour vous.

Pour plus d'informations sur les expressions de condition, consultez[Exemple de commande CLI d’expression de condition DynamoDB](Expressions.ConditionExpressions.md).

# Verrouillage pessimiste avec les transactions DynamoDB
<a name="BestPractices_PessimisticLocking"></a>

Les transactions [DynamoDB](transactions.md) fournissent all-or-nothing une approche pour les opérations groupées. Lorsque vous l'utilisez`TransactWriteItems`, DynamoDB surveille tous les éléments de la transaction. Si un élément est modifié par une autre opération au cours de la transaction, l'intégralité de la transaction est annulée et DynamoDB renvoie un. `TransactionCanceledException` Ce comportement fournit une forme de contrôle de simultanéité pessimiste, car les modifications simultanées contradictoires sont empêchées plutôt que détectées a posteriori.

## Quand utiliser les transactions pour le verrouillage
<a name="BestPractices_PessimisticLocking_WhenToUse"></a>

Les transactions conviennent bien lorsque :
+ Vous devez mettre à jour plusieurs éléments de manière atomique, soit au sein d'une même table, soit entre plusieurs tables.
+ Votre logique métier nécessite de la all-or-nothing sémantique : soit tous les changements sont réussis, soit aucun n'est appliqué.

Les exemples courants incluent le transfert de fonds entre comptes, les commandes qui mettent à jour à la fois l'inventaire et les tables de commandes, et l'échange d'articles entre joueurs dans un jeu.

## Compromis
<a name="BestPractices_PessimisticLocking_Tradeoffs"></a>

**Coût d'écriture plus élevé**  
Pour les éléments allant jusqu'à 1 Ko, les transactions WCUs en consomment 2 (un pour préparer, un pour valider), contre 1 WCU pour une écriture standard.

**Limite d'articles**  
Une seule transaction peut inclure jusqu'à 100 actions réparties sur une ou plusieurs tables.

**Sensibilité aux conflits**  
Si un élément de la transaction est modifié par une autre opération, l'ensemble de la transaction échoue. Dans les scénarios très litigieux, cela peut entraîner de fréquentes annulations.

## Mise en œuvre
<a name="BestPractices_PessimisticLocking_Implementation"></a>

L'exemple suivant permet `TransactWriteItems` de transférer le stock entre deux articles de manière atomique. Si un autre processus modifie l'un des éléments au cours de la transaction, l'ensemble de l'opération est annulé.

```
import boto3

client = boto3.client('dynamodb')

def transfer_inventory(source_id, target_id, quantity):
    try:
        client.transact_write_items(
            TransactItems=[
                {
                    'Update': {
                        'TableName': 'Inventory',
                        'Key': {'ItemID': {'S': source_id}},
                        'UpdateExpression': 'SET QuantityLeft = QuantityLeft - :qty',
                        'ConditionExpression': 'QuantityLeft >= :qty',
                        'ExpressionAttributeValues': {
                            ':qty': {'N': str(quantity)}
                        }
                    }
                },
                {
                    'Update': {
                        'TableName': 'Inventory',
                        'Key': {'ItemID': {'S': target_id}},
                        'UpdateExpression': 'SET QuantityLeft = QuantityLeft + :qty',
                        'ExpressionAttributeValues': {
                            ':qty': {'N': str(quantity)}
                        }
                    }
                }
            ]
        )
        return True
    except client.exceptions.TransactionCanceledException as e:
        print(f"Transaction canceled: {e}")
        return False
```

Dans cet exemple, l'expression de condition vérifie qu'il existe un inventaire suffisant, mais aucun attribut de version n'est nécessaire. DynamoDB annule automatiquement la transaction si un élément de la transaction est modifié par une autre opération entre les phases de préparation et de validation. C'est ce qui permet un contrôle pessimiste de la simultanéité : les modifications simultanées contradictoires sont empêchées par la transaction elle-même.

**Note**  
Vous pouvez associer des transactions à un verrouillage optimiste en ajoutant des vérifications de version sous forme d'expressions de condition supplémentaires. Cela fournit un niveau de protection supplémentaire mais n'est pas nécessaire pour que la transaction détecte les conflits.

Pour de plus amples informations, veuillez consulter [Gestion des flux complexes avec des transactions Amazon DynamoDB](transactions.md).

# Verrouillage distribué avec le client DynamoDB Lock
<a name="BestPractices_DistributedLocking"></a>

Pour les applications qui nécessitent une lock-acquire-release sémantique traditionnelle, le client DynamoDB Lock est une bibliothèque open source qui implémente le verrouillage distribué en utilisant une table DynamoDB comme magasin de verrous. Cette approche est utile lorsque vous devez coordonner l'accès à une ressource externe (telle qu'un objet S3 ou une configuration partagée) entre plusieurs instances d'application.

Le client de verrouillage est disponible sous forme de [bibliothèque Java](https://github.com/awslabs/amazon-dynamodb-lock-client) open source.

## Comment ça marche
<a name="BestPractices_DistributedLocking_HowItWorks"></a>

Le client de verrouillage utilise une table DynamoDB dédiée pour suivre les verrouillages. Chaque verrou est représenté sous la forme d'un élément doté des attributs clés suivants :
+ Clé de partition qui identifie la ressource verrouillée.
+ Durée du bail qui indique la durée de validité du verrou. Si le porte-cadenas tombe en panne ou ne répond plus, le verrou expire automatiquement après la durée du bail.
+ Un battement de cœur que le porteur du cadenas envoie périodiquement pour prolonger le bail. Cela empêche le verrou d'expirer alors que le support est encore en cours de traitement.

Le client de verrouillage utilise des écritures conditionnelles pour garantir qu'un seul processus peut acquérir un verrou à la fois. Si un verrou est déjà bloqué, l'appelant peut choisir d'attendre et de réessayer ou d'échouer immédiatement.

## Quand utiliser le client de verrouillage
<a name="BestPractices_DistributedLocking_WhenToUse"></a>

Le client de verrouillage convient parfaitement lorsque :
+ Vous devez coordonner l'accès à une ressource partagée entre plusieurs instances d'application ou microservices.
+ La section critique est longue (quelques secondes à quelques minutes) et il serait coûteux de réessayer l'ensemble de l'opération en cas de conflit.
+ Vous avez besoin d'une expiration automatique du verrouillage pour gérer les défaillances de processus avec élégance.

Les exemples courants incluent l'orchestration de flux de travail distribués, la coordination des tâches cron sur plusieurs instances et la gestion de l'accès aux ressources externes partagées.

## Compromis
<a name="BestPractices_DistributedLocking_Tradeoffs"></a>

**Infrastructure supplémentaire**  
Nécessite une table DynamoDB dédiée pour la gestion du verrouillage, avec une capacité de lecture et d'écriture supplémentaire pour les opérations de verrouillage et les pulsations cardiaques.

**Dépendance d'horloge**  
L'expiration du verrou dépend de l'horodatage. Un décalage horaire important entre les clients peut entraîner un comportement inattendu, en particulier pour les courtes durées de location.

**Risque de blocage**  
Si votre application obtient des verrous sur plusieurs ressources, vous devez les verrouiller dans un ordre cohérent pour éviter les blocages. La durée du bail constitue un filet de sécurité en déverrouillant automatiquement les serrures lorsque les détenteurs ne répondent pas.

## Mise en œuvre
<a name="BestPractices_DistributedLocking_Implementation"></a>

L'exemple suivant montre comment utiliser le client DynamoDB Lock pour acquérir et libérer un verrou :

```
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;

final DynamoDbClient dynamoDB = DynamoDbClient.builder()
    .region(Region.US_WEST_2)
    .build();

final AmazonDynamoDBLockClient lockClient = new AmazonDynamoDBLockClient(
    AmazonDynamoDBLockClientOptions.builder(dynamoDB, "Locks")
        .withTimeUnit(TimeUnit.SECONDS)
        .withLeaseDuration(10L)
        .withHeartbeatPeriod(3L)
        .withCreateHeartbeatBackgroundThread(true)
        .build());

// Try to acquire a lock on a resource
final Optional<LockItem> lock =
    lockClient.tryAcquireLock(AcquireLockOptions.builder("my-shared-resource").build());

if (lock.isPresent()) {
    try {
        // Perform operations that require exclusive access
        processSharedResource();
    } finally {
        // Always release the lock when done
        lockClient.releaseLock(lock.get());
    }
} else {
    System.out.println("Failed to acquire lock.");
}

lockClient.close();
```

**Important**  
Relâchez toujours les verrous d'un `finally` bloc pour vous assurer que les verrous sont libérés même si votre logique de traitement génère une exception. Les verrous inédits bloquent les autres processus jusqu'à l'expiration du bail.

Vous pouvez également implémenter un mécanisme de verrouillage simple sans verrouiller la bibliothèque cliente en utilisant directement les écritures conditionnelles. L'exemple suivant utilise `UpdateItem` une expression de condition pour acquérir un verrou et `DeleteItem` le libérer :

```
from datetime import datetime, timedelta
from boto3.dynamodb.conditions import Attr

def acquire_lock(table, resource_name, owner_id, ttl_seconds):
    """Attempt to acquire a lock. Returns True if successful."""
    expiry = (datetime.now() + timedelta(seconds=ttl_seconds)).isoformat()
    now = datetime.now().isoformat()
    try:
        table.update_item(
            Key={'LockID': resource_name},
            UpdateExpression='SET #owner = :owner, #expiry = :expiry',
            ConditionExpression=Attr('LockID').not_exists() | Attr('ExpiresAt').lt(now),
            ExpressionAttributeNames={'#owner': 'OwnerID', '#expiry': 'ExpiresAt'},
            ExpressionAttributeValues={':owner': owner_id, ':expiry': expiry}
        )
        return True
    except table.meta.client.exceptions.ConditionalCheckFailedException:
        return False

def release_lock(table, resource_name, owner_id):
    """Release a lock. Only succeeds if the caller is the lock owner."""
    try:
        table.delete_item(
            Key={'LockID': resource_name},
            ConditionExpression=Attr('OwnerID').eq(owner_id)
        )
        return True
    except table.meta.client.exceptions.ConditionalCheckFailedException:
        return False
```

Cette approche utilise une expression conditionnelle pour garantir qu'un verrou ne peut être acquis que s'il n'existe pas ou a expiré, et qu'il ne peut être libéré que par le processus qui l'a acquis. Envisagez d'activer [le délai de vie (TTL)](TTL.md) sur la table de verrouillage pour nettoyer automatiquement les éléments de verrouillage expirés.

## Choisir une stratégie de contrôle de la simultanéité
<a name="BestPractices_ChoosingLockingStrategy"></a>

Suivez les directives suivantes pour choisir l'approche adaptée à votre charge de travail :

**Utilisez le verrouillage optimiste** lorsque :  
+ Les conflits sont rares.
+ Réessayer une écriture qui a échoué est peu coûteux.
+ Vous mettez à jour un seul élément à la fois.

**Utilisez des transactions** lorsque :  
+ Vous devez mettre à jour plusieurs éléments de manière atomique.
+ Vous avez besoin d'une all-or-nothing sémantique entre les éléments ou les tables.
+ Vous devez combiner les vérifications d'état avec les écritures en une seule opération.

**Utilisez le client de verrouillage** lorsque :  
+ Vous devez coordonner l'accès aux ressources externes dans le cadre des processus distribués.
+ La section critique est longue et il est coûteux de réessayer de résoudre un conflit.
+ Vous avez besoin d'une expiration automatique du verrouillage pour gérer les défaillances du processus.

**Note**  
Si vous utilisez des tables globales [DynamoDB, sachez que les tables globales](GlobalTables.md) utilisent une stratégie de réconciliation selon laquelle « le dernier rédacteur gagne » pour les mises à jour simultanées. Le verrouillage optimiste avec des numéros de version ne fonctionne pas comme prévu dans toutes les régions, car une écriture dans une région peut remplacer une écriture simultanée dans une autre région sans vérification de version. Concevez votre application de manière à gérer les conflits au niveau de l'application lorsque vous utilisez des tables globales.