Поделиться через


Диагностика и устранение проблем с запросами при использовании Azure Cosmos DB

ОБЛАСТЬ ПРИМЕНЕНИЯ: NoSQL

В этой статье рассматривается общий рекомендуемый подход для устранения проблем с запросами в Azure Cosmos DB. Хотя не следует думать, что действия, описанные в этой статье, дают полную защиту от потенциальных проблем с запросами, мы включили сюда наиболее распространенные советы по повышению производительности. Эту статью следует использовать в качестве отправного места для устранения неполадок медленных или дорогостоящих запросов в Azure Cosmos DB для NoSQL. Можно также использовать журналы диагностики для поиска запросов, которые выполняются слишком долго или потребляют значительный объем пропускной способности. Если вы используете API Azure Cosmos DB для выполнения запросов MongoDB, вам пригодится это руководство.

Оптимизации запросов в Azure Cosmos DB распределены по категориям следующим образом.

  • Оптимизация, снижающая затраты единиц запроса (RU) на запрос.
  • Оптимизация, которая просто сокращает задержку.

Сокращение количества единиц запроса также обычно сокращает задержку.

Распространенные проблемы с пакетом SDK

Прежде чем приступить к работе с этим руководством, рекомендуется рассмотреть распространенные проблемы с пакетом SDK, не связанные с обработчиком запросов.

Получение метрик запроса

При оптимизации запроса в Azure Cosmos DB первым шагом всегда является получение метрик запроса для запроса. Эти метрики доступны также на портале Azure. После выполнения запроса в обозреватель данных метрики запроса отображаются рядом с вкладкой Результаты:

Получение метрик запроса

Получив метрики запроса, сравните Число полученных документов с Числом выходных документов для запроса. Используйте это сравнение, чтобы определить соответствующие разделы для проверки в этой статье.

Число полученных документов — это число документов, которые нужно было загрузить подсистеме обработки запросов. Число выходных документов — число документов, которые были необходимы для результатов запроса. Если число извлеченных документов выше, чем число выходных документов, было по крайней мере одна часть запроса, которая не смогла использовать индекс и требуется для проверки.

Ознакомьтесь со следующими разделами, чтобы получить представление об актуальных для вашего сценариях оптимизациях запросов.

Плата в единицах запроса слишком высока.

Число извлеченных документов выше, чем число выходных документов


Число полученных документов приблизительно равно числу выходных документов


Плата в единицах запроса приемлема, но задержка по-прежнему слишком высока.

Запросы, в которых количество полученных документов превышает число выходных документов.

Число полученных документов — это число документов, которые нужно было загрузить подсистеме обработки запросов. Число выходных документов — это число документов, возвращаемых запросом. Если число извлеченных документов выше, чем число выходных документов, было по крайней мере одна часть запроса, которая не смогла использовать индекс и требуется для проверки.

Ниже приведен пример запроса сканирования, который не был полностью обработан индексом.

Запрос:

SELECT VALUE c.description
FROM c
WHERE UPPER(c.description) = "BABYFOOD, DESSERT, FRUIT DESSERT, WITHOUT ASCORBIC ACID, JUNIOR"

Метрики запроса:

Retrieved Document Count                 :          60,951
Retrieved Document Size                  :     399,998,938 bytes
Output Document Count                    :               7
Output Document Size                     :             510 bytes
Index Utilization                        :            0.00 %
Total Query Execution Time               :        4,500.34 milliseconds
  Query Preparation Times
    Query Compilation Time               :            0.09 milliseconds
    Logical Plan Build Time              :            0.05 milliseconds
    Physical Plan Build Time             :            0.04 milliseconds
    Query Optimization Time              :            0.01 milliseconds
  Index Lookup Time                      :            0.01 milliseconds
  Document Load Time                     :        4,177.66 milliseconds
  Runtime Execution Times
    Query Engine Times                   :          322.16 milliseconds
    System Function Execution Time       :           85.74 milliseconds
    User-defined Function Execution Time :            0.00 milliseconds
  Document Write Time                    :            0.01 milliseconds
Client Side Metrics
  Retry Count                            :               0
  Request Charge                         :        4,059.95 RUs

Число извлеченных документов (60 951) выше, чем число выходных документов (7), что означает, что этот запрос привел к проверке документа. В этом случае системная функция UPPER () не использует индекс.

Включение необходимых путей в политику индексирования

Политика индексирования должна охватывать все свойства, входящие в предложения WHERE, предложения ORDER BY, JOIN и большинство системных функций. Требуемые пути, указанные в политике индекса, должны соответствовать свойствам в документах JSON.

Примечание.

Свойства в политике индексирования Azure Cosmos DB учитывают регистр.

Исходная

Запрос:

SELECT *
FROM c
WHERE c.description = "Malabar spinach, cooked"

Политика индексации.

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": [
        {
            "path": "/description/*"
        }
    ]
}

Стоимость в ЕЗ: 409,51 ЕЗ

Оптимизированная

обновленная политика индексации.

{
    "indexingMode": "consistent",
    "automatic": true,
    "includedPaths": [
        {
            "path": "/*"
        }
    ],
    "excludedPaths": []
}

Стоимость в ЕЗ: 2,98 ЕЗ

Вы можете в любое время добавлять свойства в политику индексации без влияния на доступность операций чтений и записи. Можно отслеживать ход преобразования индекса.

Узнайте, какие системные функции используют индекс.

Большинство системных функций используют индексы. Вот список некоторых распространенных строковых функций, которые применяют индексы:

  • StartsWith
  • Содержит
  • RegexMatch
  • Left
  • Substring (но только если первый параметр num_expr равен 0)

Ниже приведены некоторые распространенные системные функции, которые не используют индекс и должны загружать каждый документ в случае использования в предложении WHERE:

Системная функция Идеи по оптимизации
Upper/Lower Вместо использования системной функции для нормализации данных при сравнениях следует нормализировать регистр при вставке. Запрос, подобный SELECT * FROM c WHERE UPPER(c.name) = 'BOB', преобразуется в SELECT * FROM c WHERE c.name = 'BOB'.
GetCurrentDateTime/GetCurrentTimestamp/GetCurrentTicks Определите текущее время перед выполнением запроса и используйте это строковое значение в предложении WHERE.
Математические функции (кроме статистических выражений) Если в запросе необходимо часто вычислять значение, рекомендуется сохранить значение как свойство в документе JSON.

Эти системные функции могут использовать индексы за исключением случаев, когда они включены в запросы со статистическими выражениями:

Системная функция Идеи по оптимизации
Пространственные системные функции Сохранение результата запроса в материализованном представлении режима реального времени.

При использовании в предложении SELECT неэффективные системные функции не влияют на то, как индексы используются запросами.

Повышение эффективности выполнения строковых системных функций

Для некоторых системных функций, которые используют индексы, вы можете добавить в запрос предложение ORDER BY, чтобы улучшить его выполнение.

В частности, для любой системной функции, для которой стоимость в ЕЗ увеличивается по мере увеличения кратности свойства, в запрос имеет смысл включить предложение ORDER BY. Такие запросы выполняют сканирование индекса, поэтому их результаты могут оказаться более эффективными.

Такая оптимизация позволяет улучшить выполнение следующих системных функций:

  • StartsWith (с параметром case-insensitive = true)
  • StringEquals (с параметром case-insensitive = true)
  • Содержит
  • RegexMatch
  • EndsWith

Например, рассмотрим SQL-запрос ниже с предложением CONTAINS. CONTAINS будет использовать индексы, но иногда даже после добавления соответствующего индекса вы по-прежнему можете наблюдать высокую плату за ЕЗ при выполнении приведенного ниже запроса.

Исходный запрос:

SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")

Чтобы улучшить выполнение запроса, добавьте ORDER BY:

SELECT *
FROM c
WHERE CONTAINS(c.town, "Sea")
ORDER BY c.town

Такая же оптимизация может помочь в запросах с другими фильтрами. В этом случае лучше также добавлять в предложение ORDER BY свойства с фильтрами равенства.

Исходный запрос:

SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")

Чтобы улучшить выполнение запроса, добавьте ORDER BY и составной индекс для (c.name, c.town):

SELECT *
FROM c
WHERE c.name = "Samer" AND CONTAINS(c.town, "Sea")
ORDER BY c.name, c.town

Узнайте, какие статистические запросы используют индекс.

В большинстве случаев агрегатные системные функции в Azure Cosmos DB используют индекс. Однако в зависимости от фильтров или других предложений в агрегатном запросе обработчик запросов может потребоваться для загрузки большого количества документов. Как правило, обработчик запросов сначала применяет фильтры равенства и диапазона. После применения этих фильтров подсистема запросов может оценить другие фильтры и прибегнуть к загрузке оставшихся документов для вычисления агрегата при необходимости.

Например, учитывая эти два примера запросов, запрос с равенством и CONTAINS фильтром системных функций, как правило, эффективнее, чем запрос с только системным фильтром CONTAINS функций. Это обусловлено тем, что фильтр равенства применяется в первую очередь и использует индекс до того, как документы нужно будет загрузить для более затратного фильтра CONTAINS.

Запрос только с фильтром CONTAINS — более высокая стоимость в единицах запроса:

SELECT COUNT(1)
FROM c
WHERE CONTAINS(c.description, "spinach")

Запрос с фильтром равенства и фильтром CONTAINS — более низкая стоимость в единицах запроса:

SELECT AVG(c._ts)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats" AND CONTAINS(c.description, "spinach")

Ниже приведены дополнительные примеры агрегатных запросов, которые не будут полностью использовать индекс:

Запросы с системными функциями, которые не используют индекс.

Чтобы узнать, использует ли запрос индекс, необходимо обратиться к странице соответствующей системной функции.

SELECT MAX(c._ts)
FROM c
WHERE CONTAINS(c.description, "spinach")

Статистические запросы с определяемыми пользователем функциями (UDF).

SELECT AVG(c._ts)
FROM c
WHERE udf.MyUDF("Sausages and Luncheon Meats")

Запросы с GROUP BY (группировать по).

Плата за запросы с GROUP BY увеличением числа свойств в GROUP BY предложении увеличивается. Например, в приведенном ниже запросе плата за запрос ЕЗ увеличивается по мере увеличения числа уникальных описаний.

Плата за агрегатную функцию с GROUP BY предложением выше, чем плата за агрегатную функцию. В этом примере обработчик запросов должен загрузить каждый документ, соответствующий фильтру c.foodGroup = "Sausages and Luncheon Meats", так что можно ожидать высокой стоимости в единицах запроса.

SELECT COUNT(1)
FROM c
WHERE c.foodGroup = "Sausages and Luncheon Meats"
GROUP BY c.description

Если планируется часто выполнять одни и те же статистические запросы, может оказаться более эффективным создание материализованных представлений в реальном времени с каналом изменений Azure Cosmos DB, чем выполнение отдельных запросов.

Оптимизируйте запросы, имеющие как фильтр, так и предложение ORDER BY (упорядочить по).

Хотя запросы с фильтром и ORDER BY предложением обычно будут использовать индекс диапазона, они более эффективны, если они могут служить из составного индекса. В дополнение к изменению политики индексации необходимо добавить все свойства из составного индекса в предложение ORDER BY. Это изменение запроса гарантирует, что он использует составной индекс.

Исходная

Запрос:

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c._ts ASC

Политика индексации.

{

        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[]
}

Стоимость в ЕЗ: 44,28 ЕЗ

Оптимизированная

Обновленный запрос (включает в себя оба свойства из предложения ORDER BY):

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies"
ORDER BY c.foodGroup, c._ts ASC

обновленная политика индексации.

{  
        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[],
        "compositeIndexes":[  
            [  
                {  
                    "path":"/foodGroup",
                    "order":"ascending"
        },
                {  
                    "path":"/_ts",
                    "order":"ascending"
                }
            ]
        ]
    }

Стоимость в ЕЗ: 8,86 ЕЗ

Оптимизируйте выражения JOIN (объединить) с помощью вложенного запроса.

Вложенные запросы с несколькими значениями могут оптимизировать выражения JOIN путем принудительной отправки предикатов после каждого выражения select-many, а не после всех перекрестных соединений в предложении WHERE.

Рассмотрим следующий запрос:

SELECT Count(1) AS Count
FROM c
JOIN t IN c.tags
JOIN n IN c.nutrients
JOIN s IN c.servings
WHERE t.name = 'infant formula' AND (n.nutritionValue > 0
AND n.nutritionValue < 10) AND s.amount > 1

Стоимость в ЕЗ: 167,62 ЕЗ

Для этого запроса индекс соответствует любому документу с тегом с именем infant formula, nutritionValue больше 0 и amount больше 1. Выражение здесь JOIN выполняет перекрестное произведение всех элементов тегов, питательных веществ и обслуживает массивы для каждого соответствующего документа перед применением любого фильтра. Затем предложение WHERE применит предикат фильтра к каждому кортежу <c, t, n, s>.

Например, если соответствующий документ содержит 10 элементов в каждом из трех массивов, он расширяется до 1 x 10 x 10 x 10 x 10 (то есть 1000) кортежей. Использование вложенных запросов здесь может помочь отфильтровать присоединенные элементы массива перед присоединением к следующему выражению.

Этот запрос эквивалентен предыдущему, но использует вложенные запросы:

SELECT Count(1) AS Count
FROM c
JOIN (SELECT VALUE t FROM t IN c.tags WHERE t.name = 'infant formula')
JOIN (SELECT VALUE n FROM n IN c.nutrients WHERE n.nutritionValue > 0 AND n.nutritionValue < 10)
JOIN (SELECT VALUE s FROM s IN c.servings WHERE s.amount > 1)

Стоимость в ЕЗ: 22,17 ЕЗ

Предположим, что только один элемент в массиве тегов соответствует фильтру и в нем есть пять элементов для массивов питательных веществ и порций. Выражения JOIN расширяются до 1 x 1 x 1 x 5 x 5 = 25 элементов, а не до 1000 элементов в первом запросе.

Запросы, в которых количество полученных документов равно числу выходных документов.

Если Число полученных документов приблизительно равно Числу выходных документов, механизму запросов не нужно было сканировать много ненужных документов. Для многих запросов, таких как использование ключевого слова TOP, Число полученных документов может превысить Число выходных документов на 1. Вам не нужно беспокоиться об этом.

Сведите к минимуму число запросов между секциями.

Azure Cosmos DB использует секционирование для масштабирования отдельных контейнеров по мере роста числа единиц запросов и потребностей хранилища данных. Каждый физическая секция имеет отдельный и независимый индекс. Если запрос имеет фильтр равенства, соответствующий ключу секции контейнера, необходимо проверить только индекс соответствующей секции. Эта оптимизация сокращает общее число единиц запроса, необходимых для запроса.

При наличии большого количества подготовленных единиц запроса (более 30 000) или большого объема хранимых данных (более чем примерно 100 ГБ) контейнер, вероятно, будет достаточно велик, чтобы увидеть значительное стоимости в единицах запроса.

Например, если вы создаете контейнер с разделом key foodGroup, следующие запросы должны проверить только одну физическую секцию:

SELECT *
FROM c
WHERE c.foodGroup = "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"

Запросы, имеющие фильтр с ключом IN секции, проверяют только одну или несколько соответствующих физических секций и не будут "раздуть":

SELECT *
FROM c
WHERE c.foodGroup IN("Soups, Sauces, and Gravies", "Vegetables and Vegetable Products") and c.description = "Mushroom, oyster, raw"

Запросы, имеющие фильтры диапазонов в ключе секции или не имеющие фильтров по ключу секции, вынуждены будут "растекаться" и проверять наличие результатов в индексах всех физических секций:

SELECT *
FROM c
WHERE c.description = "Mushroom, oyster, raw"
SELECT *
FROM c
WHERE c.foodGroup > "Soups, Sauces, and Gravies" and c.description = "Mushroom, oyster, raw"

Оптимизируйте запросы с фильтрами для нескольких свойств.

Хотя запросы, имеющие фильтры по нескольким свойствам, обычно будут использовать индекс диапазона, они более эффективны, если их можно обслуживать из составного индекса. Для небольших объемов данных такая оптимизация не окажет существенного влияния. Однако она может быть полезна для больших объемов данных. Для одного составного индекса можно оптимизировать только один фильтр без проверки на равенство. Если в запросе есть несколько фильтров без проверки на равенство, выберите один из них для использования составного индекса. Остальные продолжают использовать индексы диапазона. Фильтр без проверки на равенство должен быть определен последним в составном индексе. Дополнительные сведения о составных индексах.

Ниже приведены некоторые примеры запросов, которые можно оптимизировать с помощью составного индекса.

SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts = 1575503264
SELECT *
FROM c
WHERE c.foodGroup = "Vegetables and Vegetable Products" AND c._ts > 1575503264

Вот соответствующий составной индекс:

{  
        "automatic":true,
        "indexingMode":"Consistent",
        "includedPaths":[  
            {  
                "path":"/*"
            }
        ],
        "excludedPaths":[],
        "compositeIndexes":[  
            [  
                {  
                    "path":"/foodGroup",
                    "order":"ascending"
                },
                {  
                    "path":"/_ts",
                    "order":"ascending"
                }
            ]
        ]
}

Оптимизации, сокращающие задержку запросов.

Часто стоимость в единицах запроса может быть приемлемой, когда задержка запроса все еще слишком высока. В следующих разделах представлен обзор советов по сокращению задержки запросов. Если один и тот же запрос выполняется несколько раз для одного и того же набора данных, стоимость в единицах запроса будет скорее всего одинакова каждый раз. Однако задержка запросов может варьироваться при выполнении.

Добейтесь большей близости.

Запросы, выполняемые из другого региона, отличного от учетной записи Azure Cosmos DB, имеют более высокую задержку, чем если бы они выполнялись в одном регионе. Например, если выполнять код на своем настольном компьютере, задержка повысится на десятки и сотни миллисекунд (если не больше), по сравнению с запросом, поступившим с виртуальной машины в том же регионе Azure, что и Azure Cosmos DB. Можно легко глобально распределить данные в Azure Cosmos DB, чтобы обеспечить близость данных к приложениям.

Увеличьте подготовленную пропускную способность.

В Azure Cosmos DB подготовленная пропускная способность измеряется в единицах запроса (ЕЗ). Представьте, что у вас есть запрос, использующий 5 ЕЗ пропускной способности. Например, если вы подготавливаете 1000 ЕЗ, вы сможете выполнить запрос 200 раз в секунду. Если вы попытались выполнить запрос при недостаточной пропускной способности, Azure Cosmos DB возвратит ошибку HTTP 429. Любой из текущих ПАКЕТОВ SDK для NoSQL автоматически повторит этот запрос после ожидания короткого времени. Регулируемые запросы занимают больше времени, поэтому увеличение подготовленной пропускной способности может улучшить задержку запросов. Вы можете просмотреть общее число регулируемых запросов в колонке Метрики портала Azure.

Увеличьте MaxConcurrency.

Параллельные запросы позволяют одновременно обращаться к нескольким секциям. Но данные из каждой секционированной коллекции извлекаются в рамках запроса последовательно. Таким образом, установка параметра MaxConcurrency на число секций позволит достичь высокой производительности запроса, если все другие состояния системы остаются неизменными. Если число секций неизвестно, MaxConcurrency (или MaxDegreesOfParallelism в предыдущих версиях пакетов SDK) можно установить на большое число. Система выбирает минимальное (количество секций, предоставленных пользователем входных данных) в качестве максимальной степени параллелизма.

Увеличьте MaxBufferedItemCount.

Запросы предназначены для предварительного получения результатов, пока текущий пакет результатов обрабатывается клиентом. Предварительная выборка способствует общему уменьшению задержки при обработке запроса. Значение setMaxBufferedItemCount ограничивает количество предварительно выбираемых результатов. Если установить этот параметр на ожидаемое число возвращаемых результатов (или большее число), запрос может получить наибольшее преимущество от предварительной выборки. Если установить это значение на –1, система автоматически определит количество элементов для буферизации.

Следующие шаги

Ознакомьтесь со следующими статьями, чтобы получить сведения о том, как измерять единицы запроса для каждого запроса, получать статистику исполнения для настройки запросов и многом другом.