Správa souběžnosti ve službě Azure AI Search
Při správě prostředků Azure AI Search, jako jsou indexy a zdroje dat, je důležité bezpečně aktualizovat prostředky, zejména pokud se k prostředkům přistupuje souběžně různými komponentami vaší aplikace. Pokud dva klienti současně aktualizují prostředek bez koordinace, jsou možné podmínky časování. Aby tomu zabránilo, Azure AI Search používá model optimistické souběžnosti. Prostředek nemá žádné zámky. Místo toho existuje značka ETag pro každý prostředek, který identifikuje verzi prostředku, abyste mohli formulovat požadavky, které zabrání náhodnému přepsání.
Jak to funguje
Optimistická souběžnost se implementuje prostřednictvím kontrol podmínek přístupu v rozhraní API volání zápisu do indexů, indexerů, zdrojů dat, sad dovedností a prostředků synonymMap.
Všechny prostředky mají značku entity (ETag), která poskytuje informace o verzi objektu. Nejprve kontrolou značky ETag se můžete vyhnout souběžným aktualizacím v typickém pracovním postupu (získat, upravit místně, aktualizovat) tím, že zajistíte, aby značky ETag prostředku odpovídaly vaší místní kopii.
Rozhraní REST API používá v hlavičce požadavku značku ETag .
Sada Azure SDK pro .NET nastaví značku ETag prostřednictvím objektu accessCondition a nastaví if-match | Hlavička If-Match-None v prostředku. Objekty, které používají značky ETag, jako jsou SynonymMap.ETag a SearchIndex.ETag, mají objekt accessCondition.
Při každé aktualizaci prostředku se její značky ETag automaticky změní. Když implementujete správu souběžnosti, vše, co děláte, spočívá v předpokladu žádosti o aktualizaci, která vyžaduje, aby vzdálený prostředek měl stejnou značku ETag jako kopie prostředku, který jste upravili v klientovi. Pokud jiný proces změní vzdálený prostředek, značka ETag neodpovídá předpokladu a požadavek selže s HTTP 412. Pokud používáte sadu .NET SDK, zobrazí se tato chyba jako výjimka, kdy IsAccessConditionFailed()
metoda rozšíření vrátí hodnotu true.
Poznámka:
Existuje pouze jeden mechanismus pro souběžnost. Vždy se používá bez ohledu na to, které rozhraní API nebo sada SDK se používají pro aktualizace prostředků.
Příklad
Následující kód ukazuje optimistickou souběžnost operace aktualizace. Selže druhá aktualizace, protože značky ETag objektu se změní předchozí aktualizací. Konkrétně platí, že pokud se značky ETag v hlavičce požadavku už nebudou shodovat se značkou ETag objektu, vrátí vyhledávací služba stavový kód 400 (chybný požadavek) a aktualizace selže.
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 } });
}
}
Návrhový vzor
Vzor návrhu pro implementaci optimistické souběžnosti by měl obsahovat smyčku, která opakuje kontrolu podmínky přístupu, test podmínky přístupu a volitelně načte aktualizovaný prostředek před pokusem o opětovné použití změn.
Tento fragment kódu znázorňuje přidání synonymMap k indexu, který již existuje.
Fragment kódu získá index "hotels", zkontroluje verzi objektu v operaci aktualizace, vyvolá výjimku, pokud podmínka selže, a pak opakuje operaci (až třikrát), počínaje načtením indexu ze serveru získat nejnovější verzi.
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;
}