jak przyspieszyć zapytanie za pomocą klucza partycji w magazynie tabel lazur


10

Jak zwiększyć szybkość tego zapytania?

Mamy około 100 klientów w ciągu 1-2 minuteswykonywania następującego zapytania. Każdy z tych przebiegów reprezentuje 1 przebieg funkcji zużycia.

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

To zapytanie da około 5000 wyników.

Pełny kod:

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

Podczas tych egzekucji, gdy jest 100 klientów, widać, że żądania będą się grupować i tworzyć skoki:

wprowadź opis zdjęcia tutaj

Podczas tych skoków żądania często zajmują 1 minutę:

wprowadź opis zdjęcia tutaj

Jak zwiększyć szybkość tego zapytania?


5000 wyników wydaje się, że nie filtrujesz prawie wystarczająco w zapytaniu. Samo przesłanie 5000 wyników do kodu będzie kosztowało mnóstwo czasu w sieci. Nieważne, że nadal będziesz filtrować. | Zawsze wykonuj tyle samo filtrowania przetwarzania w zapytaniu. Idealnie w wierszach, które mają indeks i / lub są wynikiem obliczonego widoku.
Christopher

Czy te obiekty „tłumaczenia” są duże? Dlaczego nie chcesz dostać niektórych parametrów zamiast gettin` jak cały db?
Hirasawa Yui,

@HirasawaYui no one are small
l - '' ''----------------' '' '' ''

powinieneś zrobić więcej filtrowania, wyciągnięcie 5000 wyników wydaje się bez znaczenia. nie można powiedzieć bez znajomości swoich danych, ale powiedziałbym, że musisz wymyślić sposób na podzielenie go na
partie

Ile jest różnych partycji?
Peter Bons,

Odpowiedzi:


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

Oto jeden z problemów: uruchamiasz zapytanie, a następnie filtrujesz je z pamięci za pomocą tych „wheres”. Przenieś filtry na przed uruchomieniem zapytania, co powinno bardzo pomóc.

Po drugie, musisz podać limit wierszy do pobrania z bazy danych


to nie miało znaczenia
l - '' '' ----------- '' „” „

3

Można rozważyć 3 rzeczy:

1 . Przede wszystkim pozbądź się Whereklauzul, które wykonujesz na wyniku zapytania. Lepiej włączać klauzule do zapytania tak często, jak to możliwe (nawet lepiej, jeśli masz jakieś indeksy w swoich tabelach, również je uwzględniają). Na razie możesz zmienić zapytanie w następujący sposób:

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

Ponieważ masz do pobrania dużą ilość danych, lepiej równolegle uruchamiać zapytania. Powinieneś więc zamienić metodę do whilepętli wewnątrz na napisaną przeze mnie na podstawie Stephen Toub Parallel.While ; W ten sposób skróci czas wykonywania zapytania. Jest to dobry wybór, ponieważ możesz usunąć, kiedy wykonujesz wywołanie za pomocą tej metody, ale ma małe ograniczenie, że powiem o tym po tej części kodu:ExecuteQueryAsyncParallel.ForEachResult

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

Następnie możesz wywołać to w swojej Getmetodzie:

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

Jak widać, sama metoda nie jest asynchroniczna (należy zmienić jej nazwę) i Parallel.ForEachnie jest kompatybilna z przekazywaniem metody asynchronicznej. Właśnie dlatego użyłem ExecuteQuerySegmentedzamiast tego. Aby jednak zwiększyć wydajność i wykorzystać wszystkie zalety metody asynchronicznej, można zastąpić powyższą ForEachpętlę ActionBlockmetodą w przepływie danych lub ParallelForEachAsyncmetodą rozszerzenia z pakietu AsyncEnumerator Nuget .

2. Dobrym wyborem jest wykonywanie niezależnych równoległych zapytań, a następnie łączenie wyników, nawet jeśli jego poprawa wydajności wynosi co najwyżej 10 procent. To daje czas na znalezienie najlepszego zapytania przyjaznego wydajności. Ale nigdy nie zapominaj o uwzględnieniu wszystkich swoich ograniczeń i przetestuj oba sposoby, aby dowiedzieć się, który z nich najlepiej pasuje do Twojego problemu.

3 . Nie jestem pewien, czy to dobra sugestia, czy nie, ale zrób to i zobacz wyniki. Jak opisano w MSDN :

Usługa tabel wymusza limity czasu serwera w następujący sposób:

  • Operacje na zapytaniach: w trakcie limitu czasu zapytanie może być wykonywane maksymalnie przez pięć sekund. Jeśli zapytanie nie zakończy się w ciągu pięciu sekund, odpowiedź zawiera tokeny kontynuacji do pobrania pozostałych elementów na kolejne żądanie. Aby uzyskać więcej informacji, zobacz Limit czasu zapytania i podział na strony.

  • Wstawianie, aktualizowanie i usuwanie operacji: Maksymalny limit czasu wynosi 30 sekund. Trzydzieści sekund to również domyślny interwał dla wszystkich operacji wstawiania, aktualizacji i usuwania.

Jeśli określisz limit czasu, który jest krótszy niż domyślny limit czasu usługi, zostanie użyty interwał limitu czasu.

Możesz więc grać z limitem czasu i sprawdzać, czy nastąpiła poprawa wydajności.


2

Niestety poniższe zapytanie wprowadza pełny skan tabeli :

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

Powinieneś podzielić go na dwa filtry klucza partycji i przesłać je osobno, które staną się dwoma skanami partycji i będą działać wydajniej.


widzieliśmy może 10% poprawę dzięki temu, ale to nie wystarczy
ja - ”-” - - ”„ ”„

1

Sekret tkwi więc nie tylko w kodzie, ale także podczas konfigurowania tabel magazynu Azure.

a) Jedną z najważniejszych opcji optymalizacji zapytań na platformie Azure jest wprowadzenie buforowania. To drastycznie skróci ogólny czas reakcji, a tym samym pozwoli uniknąć wąskiego gardła podczas wspomnianej godziny szczytu.

b) Ponadto podczas wysyłania zapytań do jednostek poza platformą Azure najszybszym możliwym sposobem jest użycie zarówno PartitionKey, jak i RowKey. Są to jedyne pola indeksowane w Table Storage, a każde zapytanie wykorzystujące oba z nich zostanie zwrócone w ciągu kilku milisekund. Upewnij się więc, że używasz zarówno PartitionKey, jak i RowKey.

Zobacz więcej szczegółów tutaj: https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

Mam nadzieję że to pomoże.


-1

Uwaga: jest to ogólna porada dotycząca optymalizacji zapytań DB.

Możliwe, że ORM robi coś głupiego. Podczas optymalizacji można zejść z warstwy abstrakcji. Proponuję więc przepisać zapytanie w języku zapytań (SQL?), Aby łatwiej było zobaczyć, co się dzieje, a także zoptymalizować.

Kluczem do optymalizacji wyszukiwania jest sortowanie! Utrzymywanie sortowania tabeli jest zwykle znacznie tańsze w porównaniu do skanowania całej tabeli przy każdym zapytaniu! Jeśli to możliwe, tabelę należy posortować według klucza użytego w zapytaniu. W większości rozwiązań bazodanowych osiąga się to poprzez utworzenie klucza indeksu.

Inną strategią, która działa dobrze, jeśli jest kilka kombinacji, jest umieszczenie każdego zapytania w osobnej (tymczasowej w pamięci) tabeli, która jest zawsze aktualna. Kiedy coś jest wstawiane, jest ono również „wstawiane” do tabel „widok”. Niektóre rozwiązania baz danych nazywają to „widokami”.

Bardziej brutalną strategią jest tworzenie replik tylko do odczytu w celu rozłożenia obciążenia.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.