Zelfstudie: Indexering optimaliseren met behulp van de push-API
Azure AI Search ondersteunt twee basismethoden voor het importeren van gegevens in een zoekindex: push uw gegevens programmatisch naar de index of haal de gegevens op door een Azure AI Search-indexeerfunctie naar een ondersteunde gegevensbron te verwijzen.
In deze zelfstudie wordt uitgelegd hoe u efficiënt gegevens indexeert met behulp van het pushmodel door aanvragen te batcheren en een strategie voor exponentieel uitstel te gebruiken. U kunt de voorbeeldtoepassing downloaden en uitvoeren. In dit artikel worden de belangrijkste aspecten van de toepassing uitgelegd en welke factoren u moet overwegen bij het indexeren van gegevens.
In deze zelfstudie worden C# en de Azure.Search.Documents-bibliotheek van de Azure SDK voor .NET gebruikt om de volgende taken uit te voeren:
- Een index maken
- Verschillende batchgrootten testen om de meest efficiënte grootte te bepalen
- Batches asynchroon indexeren
- Meerdere threads gebruiken om de indexeringssnelheden te verhogen
- Een herhaalstrategie voor exponentiële uitstel gebruiken om mislukte documenten opnieuw uit te voeren
Vereisten
De volgende services en hulpprogramma's zijn vereist voor deze zelfstudie.
Een Azure-abonnement. Als u nog geen account hebt, kunt u een gratis account aanmaken.
Visual Studio, een willekeurige editie. Voorbeeldcode en instructies zijn getest met de gratis Community-editie.
Bestanden downloaden
De broncode voor deze zelfstudie bevindt zich in de map optimize-data-indexing/v11 in de GitHub-opslagplaats azure-Samples/azure-search-dotnet-scale .
Belangrijkste overwegingen
Factoren die van invloed zijn op de indexeringssnelheden, worden hierna weergegeven. Zie Grote gegevenssets indexeren voor meer informatie.
- Servicelaag en aantal partities/replica's: Het toevoegen van partities of het upgraden van uw laag verhoogt de indexeringssnelheden.
- Complexiteit van indexschema: door velden en veldeigenschappen toe te voegen, worden de indexeringssnelheden verlaagd. Kleinere indexen zijn sneller te indexeren.
- Batchgrootte: De optimale batchgrootte varieert op basis van uw indexschema en gegevensset.
- Aantal threads/werkrollen: een enkele thread profiteert niet optimaal van indexeringssnelheden.
- Strategie voor opnieuw proberen: Een strategie voor exponentieel uitstel is een best practice voor optimale indexering.
- Snelheid van netwerkgegevensoverdracht: gegevensoverdrachtsnelheden kunnen een beperkende factor zijn. Indexeer gegevens vanuit uw Azure-omgeving om de snelheid van gegevensoverdracht te verhogen.
Stap 1: Een Azure AI-Search-service maken
Als u deze zelfstudie wilt voltooien, hebt u een Azure AI-Search-service nodig, die u in Azure Portal kunt maken of een bestaande service kunt vinden onder uw huidige abonnement. U kunt het beste dezelfde laag gebruiken die u in productie wilt gebruiken, zodat u de indexeringssnelheden nauwkeurig kunt testen en optimaliseren.
Een beheerderssleutel en URL voor Azure AI Search ophalen
In deze zelfstudie wordt gebruikgemaakt van verificatie op basis van sleutels. Kopieer een beheer-API-sleutel om in het appsettings.json-bestand te plakken.
Meld u aan bij het Azure-portaal. Haal de eindpunt-URL op via de overzichtspagina van uw zoekservice. Een eindpunt ziet er bijvoorbeeld uit als
https://mydemo.search.windows.net
.Haal onder Instellingen>Sleutels een beheersleutel op voor volledige rechten op de service. Er zijn twee uitwisselbare beheersleutels die voor bedrijfscontinuïteit worden verstrekt voor het geval u een moet overschakelen. U kunt de primaire of secundaire sleutel gebruiken op aanvragen voor het toevoegen, wijzigen en verwijderen van objecten.
Stap 2: Uw omgeving instellen
Start Visual Studio en open OptimizeDataIndexing.sln.
Open appsettings.json in Solution Explorer om de verbindingsgegevens van uw service op te geven.
{
"SearchServiceUri": "https://{service-name}.search.windows.net",
"SearchServiceAdminApiKey": "",
"SearchIndexName": "optimize-indexing"
}
Stap 3: De code verkennen
Zodra u appsettings.json hebt bijgewerkt, moet het voorbeeldprogramma in OptimizeDataIndexing.sln klaar zijn om te bouwen en uit te voeren.
Deze code is afgeleid van de sectie C# van quickstart: Zoeken in volledige tekst met behulp van de Azure SDK's. Meer uitgebreide informatie over de basisbeginselen van het werken met de .NET SDK vindt u in dat artikel.
Deze eenvoudige C#-/.NET-console-app voert de volgende taken uit:
- Hiermee maakt u een nieuwe index op basis van de gegevensstructuur van de C#
Hotel
-klasse (die ook verwijst naar deAddress
klasse) - Test verschillende batchgrootten om de meest efficiënte grootte te bepalen
- Indexeert gegevens asynchroon
- Gebruik van meerdere threads om de indexeringssnelheden te verhogen
- Gebruik van een herhaalstrategie voor exponentieel uitstel om mislukte items opnieuw uit te voeren
Neem, voordat u het programma gaat uitvoeren, even een minuut de tijd om de code en de indexdefinities voor dit voorbeeld te bestuderen. De relevante code bevindt zich in twee bestanden:
- Hotel.cs en Address.cs het schema bevatten waarmee de index wordt gedefinieerd
- DataGenerator.cs bevat een eenvoudige klasse waarmee eenvoudig grote hoeveelheden hotelgegevens kunnen worden gemaakt
- ExponentialBackoff.cs bevat code voor het optimaliseren van het indexeringsproces, zoals beschreven in dit artikel
- Program.cs bevat functies die de Azure AI Search-index maken en verwijderen, batches met gegevens indexeren en verschillende batchgrootten testen
De index maken
In dit voorbeeldprogramma wordt de Azure SDK voor .NET gebruikt om een Azure AI Search-index te definiëren en te maken. Het maakt gebruik van de FieldBuilder
klasse om een indexstructuur te genereren op basis van een C#-gegevensmodelklasse.
Het gegevensmodel wordt gedefinieerd door de Hotel
klasse, die ook verwijzingen naar de Address
klasse bevat. Met FieldBuilder wordt ingezoomd op meerdere klassedefinities om een complexe gegevensstructuur voor de index te genereren. Tags voor metagegevens worden gebruikt voor het definiëren van de kenmerken van elk veld; er kan bijvoorbeeld worden aangegeven of een veld doorzoekbaar of sorteerbaar is.
De volgende fragmenten uit het bestand Hotel.cs laten zien hoe een enkel veld en een verwijzing naar een andere gegevensmodelklasse kunnen worden opgegeven.
. . .
[SearchableField(IsSortable = true)]
public string HotelName { get; set; }
. . .
public Address Address { get; set; }
. . .
In het bestand Program.cs wordt de index gedefinieerd met een naam en een veldverzameling die wordt gegenereerd door de methode FieldBuilder.Build(typeof(Hotel))
. De index wordt daarna als volgt gemaakt:
private static async Task CreateIndexAsync(string indexName, SearchIndexClient indexClient)
{
// Create a new search index structure that matches the properties of the Hotel class.
// The Address class is referenced from the Hotel class. The FieldBuilder
// will enumerate these to create a complex data structure for the index.
FieldBuilder builder = new FieldBuilder();
var definition = new SearchIndex(indexName, builder.Build(typeof(Hotel)));
await indexClient.CreateIndexAsync(definition);
}
Gegevens genereren
Er wordt een eenvoudige klasse geïmplementeerd in het bestand DataGenerator.cs om gegevens te genereren voor het testen. Het enige doel van deze klasse is om op eenvoudige wijze een groot aantal documenten te genereren met een unieke id voor indexering.
Voer de volgende coderegels uit om een lijst met 100.000 hotels met unieke id's op te halen:
long numDocuments = 100000;
DataGenerator dg = new DataGenerator();
List<Hotel> hotels = dg.GetHotels(numDocuments, "large");
Er zijn hotels in twee formaten beschikbaar die in dit voorbeeld kunnen worden getest: klein en groot.
Het schema van uw index heeft een effect op indexeringssnelheden. Daarom is het zinvol om deze klasse te converteren om gegevens te genereren die het beste overeenkomen met uw beoogde indexschema nadat u deze zelfstudie hebt uitgevoerd.
Stap 4: Batchgrootten testen
Azure AI Search ondersteunt de volgende API's om één of meerdere documenten in een index te laden:
Het indexeren van documenten in batches verbetert de indexeringsprestaties aanzienlijk. Deze batches kunnen maximaal 1000 documenten of maximaal 16 MB per batch zijn.
Het vaststellen van de optimale batchgrootte voor uw gegevens is een belangrijk onderdeel van voor het optimaliseren van de indexeringssnelheid. De twee primaire factoren die van invloed zijn op de optimale batchgrootte zijn:
- Het schema van uw index
- De hoeveelheid gegevens
Omdat de optimale batchgrootte afhankelijk is van uw index en uw gegevens, kunt u het best verschillende batchgroottes testen om te bepalen wat de snelste indexeringssnelheden voor uw scenario zijn.
Met de volgende functie wordt een eenvoudige benadering van het testen van batchgroottes gedemonstreerd.
public static async Task TestBatchSizesAsync(SearchClient searchClient, int min = 100, int max = 1000, int step = 100, int numTries = 3)
{
DataGenerator dg = new DataGenerator();
Console.WriteLine("Batch Size \t Size in MB \t MB / Doc \t Time (ms) \t MB / Second");
for (int numDocs = min; numDocs <= max; numDocs += step)
{
List<TimeSpan> durations = new List<TimeSpan>();
double sizeInMb = 0.0;
for (int x = 0; x < numTries; x++)
{
List<Hotel> hotels = dg.GetHotels(numDocs, "large");
DateTime startTime = DateTime.Now;
await UploadDocumentsAsync(searchClient, hotels).ConfigureAwait(false);
DateTime endTime = DateTime.Now;
durations.Add(endTime - startTime);
sizeInMb = EstimateObjectSize(hotels);
}
var avgDuration = durations.Average(timeSpan => timeSpan.TotalMilliseconds);
var avgDurationInSeconds = avgDuration / 1000;
var mbPerSecond = sizeInMb / avgDurationInSeconds;
Console.WriteLine("{0} \t\t {1} \t\t {2} \t\t {3} \t {4}", numDocs, Math.Round(sizeInMb, 3), Math.Round(sizeInMb / numDocs, 3), Math.Round(avgDuration, 3), Math.Round(mbPerSecond, 3));
// Pausing 2 seconds to let the search service catch its breath
Thread.Sleep(2000);
}
Console.WriteLine();
}
Omdat niet alle documenten dezelfde grootte hebben (hoewel ze dit in dit voorbeeld wel zijn), wordt een schatting gemaakt van de grootte van de gegevens die we naar de zoekservice verzenden. U kunt dit doen met behulp van de volgende functie die het object eerst converteert naar json en vervolgens de grootte ervan in bytes bepaalt. Met deze techniek kunnen we bepalen welke batchgroottes het meest efficiënt zijn in termen van MB's/indexeringssnelheden.
// Returns size of object in MB
public static double EstimateObjectSize(object data)
{
// converting object to byte[] to determine the size of the data
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
byte[] Array;
// converting data to json for more accurate sizing
var json = JsonSerializer.Serialize(data);
bf.Serialize(ms, json);
Array = ms.ToArray();
// converting from bytes to megabytes
double sizeInMb = (double)Array.Length / 1000000;
return sizeInMb;
}
De functie vereist een SearchClient
plus het aantal pogingen dat u wilt testen voor elke batchgrootte. Omdat er mogelijk variabiliteit is in indexeringstijden voor elke batch, kunt u elke batch drie keer proberen om de resultaten statistisch significanter te maken.
await TestBatchSizesAsync(searchClient, numTries: 3);
Wanneer u de functie uitvoert, ziet u een uitvoer in uw console, zoals in het volgende voorbeeld:
Bepaal welke batchgrootte het efficiëntst is en gebruik deze batchgrootte in de volgende stap van de zelfstudie. Mogelijk ziet u een plateau in MB/s in verschillende batchgrootten.
Stap 5: De gegevens indexeren
Nu u de batchgrootte hebt geïdentificeerd die u wilt gebruiken, is de volgende stap het indexeren van de gegevens. Om gegevens efficiënt te kunnen indexeren, geldt voor dit voorbeeld dat het:
- maakt gebruik van meerdere threads/werkrollen
- implementeert een strategie voor exponentieel uitstel voor opnieuw proberen
Verwijder opmerkingen 41 tot en met 49 en voer het programma opnieuw uit. Tijdens deze uitvoering genereert en verzendt het voorbeeld batches documenten, tot 100.000 als u de code uitvoert zonder de parameters te wijzigen.
Meerdere threads/werkrollen gebruiken
Als u optimaal gebruik wilt maken van de indexeringssnelheden van Azure AI Search, gebruikt u meerdere threads om aanvragen voor batchindexering gelijktijdig naar de service te verzenden.
Een aantal van de belangrijkste overwegingen die eerder zijn vermeld, kan van invloed zijn op het optimale aantal threads. U kunt dit voorbeeld wijzigen en testen met verschillende aantallen threads om het optimale aantal voor uw scenario te bepalen. Als u echter meerdere threads gelijktijdig uitvoert, profiteert u van de meeste verbeteringen in de efficiëntie.
Wanneer u de aanvragen voor de zoekservice opvoert, kunt u HTTP-statuscodes tegenkomen die aangeven dat de aanvraag niet volledig is geslaagd. Twee veelvoorkomende HTTP-statuscodes die zich tijdens het indexeren kunnen voordoen, zijn:
- 503 Service niet beschikbaar: deze fout betekent dat het systeem zwaar wordt belast en uw aanvraag op dit moment niet kan worden verwerkt.
- 207 Multi-Status: Deze fout betekent dat sommige documenten zijn geslaagd, maar ten minste één is mislukt.
Een herhaalstrategie voor exponentieel uitstel implementeren
Als er een fout optreedt, moeten aanvragen opnieuw worden ingediend met een herhaalstrategie voor exponentieel uitstel.
De .NET SDK van Azure AI Search probeert automatisch 503's en andere mislukte aanvragen opnieuw, maar u moet uw eigen logica implementeren om 207s opnieuw te proberen. Opensource-hulpprogramma's zoals Polly kunnen handig zijn in een strategie voor opnieuw proberen.
In dit voor beeld implementeren we onze eigen herhaalstrategie voor exponentieel uitstel. We beginnen met het definiëren van enkele variabelen, waaronder de maxRetryAttempts
en de eerste delay
voor een mislukte aanvraag:
// Create batch of documents for indexing
var batch = IndexDocumentsBatch.Upload(hotels);
// Create an object to hold the result
IndexDocumentsResult result = null;
// Define parameters for exponential backoff
int attempts = 0;
TimeSpan delay = delay = TimeSpan.FromSeconds(2);
int maxRetryAttempts = 5;
De resultaten van de indexeringsbewerking worden opgeslagen in de variabele IndexDocumentResult result
. Deze variabele is belangrijk omdat u hiermee kunt controleren of documenten in de batch zijn mislukt, zoals wordt weergegeven in het volgende voorbeeld. Als er een gedeeltelijke fout optreedt, wordt er een nieuwe batch gemaakt op basis van de id van de mislukte documenten.
RequestFailedException
-uitzonderingen moeten ook worden afgevangen: deze geven aan dat de aanvraag volledig is mislukt en ook opnieuw moet worden geprobeerd.
// Implement exponential backoff
do
{
try
{
attempts++;
result = await searchClient.IndexDocumentsAsync(batch).ConfigureAwait(false);
var failedDocuments = result.Results.Where(r => r.Succeeded != true).ToList();
// handle partial failure
if (failedDocuments.Count > 0)
{
if (attempts == maxRetryAttempts)
{
Console.WriteLine("[MAX RETRIES HIT] - Giving up on the batch starting at {0}", id);
break;
}
else
{
Console.WriteLine("[Batch starting at doc {0} had partial failure]", id);
Console.WriteLine("[Retrying {0} failed documents] \n", failedDocuments.Count);
// creating a batch of failed documents to retry
var failedDocumentKeys = failedDocuments.Select(doc => doc.Key).ToList();
hotels = hotels.Where(h => failedDocumentKeys.Contains(h.HotelId)).ToList();
batch = IndexDocumentsBatch.Upload(hotels);
Task.Delay(delay).Wait();
delay = delay * 2;
continue;
}
}
return result;
}
catch (RequestFailedException ex)
{
Console.WriteLine("[Batch starting at doc {0} failed]", id);
Console.WriteLine("[Retrying entire batch] \n");
if (attempts == maxRetryAttempts)
{
Console.WriteLine("[MAX RETRIES HIT] - Giving up on the batch starting at {0}", id);
break;
}
Task.Delay(delay).Wait();
delay = delay * 2;
}
} while (true);
Van hieruit verpakt u de exponentiële uitstelcode in een functie, zodat deze eenvoudig kan worden aangeroepen.
Vervolgens wordt er een andere functie gemaakt om de actieve threads te beheren. Voor het gemak is deze functie hier niet opgenomen, maar u kunt deze vinden in ExponentialBackoff.cs. De functie kan worden aangeroepen met de volgende opdracht, waarbij hotels
de gegevens zijn die we willen uploaden, 1000
de batchgrootte is en 8
het aantal gelijktijdige threads:
await ExponentialBackoff.IndexData(indexClient, hotels, 1000, 8);
Wanneer u de functie uitvoert, ziet u een uitvoer:
Wanneer een batch met documenten mislukt, wordt een fout afgedrukt die de storing aangeeft en tevens de melding dat de batch opnieuw wordt uitgevoerd:
[Batch starting at doc 6000 had partial failure]
[Retrying 560 failed documents]
Nadat de functie is uitgevoerd, kunt u controleren of alle documenten aan de index zijn toegevoegd.
Stap 6: De index verkennen
U kunt de gevulde zoekindex verkennen nadat het programma programmatisch is uitgevoerd of met behulp van Search Explorer in Azure Portal.
Programmatisch
Er zijn twee hoofdopties waarmee het aantal documenten in een index kan worden gecontroleerd: de API voor het tellen van documenten en de API voor het ophalen van de indexstatistieken. Beide paden vereisen tijd om te verwerken, dus wees niet alarmerend als het aantal geretourneerde documenten in eerste instantie lager is dan verwacht.
Documenten tellen
Met de bewerking Documenten tellen wordt een telling van het aantal documenten in een zoekindex opgehaald:
long indexDocCount = await searchClient.GetDocumentCountAsync();
Indexstatistieken ophalen
Met de bewerking Indexstatistieken ophalen wordt een telling van de documenten voor de huidige index geretourneerd plus de gebruikte hoeveelheid opslag. Indexstatistieken duren langer dan het aantal documenten om bij te werken.
var indexStats = await indexClient.GetIndexStatisticsAsync(indexName);
Azure Portal
Zoek in Azure Portal vanuit het linkernavigatiedeelvenster de indexeringsindex optimaliseren in de lijst Met indexen .
Het aantal documenten en de opslaggrootte zijn gebaseerd op de API Indexstatistieken ophalen en kan enkele minuten duren voordat deze is bijgewerkt.
Opnieuw instellen en uitvoeren
In de vroege experimentele ontwikkelingsfasen is de meest praktische benadering voor het ontwerpen van iteratie het verwijderen van de objecten uit Azure AI Search en het mogelijk maken van uw code om ze opnieuw te bouwen. Resourcenamen zijn uniek. Na het verwijderen van een object kunt u het opnieuw maken met dezelfde naam.
In de voorbeeldcode voor deze zelfstudie wordt gecontroleerd op bestaande indexen en worden deze verwijderd zodat u de code opnieuw kunt uitvoeren.
U kunt azure Portal ook gebruiken om indexen te verwijderen.
Resources opschonen
Wanneer u in uw eigen abonnement werkt, is het een goed idee om aan het einde van een project te bepalen of u de gemaakte resources nog steeds nodig hebt en of u deze moet verwijderen. Resources die actief blijven, kunnen u geld kosten. U kunt resources afzonderlijk verwijderen, maar u kunt ook de resourcegroep verwijderen als u de volledige resourceset wilt verwijderen.
U kunt resources vinden en beheren in Azure Portal met behulp van de koppeling Alle resources of resourcegroepen in het linkernavigatiedeelvenster.
Volgende stap
Raadpleeg de volgende zelfstudie voor meer informatie over het indexeren van grote hoeveelheden gegevens.