So beschleunigen Sie eine Abfrage mit dem Partitionsschlüssel im Azure-Tabellenspeicher


10

Wie erhöhen wir die Geschwindigkeit dieser Abfrage?

Wir haben ungefähr 100 Verbraucher innerhalb der Zeitspanne, in der 1-2 minutesdie folgende Abfrage ausgeführt wird. Jeder dieser Läufe repräsentiert einen Lauf einer Verbrauchsfunktion.

        TableQuery<T> treanslationsQuery = new TableQuery<T>()
         .Where(
          TableQuery.CombineFilters(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
           , TableOperators.Or,
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
          )
         );

Diese Abfrage liefert ungefähr 5000 Ergebnisse.

Vollständiger Code:

    public static async Task<IEnumerable<T>> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query) where T : ITableEntity, new()
    {
        var items = new List<T>();
        TableContinuationToken token = null;

        do
        {
            TableQuerySegment<T> seg = await table.ExecuteQuerySegmentedAsync(query, token);
            token = seg.ContinuationToken;
            items.AddRange(seg);
        } while (token != null);

        return items;
    }

    public static IEnumerable<Translation> Get<T>(string sourceParty, string destinationParty, string wildcardSourceParty, string tableName) where T : ITableEntity, new()
    {
        var acc = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("conn"));
        var tableClient = acc.CreateCloudTableClient();
        var table = tableClient.GetTableReference(Environment.GetEnvironmentVariable("TableCache"));
        var sourceDestinationPartitionKey = $"{sourceParty.ToLowerTrim()}-{destinationParty.ToLowerTrim()}";
        var anySourceDestinationPartitionKey = $"{wildcardSourceParty}-{destinationParty.ToLowerTrim()}";

        TableQuery<T> treanslationsQuery = new TableQuery<T>()
         .Where(
          TableQuery.CombineFilters(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
           , TableOperators.Or,
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
          )
         );

        var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
        return over1000Results.Where(x => x.expireAt > DateTime.Now)
                           .Where(x => x.effectiveAt < DateTime.Now);
    }

Während dieser Ausführungen werden bei 100 Verbrauchern, wie Sie sehen können, die Anforderungen gruppiert und bilden Spitzen:

Geben Sie hier die Bildbeschreibung ein

Während dieser Spitzen dauern die Anfragen oft länger als 1 Minute:

Geben Sie hier die Bildbeschreibung ein

Wie erhöhen wir die Geschwindigkeit dieser Abfrage?


5000 Ergebnisse scheinen in der Abfrage nicht annähernd genug zu filtern. Das Übertragen von 5000 Ergebnissen in den Code kostet eine Menge Netzwerkzeit. Es ist egal, dass Sie danach noch filtern werden. | Führen Sie immer so viel Filterung einer Verarbeitung in der Abfrage durch. Idealerweise für Zeilen, die einen Index haben und / oder das Ergebnis einer berechneten Ansicht sind.
Christopher

Sind diese "Übersetzungs" -Objekte groß? Warum holst du nicht gerne einige der Parameter, anstatt wie die ganze Datenbank zu werden?
Hirasawa Yui

@ HirasawaYui nein, sie sind klein
l - '' '' '-' '' '' '' '' '' '' ''

Sie sollten mehr filtern, 5000 Ergebnisse zu ziehen scheint bedeutungslos. Es ist unmöglich zu sagen, ohne Ihre Daten zu kennen, aber ich würde sagen, Sie müssten einen Weg finden, um sie sinnvoller zu partitionieren oder eine Art Filterung in die Abfrage
einzuführen

Wie viele verschiedene Partitionen gibt es?
Peter Bons

Antworten:


3
  var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
        return over1000Results.Where(x => x.expireAt > DateTime.Now)
                           .Where(x => x.effectiveAt < DateTime.Now);

Hier ist eines der Probleme: Sie führen die Abfrage aus und filtern sie dann mit diesen "Wheres" aus dem Speicher. Verschieben Sie die Filter auf, bevor die Abfrage ausgeführt wird, was sehr hilfreich sein sollte.

Zweitens müssen Sie eine bestimmte Anzahl von Zeilen angeben, die aus der Datenbank abgerufen werden sollen


das machte keinen Unterschied
l - '' '' '-' '' '' '' '' '' ''

3

Es gibt 3 Dinge, die Sie berücksichtigen können:

1 . WhereEntfernen Sie zunächst Ihre Klauseln, die Sie für das Abfrageergebnis ausführen. Es ist besser, Klauseln so weit wie möglich in die Abfrage aufzunehmen (noch besser, wenn Sie Indizes in Ihren Tabellen haben, die diese ebenfalls enthalten). Im Moment können Sie Ihre Abfrage wie folgt ändern:

var translationsQuery = new TableQuery<T>()
.Where(TableQuery.CombineFilters(
TableQuery.CombineFilters(
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey),
    TableOperators.Or,
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
    ),
TableOperators.And,
TableQuery.CombineFilters(
    TableQuery.GenerateFilterConditionForDate("affectiveAt", QueryComparisons.LessThan, DateTime.Now),
    TableOperators.And,
    TableQuery.GenerateFilterConditionForDate("expireAt", QueryComparisons.GreaterThan, DateTime.Now))
));

Da Sie eine große Datenmenge abrufen müssen, ist es besser, Ihre Abfragen parallel auszuführen. Sie sollten also die do whileLoop-Inside- ExecuteQueryAsyncMethode durch eine Methode ersetzen, Parallel.ForEachdie auf Stephen Toub Parallel.While basiert . Auf diese Weise wird die Ausführungszeit für Abfragen verkürzt. Dies ist eine gute Wahl, da Sie diese entfernen können, Resultwenn Sie diese Methode aufrufen. Es gibt jedoch eine kleine Einschränkung, dass ich nach diesem Teil des Codes darüber sprechen werde:

public static IEnumerable<T> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query) where T : ITableEntity, new()
{
    var items = new List<T>();
    TableContinuationToken token = null;

    Parallel.ForEach(new InfinitePartitioner(), (ignored, loopState) =>
    {
        TableQuerySegment<T> seg = table.ExecuteQuerySegmented(query, token);
        token = seg.ContinuationToken;
        items.AddRange(seg);

        if (token == null) // It's better to change this constraint by looking at https://www.vivien-chevallier.com/Articles/executing-an-async-query-with-azure-table-storage-and-retrieve-all-the-results-in-a-single-operation
            loopState.Stop();
    });

    return items;
}

Und dann können Sie es in Ihrer GetMethode aufrufen :

return table.ExecuteQueryAsync(translationsQuery).Cast<Translation>();

Wie Sie sehen können, ist die Methode itselft nicht asynchron (Sie sollten ihren Namen ändern) und Parallel.ForEachnicht mit der Übergabe einer asynchronen Methode kompatibel. Deshalb habe ich ExecuteQuerySegmentedstattdessen verwendet. Um die Leistung zu steigern und alle Vorteile der asynchronen Methode zu nutzen, können Sie die obige ForEachSchleife durch die ActionBlockMethode im Datenfluss oder die ParallelForEachAsyncErweiterungsmethode aus dem AsyncEnumerator Nuget-Paket ersetzen .

2. Es ist eine gute Wahl, unabhängige parallele Abfragen auszuführen und die Ergebnisse dann zusammenzuführen, selbst wenn die Leistungsverbesserung höchstens 10 Prozent beträgt. Dies gibt Ihnen Zeit, um die leistungsstärkste Abfrage zu finden. Aber nie vergessen , alle Ihre Einschränkungen darin aufzunehmen und testen beiden Richtungen zu wissen , was man besser Suiten Ihr Problem.

3 . Ich bin mir nicht sicher, ob es ein guter Vorschlag ist oder nicht, aber mach es und sieh dir die Ergebnisse an. Wie in MSDN beschrieben :

Der Tabellendienst erzwingt Server-Timeouts wie folgt:

  • Abfragevorgänge: Während des Zeitlimitintervalls kann eine Abfrage maximal fünf Sekunden lang ausgeführt werden. Wenn die Abfrage nicht innerhalb des Fünf-Sekunden-Intervalls abgeschlossen wird, enthält die Antwort Fortsetzungstoken zum Abrufen verbleibender Elemente bei einer nachfolgenden Anforderung. Weitere Informationen finden Sie unter Abfragezeitlimit und Paginierung.

  • Einfügen, Aktualisieren und Löschen von Vorgängen: Das maximale Zeitlimit beträgt 30 Sekunden. 30 Sekunden ist auch das Standardintervall für alle Einfüge-, Aktualisierungs- und Löschvorgänge.

Wenn Sie ein Zeitlimit angeben, das unter dem Standardzeitlimit des Dienstes liegt, wird Ihr Zeitlimitintervall verwendet.

Sie können also mit Timeout spielen und prüfen, ob es Leistungsverbesserungen gibt.


2

Leider führt die folgende Abfrage einen vollständigen Tabellenscan ein :

    TableQuery<T> treanslationsQuery = new TableQuery<T>()
     .Where(
      TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
       , TableOperators.Or,
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
      )
     );

Sie sollten es in zwei Partitionsschlüsselfilter aufteilen und separat abfragen, wodurch zwei Partitionsscans erstellt werden und eine effizientere Leistung erzielt wird.


sahen wir vielleicht eine 10% ige Verbesserung mit diesem, aber es ist nicht genug
l --''''''--------- ‚‘ ‚‘ ‚‘ ‚‘ ‚‘ ‚‘

1

Das Geheimnis liegt also nicht nur im Code, sondern auch beim Einrichten Ihrer Azure-Speichertabellen.

a) Eine der wichtigsten Optionen zur Optimierung Ihrer Abfragen in Azure ist die Einführung des Caching. Dies reduziert Ihre Gesamtreaktionszeiten drastisch und vermeidet so Engpässe während der von Ihnen genannten Spitzenzeit.

b) Wenn Sie Entitäten aus Azure abfragen, können Sie dies am schnellsten sowohl mit dem PartitionKey als auch mit dem RowKey tun. Dies sind die einzigen indizierten Felder im Tabellenspeicher, und jede Abfrage, die beide verwendet, wird innerhalb weniger Millisekunden zurückgegeben. Stellen Sie also sicher, dass Sie sowohl PartitionKey als auch RowKey verwenden.

Weitere Details finden Sie hier: https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

Hoffe das hilft.


-1

Hinweis: Dies ist eine allgemeine Empfehlung zur Optimierung von DB-Abfragen.

Es ist möglich, dass der ORM etwas Dummes tut. Bei Optimierungen ist es in Ordnung, eine Abstraktionsschicht herunterzufahren. Ich schlage daher vor, die Abfrage in der Abfragesprache (SQL?) Umzuschreiben, damit Sie leichter sehen können, was gerade passiert, und auch einfacher optimieren können.

Der Schlüssel zur Optimierung der Suche ist das Sortieren! Das Sortieren einer Tabelle ist normalerweise viel billiger als das Scannen der gesamten Tabelle bei jeder Abfrage! Wenn möglich, sortieren Sie die Tabelle nach dem in der Abfrage verwendeten Schlüssel. In den meisten Datenbanklösungen wird dies durch Erstellen eines Indexschlüssels erreicht.

Eine andere Strategie, die bei wenigen Kombinationen gut funktioniert, besteht darin, jede Abfrage als separate (temporär im Speicher befindliche) Tabelle zu haben, die immer auf dem neuesten Stand ist. Wenn also etwas eingefügt wird, wird es auch in die "Ansicht" -Tabellen "eingefügt". Einige Datenbanklösungen nennen dies "Ansichten".

Eine brutalere Strategie besteht darin, schreibgeschützte Replikate zu erstellen, um die Last zu verteilen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.