Come gestire la concorrenza in Ricerca di AzureHow to manage concurrency in Azure Search

Quando si gestiscono le risorse di Ricerca di Azure, ad esempio indici e origini dati, è importante aggiornare le risorse in modo sicuro, soprattutto se componenti diversi dell'applicazione accedono simultaneamente alle risorse.When managing Azure Search resources such as indexes and data sources, it's important to update resources safely, especially if resources are accessed concurrently by different components of your application. Quando due client aggiornano simultaneamente un risorsa senza coordinazione, si possono verificare race condition.When two clients concurrently update a resource without coordination, race conditions are possible. Per evitare questo problema, Ricerca di Azure offre un modello di concorrenza ottimistica.To prevent this, Azure Search offers an optimistic concurrency model. Non sono presenti blocchi su una risorsa,There are no locks on a resource. ma per ogni risorsa esiste un ETag che identifica la versione della risorsa in modo che sia possibile creare richieste che evitano sovrascritture accidentali.Instead, there is an ETag for every resource that identifies the resource version so that you can craft requests that avoid accidental overwrites.

Suggerimento

Il codice concettuale in una soluzione C# di esempio illustra come funziona il controllo della concorrenza in Ricerca di Azure.Conceptual code in a sample C# solution explains how concurrency control works in Azure Search. Il codice crea le condizioni che richiamano il controllo della concorrenza.The code creates conditions that invoke concurrency control. La lettura del frammento di codice riportato più avanti è probabilmente sufficiente per la maggior parte degli sviluppatori, ma, se lo si vuole eseguire, modificare appsettings.json per aggiungere il nome del servizio e una chiave API di amministrazione.Reading the code fragment below is probably sufficient for most developers, but if you want to run it, edit appsettings.json to add the service name and an admin api-key. Dato un URL del servizio http://myservice.search.windows.net, il nome del service è myservice.Given a service URL of http://myservice.search.windows.net, the service name is myservice.

FunzionamentoHow it works

La concorrenza ottimistica viene implementata tramite i controlli delle condizioni di accesso nelle chiamate API che scrivono in indici, indicizzatori, origini dati e risorse synonymMap.Optimistic concurrency is implemented through access condition checks in API calls writing to indexes, indexers, datasources, and synonymMap resources.

Tutte le risorse hanno un tag di entità (ETag) che fornisce informazioni sulla versione dell'oggetto.All resources have an entity tag (ETag) that provides object version information. Controllando prima l'ETag, è possibile evitare aggiornamenti simultanei in un flusso di lavoro tipico (acquisizione, modifica locale, aggiornamento) assicurandosi che l'ETag della risorsa corrisponda alla copia locale.By checking the ETag first, you can avoid concurrent updates in a typical workflow (get, modify locally, update) by ensuring the resource's ETag matches your local copy.

Ogni volta che si aggiorna una risorsa, l'ETag cambia automaticamente.Every time you update a resource, its ETag changes automatically. Quando si implementa la gestione della concorrenza, si inserisce semplicemente una precondizione nella richiesta di aggiornamento, che obbliga la risorsa remota ad avere lo stesso ETag della copia della risorsa modificata nel client.When you implement concurrency management, all you're doing is putting a precondition on the update request that requires the remote resource to have the same ETag as the copy of the resource that you modified on the client. Se un processo simultaneo ha già modificato la risorsa remota, l'ETag non soddisferà la precondizione e la richiesta genererà l'errore HTTP 412.If a concurrent process has changed the remote resource already, the ETag will not match the precondition and the request will fail with HTTP 412. Se si usa .NET SDK, viene generata una CloudException in cui il metodo di estensione IsAccessConditionFailed() restituisce true.If you're using the .NET SDK, this manifests as a CloudException where the IsAccessConditionFailed() extension method returns true.

Nota

Esiste un solo meccanismo per la concorrenza,There is only one mechanism for concurrency. che viene sempre usato indipendentemente dall'API usata per gli aggiornamenti delle risorse.It's always used regardless of which API is used for resource updates.

Casi d'uso e codici di esempioUse cases and sample code

Il codice seguente illustra i controlli accessCondition per le principali operazioni di aggiornamento:The following code demonstrates accessCondition checks for key update operations:

  • L'aggiornamento non riesce se la risorsa non esiste piùFail an update if the resource no longer exists
  • L'aggiornamento non riesce se la versione della risorsa viene modificataFail an update if the resource version changes

Codice di esempio dal programma DotNetETagsExplainerSample code from DotNetETagsExplainer program

    class Program
    {
        // This sample shows how ETags work by performing conditional updates and deletes
        // on an Azure Search index.
        static void Main(string[] args)
        {
            IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
            IConfigurationRoot configuration = builder.Build();

            SearchServiceClient serviceClient = CreateSearchServiceClient(configuration);

            Console.WriteLine("Deleting index...\n");
            DeleteTestIndexIfExists(serviceClient);

            // Every top-level resource in Azure Search has an associated ETag that keeps track of which version
            // of the resource you're working on. When you first create a resource such as an index, its ETag is
            // empty.
            Index index = DefineTestIndex();
            Console.WriteLine(
                $"Test index hasn't been created yet, so its ETag should be blank. ETag: '{index.ETag}'");

            // Once the resource exists in Azure Search, its ETag will be populated. Make sure to use the object
            // returned by the SearchServiceClient! Otherwise, you will still have the old object with the
            // blank ETag.
            Console.WriteLine("Creating index...\n");
            index = serviceClient.Indexes.Create(index);

            Console.WriteLine($"Test index created; Its ETag should be populated. ETag: '{index.ETag}'");

            // ETags let you do some useful things you couldn't do otherwise. For example, by using an If-Match
            // condition, we can update an index using CreateOrUpdate and be guaranteed that the update will only
            // succeed if the index already exists.
            index.Fields.Add(new Field("name", AnalyzerName.EnMicrosoft));
            index =
                serviceClient.Indexes.CreateOrUpdate(
                    index,
                    accessCondition: AccessCondition.GenerateIfExistsCondition());

            Console.WriteLine(
                $"Test index updated; Its ETag should have changed since it was created. ETag: '{index.ETag}'");

            // More importantly, ETags protect you from concurrent updates to the same resource. If another
            // client tries to update the resource, it will fail as long as all clients are using the right
            // access conditions.
            Index indexForClient1 = index;
            Index indexForClient2 = serviceClient.Indexes.Get("test");

            Console.WriteLine("Simulating concurrent update. To start, both clients see the same ETag.");
            Console.WriteLine($"Client 1 ETag: '{indexForClient1.ETag}' Client 2 ETag: '{indexForClient2.ETag}'");

            // Client 1 successfully updates the index.
            indexForClient1.Fields.Add(new Field("a", DataType.Int32));
            indexForClient1 =
                serviceClient.Indexes.CreateOrUpdate(
                    indexForClient1,
                    accessCondition: AccessCondition.IfNotChanged(indexForClient1));

            Console.WriteLine($"Test index updated by client 1; ETag: '{indexForClient1.ETag}'");

            // Client 2 tries to update the index, but fails, thanks to the ETag check.
            try
            {
                indexForClient2.Fields.Add(new Field("b", DataType.Boolean));
                serviceClient.Indexes.CreateOrUpdate(
                    indexForClient2, 
                    accessCondition: AccessCondition.IfNotChanged(indexForClient2));

                Console.WriteLine("Whoops; This shouldn't happen");
                Environment.Exit(1);
            }
            catch (CloudException e) when (e.IsAccessConditionFailed())
            {
                Console.WriteLine("Client 2 failed to update the index, as expected.");
            }

            // You can also use access conditions with Delete operations. For example, you can implement an
            // atomic version of the DeleteTestIndexIfExists method from this sample like this:
            Console.WriteLine("Deleting index...\n");
            serviceClient.Indexes.Delete("test", accessCondition: AccessCondition.GenerateIfExistsCondition());

            // This is slightly better than using the Exists method since it makes only one round trip to
            // Azure Search instead of potentially two. It also avoids an extra Delete request in cases where
            // the resource is deleted concurrently, but this doesn't matter much since resource deletion in
            // Azure Search is idempotent.

            // And we're done! Bye!
            Console.WriteLine("Complete.  Press any key to end application...\n");
            Console.ReadKey();
        }

        private static SearchServiceClient CreateSearchServiceClient(IConfigurationRoot configuration)
        {
            string searchServiceName = configuration["SearchServiceName"];
            string adminApiKey = configuration["SearchServiceAdminApiKey"];

            SearchServiceClient serviceClient =
                new SearchServiceClient(searchServiceName, new SearchCredentials(adminApiKey));
            return serviceClient;
        }

        private static void DeleteTestIndexIfExists(SearchServiceClient serviceClient)
        {
            if (serviceClient.Indexes.Exists("test"))
            {
                serviceClient.Indexes.Delete("test");
            }
        }

        private static Index DefineTestIndex() =>
            new Index()
            {
                Name = "test",
                Fields = new[] { new Field("id", DataType.String) { IsKey = true } }
            };
    }
}

Schema progettualeDesign pattern

Uno schema progettuale per l'implementazione della concorrenza ottimistica deve includere un ciclo che recupera il controllo della condizione di accesso, un test per la condizione di accesso e, facoltativamente, una risorsa aggiornata prima di provare ad applicare di nuovo le modifiche.A design pattern for implementing optimistic concurrency should include a loop that retries the access condition check, a test for the access condition, and optionally retrieves an updated resource before attempting to re-apply the changes.

Questo frammento di codice illustra l'aggiunta di synonymMap a un indice già esistente.This code snippet illustrates the addition of a synonymMap to an index that already exists. Questo codice è tratto da Esercitazione sui sinonimi (anteprima) in C# per Ricerca di Azure.This code is from the Synonym (preview) C# tutorial for Azure Search.

Il frammento ottiene l'indice "hotels", controlla la versione dell'oggetto in un'operazione di aggiornamento, genera un'eccezione se la condizione ha esito negativo e quindi esegue un nuovo tentativo di operazione (fino a tre volte), iniziando con il recupero dell'indice dal server per ottenere la versione più recente.The snippet gets the "hotels" index, checks the object version on an update operation, throws an exception if the condition fails, and then retries the operation (up to three times), starting with index retrieval from the server to get the latest version.

    private static void EnableSynonymsInHotelsIndexSafely(SearchServiceClient serviceClient)
    {
        int MaxNumTries = 3;

        for (int i = 0; i < MaxNumTries; ++i)
        {
            try
            {
                Index index = serviceClient.Indexes.Get("hotels");
                index = AddSynonymMapsToFields(index);

                // The IfNotChanged condition ensures that the index is updated only if the ETags match.
                serviceClient.Indexes.CreateOrUpdate(index, accessCondition: AccessCondition.IfNotChanged(index));

                Console.WriteLine("Updated the index successfully.\n");
                break;
            }
            catch (CloudException e) when (e.IsAccessConditionFailed())
            {
                Console.WriteLine($"Index update failed : {e.Message}. Attempt({i}/{MaxNumTries}).\n");
            }
        }
    }

    private static Index AddSynonymMapsToFields(Index index)
    {
        index.Fields.First(f => f.Name == "category").SynonymMaps = new[] { "desc-synonymmap" };
        index.Fields.First(f => f.Name == "tags").SynonymMaps = new[] { "desc-synonymmap" };
        return index;
    }

Passaggi successiviNext steps

Per altre informazioni su come aggiornare in modo sicuro un indice esistente, vedere l'esempio in C# per i sinonimi.Review the synonyms C# sample for more context on how to safely update an existing index.

Provare a modificare uno dei due esempi seguenti per includere ETag o oggetti AccessCondition.Try modifying either of the following samples to include ETags or AccessCondition objects.

Vedere ancheSee also

Common HTTP request and response headers (Intestazioni della risposta e della richiesta HTTP comuni)Common HTTP request and response headers
HTTP status codes (Codici di stato HTTP) Index operations (REST API) (Operazioni sugli indici - API REST)HTTP status codes Index operations (REST API)