Zelfstudie: indexering optimaliseren met 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 wijzen naar een Azure AI Search-indexeerfunctie op een ondersteunde gegevensbron om de gegevens op te halen .

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

Als u geen Azure-abonnement hebt, maakt u een gratis account voordat u begint.

Vereisten

De volgende services en hulpprogramma's zijn vereist voor deze zelfstudie.

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-samples .

Belangrijkste overwegingen

Factoren die van invloed zijn op de indexeringssnelheden worden hierna vermeld. Meer informatie vindt u in Grote gegevenssets indexeren.

  • 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 naar gelang uw indexschema en gegevensset.
  • Aantal threads/werkrollen : één thread profiteert niet optimaal van indexeringssnelheden.
  • Strategie voor opnieuw proberen: een strategie voor exponentieel uitstel is een best practice voor optimale indexering.
  • Snelheden van overdracht van netwerkgegevens: snelheden van gegevensoverdracht kunnen een beperkende factor zijn. Indexeer gegevens vanuit uw Azure-omgeving om de snelheid van gegevensoverdracht te verhogen.

1 - Azure AI-Search-service maken

Voor het voltooien van deze zelfstudie hebt u een Azure AI-Search-service nodig, die u in de portal kunt maken. U kunt het beste dezelfde laag gebruiken die u in productie wilt gebruiken, zodat u de indexeringssnelheden nauwkeurig kunt testen en optimaliseren.

In deze zelfstudie wordt gebruikgemaakt van verificatie op basis van sleutels. Kopieer een beheer-API-sleutel om in het appsettings.json-bestand te plakken.

  1. Meld u aan bij Azure Portal en haal op de pagina Overzicht van de zoekservice de URL op. Een eindpunt ziet er bijvoorbeeld uit als https://mydemo.search.windows.net.

  2. 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.

    Get an HTTP endpoint and access key

2 - De omgeving instellen

  1. Start Visual Studio en open OptimizeDataIndexing.sln.
  2. Open appsettings.json in Solution Explorer om verbindingsgegevens op te geven.
{
  "SearchServiceUri": "https://{service-name}.search.windows.net",
  "SearchServiceAdminApiKey": "",
  "SearchIndexName": "optimize-indexing"
}

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:

  • Maakt een nieuwe index op basis van de gegevensstructuur van de C#-klasse Hotel (die ook verwijst naar de klasse Address).
  • 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 bevatten het schema dat de index definieert
  • 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 klasse Hotel, die ook verwijzingen bevat naar de klasse Address. 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.

4 - Batchgroottes testen

Azure AI Search ondersteunt de volgende API's om één of meerdere documenten in een index te laden:

Als u documenten in batches indexeert, worden de prestaties van de indexering aanzienlijk verbeterd. Deze batches kunnen maximaal duizend documenten of maximaal circa 16 MB per batch bevatten.

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. Dit wordt gedaan met behulp van de onderstaande functie. Hiermee wordt het object eerst naar JSON geconverteerd en wordt vervolgens de grootte in bytes vastgesteld. 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 de indexeringstijden voor elke batch, proberen we elke batch standaard drie keer om de resultaten statistisch significanter te maken.

await TestBatchSizesAsync(searchClient, numTries: 3);

Wanneer u de functie uitvoert, ziet u in de console een uitvoer zoals hieronder weergegeven:

Output of test batch size function

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.

5 - Gegevens indexeren

Nu de batchgrootte die we willen gebruiken, is vastgesteld, gaan we in de volgende stap de gegevens indexeren. Om gegevens efficiënt te kunnen indexeren, geldt voor dit voorbeeld dat het:

  • Gebruikmaakt van meerdere threads/werkrollen.
  • Een herhaalstrategie voor exponentieel uitstel implementeert.

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 belast wordt en uw aanvraag op dit moment niet kan worden verwerkt.
  • 207: meerdere statussen: deze fout betekent dat sommige documenten zijn geslaagd, maar dat er 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 hieronder wordt weergegeven. 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);

Hierna verpakken we de code voor exponentieel uitstel 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 zoals hieronder weergegeven:

Output of index data function

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.

6 - Index onderzoeken

U kunt de ingevulde zoekindex verkennen nadat het programma programmatisch is uitgevoerd of de Search Explorer in de portal gebruiken.

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. Het bijwerken van de indexstatistieken duurt langer dan dat van de telling van de documenten.

var indexStats = await indexClient.GetIndexStatisticsAsync(indexName);

Azure Portal

Zoek in Azure Portal vanuit het linkernavigatiedeelvenster de indexeringsindex optimaliseren in de lijst met indexen.

List of Azure AI Search indexes

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 de 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 de portal via de koppeling Alle resources of Resourcegroepen in het navigatiedeelvenster aan de linkerkant.

Volgende stappen

Raadpleeg de volgende zelfstudie voor meer informatie over het indexeren van grote hoeveelheden gegevens.