Freigeben über


Lernprogramm: Optimieren der Indizierung mithilfe der Push-API

Azure AI Search unterstützt zwei grundlegende Ansätze zum Importieren von Daten in einen Suchindex: Programmgesteuertes Übertragen ihrer Daten in den Index oder Abrufen ihrer Daten durch Zeigen eines Azure AI Search-Indexers auf eine unterstützte Datenquelle.

In diesem Tutorial erfahren Sie, wie Sie Daten effizient mithilfe des Pushmodells indizieren, indem Sie Anforderungen zu einem Batch zusammenfassen und eine Wiederholungsstrategie mit exponentiellem Backoff verwenden. Sie können die Beispielanwendung herunterladen und ausführen. In diesem Lernprogramm werden auch die wichtigsten Aspekte der Anwendung und die Faktoren erläutert, die beim Indizieren von Daten berücksichtigt werden müssen.

In diesem Lernprogramm verwenden Sie C# und die Azure.Search.Documents-Bibliothek aus dem Azure SDK für .NET zu:

  • Erstellen eines Index
  • Testen verschiedener Batchgrößen, um die effizienteste Größe zu ermitteln
  • Asynchrones Indizieren von Batches
  • Erhöhen der Indizierungsgeschwindigkeit durch Verwendung mehrerer Threads
  • Verwenden einer Wiederholungsstrategie mit exponentiellem Backoff, um die Indizierung für nicht erfolgreiche Dokumente zu wiederholen

Voraussetzungen

Herunterladen von Dateien

Der Quellcode für dieses Tutorial befindet sich im Ordner optimize-data-indexing/v11 im GitHub-Repository Azure-Samples/azure-search-dotnet-scale.

Wichtige Aspekte

Die folgenden Faktoren wirken sich auf die Indizierungsgeschwindigkeit aus. Weitere Informationen finden Sie unter Index large data sets.

  • Dienstebene und Anzahl von Partitionen/Replikaten: Das Hinzufügen von Partitionen oder das Upgraden Ihrer Ebene erhöht die Indizierungsgeschwindigkeit.
  • Komplexität des Indexschemas: Das Hinzufügen von Feldern und Feldeigenschaften verringert die Indizierungsgeschwindigkeit. Kleinere Indizes werden schneller indiziert.
  • Batchgröße: Die optimale Batchgröße hängt von Ihrem Indexschema und Dataset ab.
  • Anzahl von Threads/Workern: Ein einzelner Thread nutzt nicht die volle Indizierungsgeschwindigkeit.
  • Wiederholungsstrategie: Eine Wiederholungsstrategie mit exponentielle Backoff ist eine bewährte Methode für eine optimale Indizierung.
  • Datenübertragungsgeschwindigkeit im Netzwerk: Die Datenübertragungsgeschwindigkeit kann ein limitierender Faktor sein. Indizieren Sie Daten innerhalb Ihrer Azure-Umgebung, um die Datenübertragungsgeschwindigkeit zu erhöhen.

Erstellen eines Azure KI-Suche-Diensts

Dieses Lernprogramm erfordert einen Azure AI Search-Dienst, den Sie im Azure-Portal erstellen können. Sie können auch einen vorhandenen Dienst in Ihrem aktuellen Abonnement finden. Um die Indizierungsgeschwindigkeiten genau zu testen und zu optimieren, empfehlen wir, die gleiche Stufe zu verwenden, die Sie in der Produktion verwenden möchten.

In diesem Tutorial wird die schlüsselbasierte Authentifizierung verwendet. Kopieren Sie einen Administrator-API-Schlüssel, um ihn in die Datei appsettings.json einzufügen.

  1. Melden Sie sich beim Azure-Portal an. Kopieren Sie auf der Seite "Dienstübersicht " die Endpunkt-URL. Ein Beispiel für einen Endpunkt ist https://mydemo.search.windows.net.

  2. Gehen Sie zu Einstellungen>Schlüssel, um einen Administratorschlüssel für vollständige Rechte für den Dienst zu erhalten. Es gibt zwei austauschbare Administratorschlüssel, die zur Gewährleistung der Geschäftskontinuität bereitgestellt wurden, falls ein Schlüssel ausgetauscht werden muss. Für Anforderungen zum Hinzufügen, Ändern und Löschen von Objekten können Sie den primären oder den sekundären Schlüssel verwenden.

    Screenshot der Orte des HTTP-Endpunkts und des API-Schlüssels.

Richten Sie Ihre Umgebung ein

  1. Starten Sie Visual Studio, und öffnen Sie OptimizeDataIndexing.sln.

  2. Öffnen Sie im Projektmappen-Explorer die Datei appsettings.json, um Verbindungsinformationen Ihres Diensts anzugeben.

{
  "SearchServiceUri": "https://{service-name}.search.windows.net",
  "SearchServiceAdminApiKey": "",
  "SearchIndexName": "optimize-indexing"
}

Erkunden des Codes

Nachdem Sie appsettings.jsonaktualisiert haben, sollte das Beispielprogramm in OptimizeDataIndexing.sln zum Erstellen und Ausführen bereit sein.

Dieser Code wird vom C#-Abschnitt der Schnellstartanleitung abgeleitet: Volltextsuche mithilfe der Azure-SDKs, die detaillierte Informationen zu den Grundlagen der Arbeit mit dem .NET SDK bereitstellt.

Diese einfache C#/.NET-Konsolen-App führt folgende Aufgaben aus:

  • Dies erstellt einen neuen Index basierend auf der Datenstruktur der C#-Klasse Hotel (die auch auf die Klasse Address verweist)
  • Sie testet verschiedene Batchgrößen, um die effizienteste Größe zu ermitteln.
  • Daten werden asynchron indexiert.
    • Unter Verwendung mehrerer Threads zur Erhöhung der Indizierungsgeschwindigkeit
    • Verwenden einer Wiederholungsstrategie mit exponentiellem Backoff, um nicht erfolgreiche Elemente zu wiederholen

Nehmen Sie sich vor dem Ausführen des Programms etwas Zeit, um sich den Code sowie die Indexdefinitionen für dieses Beispiel etwas genauer anzusehen. Der relevante Code befindet sich in verschiedenen Dateien:

  • Hotel.cs und Address.cs enthalten das Schema, das den Index definiert
  • DataGenerator.cs enthält eine einfache Klasse, um die Erstellung großer Mengen an Hoteldaten zu vereinfachen.
  • ExponentialBackoff.cs enthält Code zur Optimierung des Indizierungsprozesses, wie in diesem Artikel beschrieben
  • Program.cs enthält Funktionen zum Erstellen und Löschen des Azure KI Search-Index, zum Indizieren von Datenbatches sowie zum Testen verschiedener Batchgrößen.

Erstellen des Index

In diesem Beispielprogramm wird das Azure SDK für .NET verwendet, um einen Azure KI Search-Index zu definieren und zu erstellen. Das SDK nutzt die Klasse FieldBuilder, um eine Indexstruktur auf der Grundlage einer C#-Datenmodellklasse zu generieren.

Das Datenmodell wird durch die Klasse Hotel definiert, die auch Verweise auf die Klasse Address enthält. FieldBuilder führt ein Drilldown durch die verschiedenen Klassendefinitionen aus, um eine komplexe Datenstruktur für den Index zu generieren. Mithilfe von Metadatentags werden die Attribute der einzelnen Felder definiert, um beispielsweise anzugeben, ob das Feld durchsuchbar oder sortierbar ist.

Die folgenden Codeausschnitte aus der datei Hotel.cs geben ein einzelnes Feld und einen Verweis auf eine andere Datenmodellklasse an.

. . .
[SearchableField(IsSortable = true)]
public string HotelName { get; set; }
. . .
public Address Address { get; set; }
. . .

In der Datei Program.cs wird der Index mit einem Namen und einer Feldsammlung definiert, die durch die Methode FieldBuilder.Build(typeof(Hotel)) generiert wird, und anschließend erstellt:

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);
}

Daten generieren

In der Datei DataGenerator.cs wird eine einfache Klasse implementiert, um Testdaten zu generieren. Der Zweck dieser Klasse besteht darin, eine große Anzahl von Dokumenten mit einer eindeutigen ID für die Indizierung zu generieren.

Um eine Liste von 100.000 Hotels mit eindeutigen IDs zu erhalten, führen Sie den folgenden Code aus:

long numDocuments = 100000;
DataGenerator dg = new DataGenerator();
List<Hotel> hotels = dg.GetHotels(numDocuments, "large");

Zu Testzwecken stehen in diesem Beispiel zwei Hotelgrößen zur Verfügung: small (klein) und large (groß).

Das Schema Ihres Indexes wirkt sich auf die Indizierungsgeschwindigkeit aus. Nachdem Sie dieses Lernprogramm abgeschlossen haben, sollten Sie diese Klasse konvertieren, um Daten zu generieren, die ihrem beabsichtigten Indexschema am besten entsprechen.

Testbatchgrößen

Um einzelne oder mehrere Dokumente in einen Index zu laden, unterstützt Azure AI Search die folgenden APIs:

Das Indizieren von Dokumenten in Batches verbessert die Indizierungsleistung erheblich. Diese Batches können bis zu 1.000 Dokumente oder bis zu ca. 16 MB pro Batch sein.

Die Bestimmung der optimalen Batchgröße für Ihre Daten ist ein wichtiger Faktor bei der Optimierung der Indizierungsgeschwindigkeit. Die optimale Batchgröße wird hauptsächlich durch die beiden folgenden Faktoren beeinflusst:

  • Schema Ihres Indexes
  • Datengröße

Da die optimale Batchgröße von Ihrem Index und Ihren Daten abhängt, besteht der beste Ansatz darin, verschiedene Batchgrößen zu testen, um zu bestimmen, welche Ergebnisse die schnellsten Indizierungsgeschwindigkeiten für Ihr Szenario ergeben.

Die folgende Funktion veranschaulicht einen einfachen Ansatz zum Testen von Batchgrößen:

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

Da nicht alle Dokumente die gleiche Größe haben (auch wenn dies in diesem Beispiel der Fall ist), schätzen wir die Größe der Daten, die wir an den Suchdienst senden. Dazu können Sie die folgende Funktion verwenden, die das Objekt zuerst in JSON konvertiert und dann seine Größe in Byte bestimmt. Dadurch können wir die effizientesten Batchgrößen im Hinblick auf die Indizierungsgeschwindigkeit (MB/s) ermitteln.

// 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;
}

Für die Funktion sind ein Element vom Typ SearchClient sowie die Anzahl von Versuchen erforderlich, die für die einzelnen Batchgrößen getestet werden sollen. Da die Indizierungszeiten bei den einzelnen Batches variieren können, testen Sie jeden Batch standardmäßig dreimal, um die Ergebnisse statistisch relevanter zu machen.

await TestBatchSizesAsync(searchClient, numTries: 3);

Wenn Sie die Funktion ausführen, sollten Sie in der Konsole eine Ausgabe sehen, die dem folgenden Beispiel ähnelt:

Screenshot der Ausgabe der Funktion „Testen der Batchgröße“.

Ermitteln Sie, welche Batchgröße am effizientesten ist, und verwenden Sie diese Batchgröße im nächsten Schritt dieses Lernprogramms. Unter Umständen ist bei verschiedenen Batchgrößen eine Stabilisierung auf einem bestimmten Niveau in MB/s zu beobachten.

Indizierung der Daten

Nachdem Sie nun die zu verwendenden Batchgröße identifiziert haben, die Sie verwenden wollen, besteht der nächste Schritt darin, mit der Indizierung der Daten zu beginnen. In diesem Beispiel werden folgende Schritte unternommen, um Daten effizient zu indizieren:

  • Verwendet mehrere Threads/Worker
  • Implementiert eine Wiederholungsstrategie mit exponentiellem Backoff

Heben Sie die Auskommentierung der Zeilen 41 bis 49 auf, und führen Sie dann das Programm erneut aus. Bei dieser Ausführung generiert und sendet das Beispiel Batches von Dokumenten, bis zu 100.000, wenn Sie den Code ausführen, ohne die Parameter zu ändern.

Verwenden Sie mehrere Threads/Arbeiter

Um die Indizierungsgeschwindigkeiten von Azure AI Search zu nutzen, verwenden Sie mehrere Threads, um Batchindizierungsanforderungen gleichzeitig an den Dienst zu senden.

Mehrere der wichtigsten Überlegungen können sich auf die optimale Anzahl von Threads auswirken. Sie können dieses Beispiel ändern und mit einer anderen Threadanzahl testen, um die optimale Threadanzahl für Ihr Szenario zu ermitteln. Solange Sie jedoch mehrere Threads parallel ausführen, sollten Sie in der Lage sein, von einem Großteil der Effizienzsteigerungen zu profitieren.

Wenn Sie die Anfragen an den Suchdienst erhöhen, werden möglicherweise HTTP-Statuscodes angezeigt, die darauf hinweisen, dass die Anfrage nicht erfolgreich war. Zwei gängige HTTP-Statuscodes im Zusammenhang mit der Indizierung sind:

  • 503 Dienst nicht verfügbar: Dieser Fehler bedeutet, dass die Auslastung des Systems sehr hoch ist und Ihre Anforderung aktuell nicht verarbeitet werden kann.
  • 207 Multi-Status: Dieser Fehler bedeutet, dass der Vorgang für einige Dokumente erfolgreich war, bei mindestens einem Dokument aber ein Fehler aufgetreten ist.

Implementieren einer Wiederholungsstrategie mit exponentiellem Backoff

Wenn ein Fehler auftritt, müssen Sie Anforderungen mithilfe einer exponentiellen Backoff-Wiederholungsstrategie wiederholen.

Das .NET SDK von Azure AI Search wiederholt automatisch 503s und andere fehlgeschlagene Anforderungen, Aber Sie sollten Ihre eigene Logik implementieren, um 207s erneut zu versuchen. Open-Source-Tools wie Polly können in einer Wiederholungsstrategie nützlich sein.

In diesem Beispiel wird eine eigene Wiederholungsstrategie mit exponentiellem Backoff implementiert. Zunächst definieren wir einige Variablen, einschließlich der maxRetryAttempts und der Initiale delay für eine fehlgeschlagene Anforderung.

// 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;

Die Ergebnisse des Indizierungsvorgangs werden in der Variable IndexDocumentResult result gespeichert. Mit dieser Variablen können Sie überprüfen, ob Dokumente im Batch fehlgeschlagen sind, wie im folgenden Beispiel gezeigt. Bei partiellen Fehlern wird anhand der IDs der fehlerhaften Dokumente ein neues Batch erstellt.

RequestFailedException Ausnahmen sollten ebenfalls abgefangen werden, da sie darauf hinweisen, dass die Anforderung vollständig fehlgeschlagen ist und erneut überprüft wird.

// 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);

Schließen Sie von hier aus den Code für das exponentielle Backoff in eine Funktion ein, damit er mühelos aufgerufen werden kann.

Anschließend wird eine weitere Funktion erstellt, um die aktiven Threads zu verwalten. Der Einfachheit halber ist diese Funktion hier nicht enthalten. Sie finden sie aber in ExponentialBackoff.cs. Sie können die Funktion mit dem folgenden Befehl aufrufen. Dabei hotels handelt es sich um die Daten, die wir hochladen möchten, 1000 um die Batchgröße und 8 die Anzahl der gleichzeitigen Threads.

await ExponentialBackoff.IndexData(indexClient, hotels, 1000, 8);

Wenn Sie die Funktion ausführen, sollte eine Ausgabe ähnlich dem folgenden Beispiel angezeigt werden:

Screenshot, der die Ausgabe einer Funktion „Indizieren von Daten“ zeigt.

Wenn eine Reihe von Dokumenten fehlschlägt, wird ein Fehler ausgegeben, der den Fehler angibt und dass der Batch erneut versucht wird.

[Batch starting at doc 6000 had partial failure]
[Retrying 560 failed documents]

Nach Abschluss der Ausführung der Funktion können Sie überprüfen, ob alle Dokumente dem Index hinzugefügt wurden.

Erkunden des Indexes

Nachdem die Ausführung des Programms abgeschlossen ist, können Sie den ausgefüllten Suchindex entweder programmgesteuert oder mit dem Such-Explorer im Azure-Portal erkunden.

Programmgesteuert

Bei der Überprüfung der Dokumentanzahl in einem Index stehen Ihnen hauptsächlich zwei Optionen zur Verfügung: die API zum Zählen der Dokumente und die API zum Abrufen der Indexstatistik. Beide Pfade erfordern Zeit zum Verarbeiten, daher sollten Sie nicht alarmiert werden, wenn die Anzahl der zurückgegebenen Dokumente anfänglich niedriger ist als erwartet.

Dokumentenanzahl

Der Vorgang "Dokumente zählen" ruft die Anzahl der Dokumente in einem Suchindex ab.

long indexDocCount = await searchClient.GetDocumentCountAsync();

Indexstatistiken abrufen

Mit dem Vorgang zum Abrufen der Indexstatistik werden Informationen zur Dokumentanzahl für den aktuellen Index sowie zur Speichernutzung zurückgegeben. Die Aktualisierung der Indexstatistik dauert länger als die der Dokumentanzahl.

var indexStats = await indexClient.GetIndexStatisticsAsync(indexName);

Azure-Portal

Suchen Sie im Azure-Portal im linken Bereich den Optimierindex in der Liste " Indizes ".

Screenshot, der eine Liste der Azure KI-Suche-Indizes zeigt.

Die Dokumentanzahl und die Speichergröße basieren auf der API zum Abrufen von Indexstatistiken und können mehrere Minuten dauern, bis sie aktualisiert werden.

Zurücksetzen und erneut ausführen

In den frühen experimentellen Phasen der Entwicklung ist es am praktischsten, die Objekt aus Azure KI-Suche zu löschen und von Ihrem Code neu erstellen zu lassen. Ressourcennamen sind eindeutig. Wenn Sie ein Objekt löschen, können Sie es unter dem gleichen Namen neu erstellen.

Im Beispielcode dieses Tutorials wird eine Überprüfung auf bereits vorhandene Indizes durchgeführt. Diese werden gelöscht, damit Sie Ihren Code erneut ausführen können.

Die Indizes können auch über das Azure-Portal gelöscht werden.

Bereinigen von Ressourcen

Wenn Sie in Ihrem eigenen Abonnement arbeiten, ist es ratsam, nach Abschluss eines Projekts die nicht mehr benötigten Ressourcen zu entfernen. Ressourcen, die weiterhin ausgeführt werden, können Sie Geld kosten. Sie können einzelne Ressourcen oder die gesamte Ressourcengruppe mit allen darin enthaltenen Ressourcen löschen.

Sie können Ressourcen im Azure-Portal über den Link Alle Ressourcen oder Ressourcengruppen im linken Navigationsbereich suchen und verwalten.

Nächster Schritt

Weitere Informationen zum Indizieren großer Mengendaten finden Sie im folgenden Lernprogramm: