Delen via


Gelijktijdigheid beheren in Azure AI Search

Bij het beheren van Azure AI Search-resources, zoals indexen en gegevensbronnen, is het belangrijk om resources veilig bij te werken, met name als resources gelijktijdig worden geopend door verschillende onderdelen van uw toepassing. Wanneer twee clients een resource gelijktijdig bijwerken zonder coördinatie, zijn racevoorwaarden mogelijk. Om dit te voorkomen, maakt Azure AI Search gebruik van een optimistisch gelijktijdigheidsmodel. Er zijn geen vergrendelingen op een resource. In plaats daarvan is er een ETag voor elke resource die de resourceversie identificeert, zodat u aanvragen kunt formuleren die onbedoeld overschrijven voorkomen.

Hoe het werkt

Optimistische gelijktijdigheid wordt geïmplementeerd via toegangsvoorwaardecontroles in API-aanroepen die worden geschreven naar indexen, indexeerfuncties, gegevensbronnen, vaardighedensets en synonymMap-resources.

Alle resources hebben een entiteitstag (ETag) die informatie over objectversies biedt. Door eerst de ETag te controleren, kunt u gelijktijdige updates in een typische werkstroom voorkomen (lokaal wijzigen, bijwerken) door ervoor te zorgen dat de ETag van de resource overeenkomt met uw lokale kopie.

Telkens wanneer u een resource bijwerkt, wordt de ETag automatisch gewijzigd. Wanneer u gelijktijdigheidsbeheer implementeert, plaatst u alleen een voorwaarde voor de updateaanvraag waarvoor de externe resource dezelfde ETag moet hebben als de kopie van de resource die u hebt gewijzigd op de client. Als een ander proces de externe resource wijzigt, komt de ETag niet overeen met de voorwaarde en mislukt de aanvraag met HTTP 412. Als u de .NET SDK gebruikt, wordt deze fout weergegeven als een uitzondering waarbij de IsAccessConditionFailed() extensiemethode waar retourneert.

Notitie

Er is slechts één mechanisme voor gelijktijdigheid. Deze wordt altijd gebruikt, ongeacht welke API of SDK wordt gebruikt voor resource-updates.

Opmerking

De volgende code demonstreert optimistische gelijktijdigheid voor een updatebewerking. De tweede update mislukt omdat de ETag van het object wordt gewijzigd door een eerdere update. Wanneer de ETag in de aanvraagheader niet meer overeenkomt met de ETag van het object, retourneert de zoekservice een statuscode van 400 (ongeldige aanvraag) en mislukt de update.

using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using System;
using System.Net;
using System.Threading.Tasks;

namespace AzureSearch.SDKHowTo
{
    class Program
    {
        // This sample shows how ETags work by performing conditional updates and deletes
        // on an Azure Search index.
        static void Main(string[] args)
        {
            string serviceName = "PLACEHOLDER FOR YOUR SEARCH SERVICE NAME";
            string apiKey = "PLACEHOLDER FOR YOUR SEARCH SERVICE ADMIN API KEY";

            // Create a SearchIndexClient to send create/delete index commands
            Uri serviceEndpoint = new Uri($"https://{serviceName}.search.windows.net/");
            AzureKeyCredential credential = new AzureKeyCredential(apiKey);
            SearchIndexClient adminClient = new SearchIndexClient(serviceEndpoint, credential);

            // Delete index if it exists
            Console.WriteLine("Check for index and delete if it already exists...\n");
            DeleteTestIndexIfExists(adminClient);

            // 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.
            SearchIndex index = DefineTestIndex();

            Console.WriteLine(
                $"Test searchIndex hasn't been created yet, so its ETag should be blank. ETag: '{index.ETag}'");

            // Once the resource exists in Azure Search, its ETag is populated. Make sure to use the object
            // returned by the SearchIndexClient. Otherwise, you will still have the old object with the
            // blank ETag.
            Console.WriteLine("Creating index...\n");
            index = adminClient.CreateIndex(index);
            Console.WriteLine($"Test index created; Its ETag should be populated. ETag: '{index.ETag}'");


            // ETags prevent 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.
            SearchIndex indexForClientA = index;
            SearchIndex indexForClientB = adminClient.GetIndex("test-idx");

            Console.WriteLine("Simulating concurrent update. To start, clients A and B see the same ETag.");
            Console.WriteLine($"ClientA ETag: '{indexForClientA.ETag}' ClientB ETag: '{indexForClientB.ETag}'");

            // indexForClientA successfully updates the index.
            indexForClientA.Fields.Add(new SearchField("a", SearchFieldDataType.Int32));
            indexForClientA = adminClient.CreateOrUpdateIndex(indexForClientA);

            Console.WriteLine($"Client A updates test-idx by adding a new field. The new ETag for test-idx is: '{indexForClientA.ETag}'");

            // indexForClientB tries to update the index, but fails due to the ETag check.
            try
            {
                indexForClientB.Fields.Add(new SearchField("b", SearchFieldDataType.Boolean));
                adminClient.CreateOrUpdateIndex(indexForClientB);

                Console.WriteLine("Whoops; This shouldn't happen");
                Environment.Exit(1);
            }
            catch (RequestFailedException e) when (e.Status == 400)
            {
                Console.WriteLine("Client B failed to update the index, as expected.");
            }

            // Uncomment the next line to remove test-idx
            //adminClient.DeleteIndex("test-idx");
            Console.WriteLine("Complete.  Press any key to end application...\n");
            Console.ReadKey();
        }


        private static void DeleteTestIndexIfExists(SearchIndexClient adminClient)
        {
            try
            {
                if (adminClient.GetIndex("test-idx") != null)
                {
                    adminClient.DeleteIndex("test-idx");
                }
            }
            catch (RequestFailedException e) when (e.Status == 404)
            {
                //if an exception occurred and status is "Not Found", this is working as expected
                Console.WriteLine("Failed to find index and this is because it's not there.");
            }
        }

        private static SearchIndex DefineTestIndex() =>
            new SearchIndex("test-idx", new[] { new SearchField("id", SearchFieldDataType.String) { IsKey = true } });
    }
}

Ontwerppatroon

Een ontwerppatroon voor het implementeren van optimistische gelijktijdigheid moet een lus bevatten die de controle van de toegangsvoorwaarde opnieuw probeert uit te voeren, een test voor de toegangsvoorwaarde en eventueel een bijgewerkte resource ophaalt voordat u de wijzigingen opnieuw probeert toe te passen.

Dit codefragment illustreert de toevoeging van een synoniemmap aan een index die al bestaat.

Het codefragment haalt de index 'hotels' op, controleert de objectversie op een updatebewerking, genereert een uitzondering als de voorwaarde mislukt en probeert vervolgens de bewerking (maximaal drie keer) opnieuw uit te voeren, te beginnen met het ophalen van de index vanaf de server om de nieuwste versie op te halen.

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 (Exception 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;
}

Zie ook