Gestion de l’accès concurrentiel dans Blob StorageManaging Concurrency in Blob storage

Dans les applications modernes, les données sont souvent consultées et mises à jour par plusieurs utilisateurs à la fois.Modern applications often have multiple users viewing and updating data simultaneously. Les développeurs d'applications doivent donc bien réfléchir à la manière de proposer une expérience prévisible à leurs utilisateurs finaux, notamment lorsque plusieurs utilisateurs peuvent mettre à jour les mêmes données.Application developers need to think carefully about how to provide a predictable experience to their end users, particularly for scenarios where multiple users can update the same data. Les développeurs prennent généralement en compte trois grandes stratégies d’accès concurrentiel aux données :There are three main data concurrency strategies that developers typically consider:

  • Accès concurrentiel optimiste : Une application procédant à une mise à jour vérifie, dans le cadre de la mise à jour, si les données n'ont pas été modifiées depuis la dernière lecture.Optimistic concurrency: An application performing an update will, as part of its update, determine whether the data has changed since the application last read that data. Par exemple, si deux utilisateurs qui consultent une page wiki procèdent à une mise à jour de cette page, la plateforme wiki doit veiller à ce que la deuxième mise à jour n'écrase pas la première.For example, if two users viewing a wiki page make an update to that page, then the wiki platform must ensure that the second update does not overwrite the first update. Ils doivent également veiller à ce que les deux utilisateurs sachent si leur mise à jour a fonctionné ou non.It must also ensure that both users understand whether their update was successful. Cette stratégie est la plus souvent utilisée dans les applications web.This strategy is most often used in web applications.

  • Accès concurrentiel pessimiste : Une application qui cherche à procéder à une mise à jour verrouille l'objet, ce qui empêche les autres utilisateurs de mettre les données à jour jusqu'à ce qu'elles soient déverrouillées.Pessimistic concurrency: An application looking to perform an update will take a lock on an object preventing other users from updating the data until the lock is released. Par exemple, dans un scénario de réplication de données avec réplicas principal/secondaire où seul le réplica principal procède aux mises à jour, celui-ci verrouille généralement les données de manière exclusive pendant une période prolongée de manière à ce que personne d'autre ne puisse les mettre à jour.For example, in a primary/secondary data replication scenario in which only the primary performs updates, the primary typically holds an exclusive lock on the data for an extended period of time to ensure no one else can update it.

  • Dernière écriture prioritaire : Une approche permettant aux opérations de mise à jour de continuer sans déterminer d’abord si une autre application a mis à jour les données depuis leur lecture.Last writer wins: An approach that allows update operations to proceed without first determining whether another application has updated the data since it was read. Cette approche est généralement utilisée lorsque les données sont partitionnées de manière à ce que plusieurs utilisateurs n’accèdent pas simultanément aux mêmes données.This approach is typically used when data is partitioned in such a way that multiple users will not access the same data at the same time. Elle peut également être utile lors du traitement de flux de données à durée de vie limitée.It can also be useful where short-lived data streams are being processed.

Stockage Azure prend en charge les trois stratégies, bien qu’il se distingue par sa capacité à prendre en charge pleinement l’accès concurrentiel optimiste et pessimiste.Azure Storage supports all three strategies, although it is distinctive in its ability to provide full support for optimistic and pessimistic concurrency. Stockage Azure a été conçu pour appliquer un modèle de cohérence fort qui garantit qu’une fois que le service a effectué une opération d’insertion ou de mise à jour, les opérations de lecture suivantes retournent la dernière mise à jour.Azure Storage was designed to embrace a strong consistency model that guarantees that after the service performs an insert or update operation, subsequent read operations return the latest update.

Parallèlement à la sélection d'une stratégie d'accès concurrentiel adaptée, les développeurs doivent savoir comment la plateforme de stockage isole les changements, notamment ceux apportés à un même objet au fil des transactions.In addition to selecting an appropriate concurrency strategy, developers should also be aware of how a storage platform isolates changes, particularly changes to the same object across transactions. Stockage Azure utilise l’isolement de capture instantanée pour permettre l’exécution simultanée des opérations de lecture et d’écriture au sein d’une même partition.Azure Storage uses snapshot isolation to allow read operations concurrently with write operations within a single partition. L’isolement d’instantané garantit que toutes les opérations de lecture retournent un instantané cohérent des données, même pendant les mises à jour.Snapshot isolation guarantees that all read operations return a consistent snapshot of the data even while updates are occurring.

Vous pouvez choisir d’utiliser des modèles d’accès concurrentiel optimiste ou pessimiste pour gérer l’accès aux objets blob et aux conteneurs.You can opt to use either optimistic or pessimistic concurrency models to manage access to blobs and containers. Si vous ne spécifiez pas une stratégie de manière explicite, la dernière écriture prévaut par défaut.If you don't explicitly specify a strategy, then by default the last writer wins.

Accès concurrentiel optimisteOptimistic concurrency

Stockage Azure attribue un identificateur à chaque objet stocké.Azure Storage assigns an identifier to every object stored. Cet identificateur est mis à jour chaque fois qu'une opération d’écriture est effectuée sur un objet.This identifier is updated every time a write operation is performed on an object. L'identificateur est renvoyé au client en tant que réponse HTTP GET dans l'en-tête ETag défini par le protocole HTTP.The identifier is returned to the client as part of an HTTP GET response in the ETag header that is defined by the HTTP protocol.

Un client qui effectue une mise à jour peut envoyer la valeur ETag d’origine avec un en-tête conditionnel pour s’assurer qu’une mise à jour se produira uniquement si une certaine condition est remplie.A client that is performing an update can send the original ETag together with a conditional header to ensure that an update will only occur if a certain condition has been met. Par exemple, si l’en-tête If-Match est spécifié, Stockage Azure vérifie que la valeur ETag spécifiée dans la demande de mise à jour est identique à la valeur ETag de l’objet en cours de mise à jour.For example, if the If-Match header is specified, Azure Storage verifies that the value of the ETag specified in the update request is the same as the ETag for the object being updated. Pour plus d’informations sur les en-têtes conditionnels, consultez Spécification des en-têtes conditionnels pour les opérations du service Blob.For more information about conditional headers, see Specifying conditional headers for Blob service operations.

Ce processus se déroule comme suit :The outline of this process is as follows:

  1. Récupérez un objet blob à partir de Stockage Azure.Retrieve a blob from Azure Storage. La réponse inclut une valeur d'en-tête ETag HTTP qui identifie la version actuelle de l'objet.The response includes an HTTP ETag Header value that identifies the current version of the object.
  2. Lorsque vous mettez l'objet blob à jour, incluez la valeur ETag reçue à l'étape 1 dans l'en-tête conditionnel If-Match de la demande d’écriture.When you update the blob, include the ETag value you received in step 1 in the If-Match conditional header of the write request. Stockage Azure compare la valeur ETag de la demande à la valeur ETag de l'objet blob.Azure Storage compares the ETag value in the request with the current ETag value of the blob.
  3. Si la valeur ETag actuelle de l’objet blob diffère de celle spécifiée dans l’en-tête conditionnel If-Match fourni dans la demande, Stockage Azure retourne le code d’état HTTP 412 (échec de la précondition).If the blob's current ETag value differs from that specified in the If-Match conditional header provided on the request, then Azure Storage returns HTTP status code 412 (Precondition Failed). Cette erreur indique au client que l’objet blob a été mis à jour par un autre processus depuis la première récupération par le client.This error indicates to the client that another process has updated the blob since the client first retrieved it.
  4. Si la valeur ETag actuelle de l'objet blob est la même que la balise ETag dans l'en-tête conditionnel If-Match de la demande, Stockage Azure effectue l'opération demandée et met la valeur ETag de l'objet blob à jour.If the current ETag value of the blob is the same version as the ETag in the If-Match conditional header in the request, Azure Storage performs the requested operation and updates the current ETag value of the blob.

Les exemples de code suivants montrent comment construire une condition If-Match sur la demande d’écriture qui vérifie la valeur ETag pour un objet blob.The following code examples show how to construct an If-Match condition on the write request that checks the ETag value for a blob. Stockage Azure évalue si la valeur ETag actuelle de l’objet blob est identique à la valeur ETag fournie dans la demande, puis exécute l’opération d’écriture uniquement si les deux valeurs ETag correspondent.Azure Storage evaluates whether the blob's current ETag is the same as the ETag provided on the request and performs the write operation only if the two ETag values match. Si l’objet blob a été mis à jour entre-temps par un autre processus, Stockage Azure retourne un message d’état HTTP 412 (Échec de la condition préalable).If another process has updated the blob in the interim, then Azure Storage returns an HTTP 412 (Precondition Failed) status message.

private static async Task DemonstrateOptimisticConcurrencyBlob(BlobClient blobClient)
{
    Console.WriteLine("Demonstrate optimistic concurrency");

    BlobContainerClient containerClient = blobClient.GetParentBlobContainerClient();

    try
    {
        // Create the container if it does not exist.
        await containerClient.CreateIfNotExistsAsync();

        // Upload text to a new block blob.
        string blobContents1 = "First update. Overwrite blob if it exists.";
        byte[] byteArray = Encoding.ASCII.GetBytes(blobContents1);

        ETag originalETag;

        using (MemoryStream stream = new MemoryStream(byteArray))
        {
            BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream, overwrite: true);
            originalETag = blobContentInfo.ETag;
            Console.WriteLine("Blob added. Original ETag = {0}", originalETag);
        }

        // This code simulates an update by another client.
        // No ETag was provided, so original blob is overwritten and ETag updated.
        string blobContents2 = "Second update overwrites first update.";
        byteArray = Encoding.ASCII.GetBytes(blobContents2);

        using (MemoryStream stream = new MemoryStream(byteArray))
        {
            BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream, overwrite: true);
            Console.WriteLine("Blob updated. Updated ETag = {0}", blobContentInfo.ETag);
        }

        // Now try to update the blob using the original ETag value.
        string blobContents3 = "Third update. If-Match condition set to original ETag.";
        byteArray = Encoding.ASCII.GetBytes(blobContents3);

        // Set the If-Match condition to the original ETag.
        BlobUploadOptions blobUploadOptions = new BlobUploadOptions()
        {
            Conditions = new BlobRequestConditions()
            {
                IfMatch = originalETag
            }
        };

        using (MemoryStream stream = new MemoryStream(byteArray))
        {
            // This call should fail with error code 412 (Precondition Failed).
            BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream, blobUploadOptions);
        }
    }
    catch (RequestFailedException e)
    {
        if (e.Status == (int)HttpStatusCode.PreconditionFailed)
        {
            Console.WriteLine(
                @"Precondition failure as expected. Blob's ETag does not match ETag provided.");
        }
        else
        {
            Console.WriteLine(e.Message);
            throw;
        }
    }
}

Stockage Azure prend également en charge d’autres en-têtes conditionnels, notamment If-Modified-Since, If-Unmodified-Since et If-None-Match.Azure Storage also supports other conditional headers, including as If-Modified-Since, If-Unmodified-Since and If-None-Match. Pour plus d’informations, consultez Spécification des en-têtes conditionnels pour les opérations du service Blob.For more information, see Specifying Conditional Headers for Blob Service Operations.

Accès concurrentiel pessimiste pour les objets blobPessimistic concurrency for blobs

Pour verrouiller un objet blob de manière à l'utiliser de manière exclusive, vous pouvez obtenir un bail pour l'objet blob.To lock a blob for exclusive use, you can acquire a lease on it. Lorsque vous acquérez le bail, vous spécifiez sa durée.When you acquire the lease, you specify the duration of the lease. Un bail fini peut être compris entre 15 et 60 secondes.A finite lease may be valid from between 15 to 60 seconds. Un bail peut également être infini, ce qui correspond à un verrou exclusif.A lease can also be infinite, which amounts to an exclusive lock. Vous pouvez renouveler un bail à durée limitée et vous pouvez libérer le bail lorsque vous n'en avez plus besoin.You can renew a finite lease to extend it, and you can release the lease when you're finished with it. Azure Stockage libère automatiquement les baux à durée limitée quand ils expirent.Azure Storage automatically releases finite leases when they expire.

Les baux permettent la prise en charge de différentes stratégies de synchronisation, dont des opérations d'écriture exclusive/de lecture partagée, d'écriture exclusive/de lecture exclusive et d'écriture partagée/de lecture exclusive.Leases enable different synchronization strategies to be supported, including exclusive write/shared read operations, exclusive write/exclusive read operations, and shared write/exclusive read operations. Si un bail existe, Stockage Azure applique l’accès exclusif aux opérations d’écriture pour le titulaire de ce bail.When a lease exists, Azure Storage enforces exclusive access to write operations for the lease holder. Cependant, pour garantir l’exclusivité des opérations de lecture, le développeur doit veiller à ce que toutes les applications clientes utilisent un identificateur de bail et à ce que seul un client à la fois dispose d’un identificateur de bail valable.However, ensuring exclusivity for read operations requires the developer to make sure that all client applications use a lease ID and that only one client at a time has a valid lease ID. Les opérations de lecture sans identificateur de bail entraînent l’application d’une stratégie de lecture partagée.Read operations that do not include a lease ID result in shared reads.

Les exemples de code suivants montrent comment acquérir un bail exclusif sur un objet blob, mettre à jour le contenu de cet objet blob en fournissant l’ID de bail, puis libérer le bail.The following code examples show how to acquire an exclusive lease on a blob, update the content of the blob by providing the lease ID, and then release the lease. Si le bail est actif et que l’ID de bail n’est pas fourni dans une demande d’écriture, l’opération d’écriture échoue avec le code d’erreur 412 (échec de la précondition).If the lease is active and the lease ID is not provided on a write request, then the write operation fails with error code 412 (Precondition Failed).

public static async Task DemonstratePessimisticConcurrencyBlob(BlobClient blobClient)
{
    Console.WriteLine("Demonstrate pessimistic concurrency");

    BlobContainerClient containerClient = blobClient.GetParentBlobContainerClient();
    BlobLeaseClient blobLeaseClient = blobClient.GetBlobLeaseClient();

    try
    {
        // Create the container if it does not exist.
        await containerClient.CreateIfNotExistsAsync();

        // Upload text to a blob.
        string blobContents1 = "First update. Overwrite blob if it exists.";
        byte[] byteArray = Encoding.ASCII.GetBytes(blobContents1);
        using (MemoryStream stream = new MemoryStream(byteArray))
        {
            BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream, overwrite: true);
        }

        // Acquire a lease on the blob.
        BlobLease blobLease = await blobLeaseClient.AcquireAsync(TimeSpan.FromSeconds(15));
        Console.WriteLine("Blob lease acquired. LeaseId = {0}", blobLease.LeaseId);

        // Set the request condition to include the lease ID.
        BlobUploadOptions blobUploadOptions = new BlobUploadOptions()
        {
            Conditions = new BlobRequestConditions()
            {
                LeaseId = blobLease.LeaseId
            }
        };

        // Write to the blob again, providing the lease ID on the request.
        // The lease ID was provided, so this call should succeed.
        string blobContents2 = "Second update. Lease ID provided on request.";
        byteArray = Encoding.ASCII.GetBytes(blobContents2);

        using (MemoryStream stream = new MemoryStream(byteArray))
        {
            BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream, blobUploadOptions);
        }

        // This code simulates an update by another client.
        // The lease ID is not provided, so this call fails.
        string blobContents3 = "Third update. No lease ID provided.";
        byteArray = Encoding.ASCII.GetBytes(blobContents3);

        using (MemoryStream stream = new MemoryStream(byteArray))
        {
            // This call should fail with error code 412 (Precondition Failed).
            BlobContentInfo blobContentInfo = await blobClient.UploadAsync(stream);
        }
    }
    catch (RequestFailedException e)
    {
        if (e.Status == (int)HttpStatusCode.PreconditionFailed)
        {
            Console.WriteLine(
                @"Precondition failure as expected. The lease ID was not provided.");
        }
        else
        {
            Console.WriteLine(e.Message);
            throw;
        }
    }
    finally
    {
        await blobLeaseClient.ReleaseAsync();
    }
}

Accès concurrentiel pessimiste pour les conteneursPessimistic concurrency for containers

Les baux sur des conteneurs permettent les mêmes stratégies de synchronisation prises en charge pour les objets blob, dont des stratégies d'écriture exclusive/de lecture partagée, d'écriture exclusive/de lecture exclusive et d'écriture partagée/de lecture exclusive.Leases on containers enable the same synchronization strategies that are supported for blobs, including exclusive write/shared read, exclusive write/exclusive read, and shared write/exclusive read. Pour les conteneurs, toutefois, le verrou exclusif est appliqué uniquement aux opérations de suppression.For containers, however, the exclusive lock is enforced only on delete operations. Pour supprimer un conteneur avec un bail actif, le client doit inclure l'identificateur du bail actif dans la demande de suppression.To delete a container with an active lease, a client must include the active lease ID with the delete request. Toutes les autres opérations de conteneur réussiront sur un conteneur loué sans ID de bail.All other container operations will succeed on a leased container without the lease ID.

Étapes suivantesNext steps