як пришвидшити запит за допомогою розділової ключа в сховищі таблиць блакитного кольору


10

Як ми збільшуємо швидкість цього запиту?

У нас приблизно 100 споживачів в межах 1-2 minutesвиконання наступного запиту. Кожен з цих циклів представляє 1 запуск функції споживання.

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

Цей запит дасть приблизно 5000 результатів.

Повний код:

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

Під час цих виконання, коли є 100 споживачів, як ви бачите, запити кластеризуються та утворюють шипи:

введіть тут опис зображення

Під час цих шипів запити часто займають більше 1 хвилини:

введіть тут опис зображення

Як ми збільшуємо швидкість цього запиту?


5000 результатів здається, що ви не фільтруєте майже в запиті. Просто перенесення 5000 результатів до коду коштуватиме тонни мережі. Не забувайте, що після цього ви все ще збираєтеся фільтрувати. | Завжди виконуйте стільки файлових процесів, які обробляються в запиті. В ідеалі для рядків, які отримали індекс та / або є результатом обчисленого перегляду.
Крістофер

Ці об’єкти "Перекладу" великі? Чому вам не подобається отримувати деякі параметри, а не отримувати ", як весь db?
Hirasawa Yui

@HirasawaYui ні вони маленькі
l --'''''--------- '' '' '' '' '' '

вам слід зробити більше фільтрування, витягнення 5000 результатів здається безглуздим. сказати це неможливо, не знаючи ваших даних, але я б сказав, що вам потрібно буде розібратися в способі їх розділення більш осмисленим способом або ввести якесь фільтрування у запиті
4c74356b41

Скільки існує різних перегородок?
Пітер Бонс

Відповіді:


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

Ось одна з проблем: ви запускаєте запит, а потім фільтруєте його з пам’яті за допомогою цих «хедерів». Перемістіть фільтри до запуску запиту, який повинен сильно допомогти.

По-друге, ви повинні надати деякий ліміт рядків для отримання з бази даних


це не
змінило

3

Ви можете врахувати 3 речі:

1 . Перш за все, позбудьтеся від своїх Whereпропозицій, які ви виконуєте за результатами запиту. Краще якомога більше включити до запиту запити (ще краще, якщо у вас є якісь індекси на ваших таблицях). Наразі ви можете змінити запит, як показано нижче:

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

Оскільки у вас є велика кількість даних для отримання, то краще запускати запити паралельно. Отже, вам слід замінити метод do whileциклу внутрішньої версії на я написав на основі Стівена Туба Паралельного . Таким чином це скоротить час виконання запитів. Це хороший вибір, оскільки ви можете видалити, коли ви телефонуєте за цим методом, але це має невелике обмеження, що я розповім про це після цієї частини коду: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;
}

І тоді ви можете назвати це у своєму Getметоді:

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

Як ви бачите, метод itselft не є асинхронним (слід змінити ім'я) і Parallel.ForEachне сумісний з передачею в метод асинхронізації. Ось чому я використовував ExecuteQuerySegmentedзамість цього. Але, щоб зробити його більш ефективним та використати всі переваги асинхронного методу, ви можете замінити вищевказаний ForEachцикл ActionBlockметодом у Dataflow або ParallelForEachAsyncметодом розширення з пакету AsyncEnumerator Nuget .

2. Це хороший вибір для виконання незалежних паралельних запитів, а потім об'єднання результатів, навіть якщо підвищення його продуктивності становить щонайменше 10 відсотків. Це дає вам час, щоб можна було знайти найкращий запит щодо ефективності. Але, ніколи не забудьте включити в нього всі свої обмеження і перевірити обидва способи дізнатися, який із них краще відповідає вашій проблемі.

3 . Я не впевнений, що це гарна пропозиція чи ні, але зробіть це і подивіться результати. Як описано в MSDN :

Послуга «Таблиця» застосовує тайм-аути сервера таким чином:

  • Операції з запитом: протягом інтервалу тайм-ауту запит може виконуватись не більше п'яти секунд. Якщо запит не завершиться протягом п'ятисекундного інтервалу, відповідь включає маркери продовження для отримання решти елементів на наступний запит. Додаткову інформацію див. У розділі "Час очікування запиту" та "Пагинація".

  • Встановлення, оновлення та видалення операцій: Максимальний інтервал очікування - 30 секунд. Тридцять секунд - це також інтервал за замовчуванням для всіх операцій вставки, оновлення та видалення.

Якщо ви вказали тайм-аут, менший за час замовчування служби за замовчуванням, буде використаний інтервал очікування.

Таким чином, ви можете грати з тайм-аутом і перевірити, чи є якісь покращення продуктивності.


2

На жаль, нижче за запитом вводиться повне сканування таблиці :

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

Ви повинні розділити його на два фільтри Partition Key і запитувати їх окремо, що стане двома скануваннями розділів і ефективніше.


ми побачили, можливо, 10% поліпшення цього, але цього недостатньо
l - '' '''--------- '' '' '' '' '' '

1

Тому секрет полягає не лише в коді, але і в налаштуванні ваших таблиць зберігання Azure.

a) Одним із найважливіших варіантів оптимізації ваших запитів у Azure є введення кешування. Це різко скоротить загальний час реакції і тим самим уникне вузьких місць протягом згадуваної години піку.

b) Крім того, під час запитів суб'єктів із Azure, найшвидший спосіб зробити це з PartitionKey та RowKey. Це єдині індексовані поля в Табличному сховищі, і будь-який запит, який використовує обидва, буде повернутий за лічені мілісекунди. Тому переконайтеся, що ви використовуєте і PartitionKey та RowKey.

Детальніше дивіться тут: https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

Сподіваюсь, це допомагає.


-1

Примітка. Це загальна порада щодо оптимізації запитів БД.

Можливо, що ОРМ робить щось дурне. Проводячи оптимізацію, це нормально зменшити рівень абстракції. Тому я пропоную переписати запит мовою запиту (SQL?), Щоб було легше бачити, що відбувається, а також простіше оптимізувати.

Ключ до оптимізації пошуків - сортування! Зберігання сортованої таблиці зазвичай набагато дешевше порівняно зі скануванням всієї таблиці за кожним запитом! Тому, якщо можливо, зберігайте таблицю, відсортовану за ключем, використаним у запиті. У більшості рішення бази даних це досягається створенням індексного ключа.

Ще одна стратегія, яка добре працює, якщо комбінацій небагато, - це кожен запит як окрему (тимчасову в пам'яті) таблицю, яка завжди актуальна. Тож коли щось вставлено, воно також "вставляється" в таблиці "перегляду". Деякі рішення бази даних називають це "переглядами".

Більш жорстока стратегія полягає у створенні реплік лише для читання для розподілу навантаження.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.