Прочитать на английском

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


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

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

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

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

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

Если вы уменьшите стоимость запроса в RU, вы обычно также уменьшите задержку.

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

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

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

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

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

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

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

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

Стоимость запроса слишком высока.

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


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


Плата за запрос в единицах RU приемлема, но задержка всё еще слишком велика.

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

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

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

Запрос:

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
  • Лево
  • Substring (но только если первый параметр num_expr равен 0)

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

Системная функция Идеи по оптимизации
Верхний/Нижний Вместо использования системной функции для нормализации данных при сравнениях следует нормализировать регистр при вставке. Запрос, подобный 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 - более высокая стоимость RU-единиц:

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

Запрос с фильтром равенства и фильтром CONTAINS — сниженная плата за использование RU:

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

Объем ресурсных единиц (RU) для запросов с GROUP BY увеличивается по мере роста количества свойств в GROUP BY предложении. Например, в приведенном ниже запросе расход RU увеличивается по мере увеличения числа уникальных описаний.

Плата в очках производительности (RU) за агрегатную функцию с предложением 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":[]
}

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

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

Обновленный запрос (включает в себя оба свойства из предложения 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"
                }
            ]
        ]
    }

Плата в RU: 8,86 RU

Оптимизируйте выражения 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)

Плата в RU: 22,17 RU

Предположим, что только один элемент в массиве тегов соответствует фильтру и в нем есть пять элементов для массивов питательных веществ и порций. Выражения 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"
                }
            ]
        ]
}

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

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

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

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

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

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

Увеличьте MaxConcurrency.

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

Увеличьте MaxBufferedItemCount.

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

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

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