Диагностика и устранение проблем с запросами при использовании 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 учитывают регистр.

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

Исходное значение

Запрос:

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
  • Левый
  • 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 (то есть 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 5 x 5 = 25 элементов, а не до 1000 элементов, как в первом запросе.

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

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

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

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

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

Например, если создать контейнер с ключом секции 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. Любой из текущих API для пакетов SDK noSQL автоматически повторит этот запрос после короткого ожидания. Регулируемые запросы занимают больше времени, поэтому увеличение подготовленной пропускной способности может улучшить задержку запросов. Вы можете просмотреть общее число регулируемых запросов в колонке Метрики портала Azure.

Увеличьте MaxConcurrency.

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

Увеличьте MaxBufferedItemCount.

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

Дальнейшие действия

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