Рекомендации по запросам OData Analytics для Azure DevOps

Azure DevOps Services | Azure DevOps Server 2022 — Azure DevOps Server 2019

Разработчики расширений могут воспользоваться рекомендациями, приведенными в этой статье, для разработки эффективных запросов OData к Аналитике для Azure DevOps. Следуя этим рекомендациям, можно убедиться, что запросы имеют хорошую производительность для времени выполнения и потребления ресурсов. Запросы, которые не соответствуют этим рекомендациям, могут привести к снижению производительности, с длительным временем ожидания отчета, запросами, превышающими допустимое потребление ресурсов или блоки служб.

Примечание.

Служба Аналитики автоматически включается и поддерживается в рабочей среде для всех Служб Azure DevOps Services. Интеграция Power BI и доступ к веб-каналу OData службы Аналитики общедоступны. Мы рекомендуем вам использовать его и дать нам отзыв. Доступные данные зависят от версий. Последняя поддерживаемая версия: v2.0последняя предварительная версия v4.0-preview. Дополнительные сведения см. в разделе "Управление версиями API OData".

Примечание.

Служба Аналитики автоматически устанавливается и поддерживается в рабочей среде для всех новых коллекций проектов для Azure DevOps Server 2020 и более поздних версий. Интеграция Power BI и доступ к веб-каналу OData службы Аналитики общедоступны. Мы рекомендуем вам использовать его и дать нам отзыв. При обновлении с Azure DevOps Server 2019 можно установить службу Аналитики во время обновления.

Доступные данные зависят от версий. Последняя поддерживаемая версия: v2.0последняя предварительная версия v4.0-preview. Дополнительные сведения см. в разделе "Управление версиями API OData".

Примечание.

Служба Аналитики доступна в предварительной версии для Azure DevOps Server 2019. Его можно включить или установить для коллекции проектов. Интеграция Power BI и доступ к веб-каналу OData службы аналитики находятся в предварительной версии. Мы рекомендуем вам использовать его и дать нам отзыв.

Доступные данные зависят от версий. Последняя поддерживаемая версия: v2.0последняя предварительная версия v4.0-preview. Дополнительные сведения см. в разделе "Управление версиями API OData".

Эти рекомендации являются префиксами терминов DO, CONSIDER, AVOID и DON'T. Ограничивающие правила, применяемые аналитикой, содержат префикс [ЗАБЛОКИРОВАНО] . Вы должны понимать компромиссы между различными решениями. При определенных обстоятельствах могут потребоваться требования к данным, которые заставляют вас нарушать одно или несколько рекомендаций. Такие случаи должны быть редкими. Мы рекомендуем вам иметь четкие и убедительные причины для таких решений.

Совет

Примеры, показанные в этом документе, основаны на URL-адресе Azure DevOps Services. Используйте подстановки для локальных версий.

https://{servername}:{port}/tfs/{OrganizationName}/{ProjectName}/_odata/{version}/

Сообщения об ошибках и предупреждения

✔️ Проверка предупреждений ответов OData

Каждый выполняемый запрос проверяется на соответствие набору предопределенных правил. Нарушения возвращают ответ OData следующим @vsts.warningsобразом. Просмотрите эти предупреждения по мере предоставления текущей и контекстно-конфиденциальной информации о том, как улучшить запрос.

{
  "@odata.context": "https://{OrganizationName}.tfsallin.net/_odata/v1.0/$metadata#WorkItems",
  "@vsts.warnings": [
    "The specified query does not include a $select or $apply clause which is recommended for all queries."
  ],
  ...
}

✔️ Проверка сообщений об ошибках OData

Запросы, которые нарушают правило ошибки OData, приводят к сбою ответа с кодом состояния 400 (недопустимый запрос). Связывание сообщений не отображается в свойстве @vsts.warnings . Вместо этого они создают сообщение об ошибке в свойстве message в ответе JSON.

{
  "error": {
  "code": "0",
  "message": "The query specified in the URI is not valid. The Snapshot tables in Analytics are intended to be used only in an aggregation."
  }
}

Ограничения

Do's

Consider

Заблокировано

Избегайте

✔️ Do limit the query to the project(s), к которых у вас есть доступ

Если запрос предназначен для данных из проекта, к которым у вас нет доступа, запрос возвращает сообщение "Доступ к проекту запрещен". Чтобы убедиться, что у вас есть доступ, убедитесь , что для всех проектов, запрашиваемых вами, задано разрешение "Просмотр аналитики ". Дополнительные сведения см. в разделе "Разрешения", необходимые для доступа к аналитике.

Если у вас нет доступа к проекту, отобразится следующее сообщение:

Результаты запроса включают данные в один или несколько проектов, для которых у вас нет доступа. Добавьте один или несколько фильтров проектов, чтобы указать проекты, к которые у вас есть доступ в сущности WorkItems. Если вы используете свойства $expand или навигации, фильтр проекта необходим для этих сущностей.

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

Например, следующий запрос извлекает рабочие элементы, принадлежащие проектам с именем {projectSK1} и {projectSK2}.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=ProjectSK eq {projectSK1} or ProjectSK eq {projectSK2}
  &$select=WorkItemId, Title

✔️ Укажите фильтр проекта внутри $expand предложения, если расширение может включать данные в другие, потенциально недоступные проекты

При развертывании свойств навигации есть вероятность того, что вы в конечном итоге ссылаетесь на данные из других недоступных проектов. Если вы ссылаетесь на недоступные данные, вы получите то же сообщение об ошибке, указанное ранее, "Результаты запроса включают данные в один или несколько проектов...". Аналогичным образом можно устранить эту проблему, добавив явные фильтры проектов для управления расширенными данными.

Это можно сделать в обычном $filter предложении для простых свойств навигации. Например, следующий запрос явно запрашивает WorkItemLinks , где ссылка и целевой объект существуют в одном проекте.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemLinks?
  $filter=ProjectSK eq {projectSK} and TargetWorkItem/ProjectSK eq {projectSK}
  &$select=LinkTypeReferenceName, SourceWorkItemId, TargetWorkItemId
  &$expand=TargetWorkItem($select=WorkItemId, Title)

Вместо этого можно переместить фильтр, чтобы $filter развернуть параметр в предложении $expand . Однако он изменяет семантику запроса. Например, следующий запрос получает все ссылки из данного проекта и условно расширяет целевой объект только в том случае, если он существует в одном проекте. Хотя этот подход является допустимым, этот подход может привести к путанице, так как может быть трудно определить, не развернуто ли свойство, так как оно null было отфильтровано. Используйте это решение только в том случае, если вам действительно нужна эта конкретная ситуация.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemLinks?
  $filter=ProjectSK eq {projectSK}
  &$select=LinkTypeReferenceName, SourceWorkItemId, TargetWorkItemId
  &$expand=TargetWorkItem($filter=ProjectSK eq {projectSK}; $select=WorkItemId, Title)

Параметр $filter развертывания полезен при использовании свойства коллекции развертывания, например Children в WorkItems наборе сущностей. Например, следующий запрос возвращает все рабочие элементы из заданного проекта вместе со всеми дочерними элементами, принадлежащими в одном проекте.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=ProjectSK eq {projectSK}
  &$select=WorkItemId, Title
  &$expand=Children($filter=ProjectSK eq {projectSK}; $select=WorkItemId, Title)

Укажите фильтр, если развернуть одно из следующих свойств:

  • WorkItems набор сущностей: Parent, Children
  • WorkItemLinks набор сущностей: TargetWorkItem.

✔️ РАССМОТРИТЕ возможность запроса с помощью конечной точки с областью проекта

Если вы заинтересованы в данных из одного проекта, рекомендуется использовать конечную точку OData с областью проекта (/{ProjectName}/_odata/v1.0). Это позволяет избежать проблем, описанных в предыдущих двух разделах, и неявно фильтрует данные в одном проекте, наборе сущностей со ссылкой и всех расширенных свойствах навигации.

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

https://analytics.dev.azure.com/{OrganizationName}/{ProjectName}/_odata/{version}//WorkItemLinks?
  &$select=LinkTypeReferenceName, SourceWorkItemId, TargetWorkItemId
  &$expand=TargetWorkItem($select=WorkItemId, Title)

Запрос к дочерним элементам рабочего элемента также гораздо короче и проще.

https://analytics.dev.azure.com/{OrganizationName}/{ProjectName}/_odata/{version}//WorkItems?
  &$select=WorkItemId, Title
  &$expand=Children($select=WorkItemId, Title)

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

✔️ Подождите или остановите операцию, если запрос превышает ограничения использования

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

Запрос был заблокирован из-за превышения использования ресурса "{resource}" в пространстве имен "{пространство имен}".

Дополнительные сведения об ограничении скорости см. в разделе "Ограничения скорости". Чтобы узнать, как создавать эффективные запросы OData, ознакомьтесь с рекомендациями по производительности далее в этой статье.

✔️ Дожидайтесь или остановите операцию, если запрос завершается сбоем с истечением времени ожидания

Аналогично превышению ограничений использования, следует ждать или останавливать операцию, если запрос приходит на время ожидания. Это может сигнализировать о временной проблеме, поэтому вы можете повторить попытку один раз, чтобы узнать, устранена ли проблема. Однако постоянные тайм-ауты указывают на то, что запрос, вероятно, слишком дорого для выполнения. Дальнейшие повторные попытки приводят только к превышению ограничений использования, и вы получаете блокировку.

TF400733: запрос отменен: запрос превысил время ожидания запроса, повторите попытку.

Время ожидания указывает, что запросу требуется оптимизация. Чтобы узнать, как создавать эффективные запросы OData, ознакомьтесь с рекомендациями по производительности далее в этой статье.

❌ [ЗАБЛОКИРОВАНО] Не используйте сущности моментального снимка для других агрегатов

Наборы сущностей моментальных снимков с суффиксом Snapshot являются особыми, так как они моделироваются как ежедневные моментальные снимки. Их можно использовать для получения состояния сущностей, как они были в конце каждого дня в прошлом. Например, если вы запросили и отфильтровали WorkItemSnapshot WorkItemIdодин элемент, вы получите одну запись за каждый день после создания рабочего элемента. Загрузка непосредственно всех этих данных будет дорогой и, скорее всего, превысит ограничения использования и будет заблокирована. Однако агрегирование для этих сущностей разрешено и рекомендуется. На самом деле наборы сущностей моментального снимка были разработаны с учетом сценариев агрегирования.

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

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemSnapshot?
  $apply=
    filter(DateSK ge 20200101 and DateSK le 20200131)/
    groupby((DateSK), aggregate($count as Count))

Дополнительные сведения об агрегированиях см. в разделе "Статистические данные".

✔️ DateSK Включение или DateValue столбец в предложение при groupby агрегации по таблицам моментальных снимков

Так как все сущности моментальных снимков моделироваются как ежедневные таблицы моментальных снимков, всегда следует включать одно из свойств дня (DateSK или DateValue) в предложение группирования. В противном случае результат может отображаться неправильно.

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

❌ [ЗАБЛОКИРОВАНО] Не используйте ключи сущностей в путях ресурсов для адресации сущностей

Синтаксис OData предоставляет способ доступа к определенной сущности, включив его ключи непосредственно в сегменты URL-адресов. Дополнительные сведения см. в статье OData версии 4.0. Часть 2. Соглашения о URL-адресах — 4.3, касающиеся сущностей. Хотя OData разрешает такое адресация, аналитика блокирует ее. Включение в запрос приводит к следующей ошибке.

Недопустимый запрос, указанный в URI. Аналитика не поддерживает навигацию по ключам или свойствам, например WorkItems(Id) или WorkItem(Id)/AssignedTo. Если вы получаете эту ошибку в PowerBI, переопределите запрос, чтобы избежать неправильной свертывания, которая вызывает проблему N+1.

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

✔️ Явно адресовать сущности с предложениями фильтра

Если вы хотите получить данные для одной сущности, следует использовать тот же подход, что и для коллекции сущностей, и явно определять фильтры в предложении $filter .

Например, следующий запрос получает один рабочий элемент по его идентификатору.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=WorkItemId eq {id}
  &$select=WorkItemId, Title

Если вы не уверены, какие свойства следует включить в такой фильтр, его можно найти в метаданных. Сведения о создании запросов OData для аналитики, компонентов URL-адресов для запроса метаданных. Свойства находятся в Key элементе EntityTypeобъекта . Например, WorkItemId это Revision ключевые столбцы для сущности WorkItemRevision .

<EntityType Name="WorkItemRevision">
  <Key>
    <PropertyRef Name="WorkItemId"/>
    <PropertyRef Name="Revision"/>
  </Key>
  [...]
</EntityType>

❌ [ЗАБЛОКИРОВАНО] Не расширяйте Revisions WorkItem сущность

Модель данных Аналитики запрещает определенные типы расширений. Один из них, который может быть удивительным для некоторых, является свойством Revisions коллекции в сущности WorkItem . При попытке развернуть это свойство вы получите следующее сообщение об ошибке.

Недопустимый запрос, указанный в URI. Свойство "Редакции" нельзя использовать в параметре запроса $expand.

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

✔️ Do use WorkItemRevisions entity set to load all the revisions for a given work item

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

Например, следующий запрос возвращает все редакции рабочего элемента с идентификатором {id} .

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemRevisions?
  $filter=WorkItemId eq {id}
  &$select=WorkItemId, Title

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

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemRevisions?
  $filter=WorkItem/State eq 'Active'
  &$select=WorkItemId, Title

❌ [ЗАБЛОКИРОВАНО] Не группировать по отдельным столбцам

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

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

Чтобы устранить эту проблему, удалите отдельный столбец из groupby предложения.

❌ [ЗАБЛОКИРОВАНО] Не используйте countdistinct агрегирование

Аналитика не поддерживает функцию countdistinct , несмотря на то, что OData делает. Хотя мы планируем добавить поддержку в будущем, она в настоящее время недоступна. Запрос, содержащий эту функцию, возвращает следующее сообщение об ошибке.

Запросы, которые применяют число отдельных с агрегированием, не поддерживаются.

❌ Избегайте агрегирования, которые могут привести к арифметическому переполнению

В редких случаях запрос агрегирования может столкнуться с проблемами с арифметическим переполнением. Например, это может произойти при сумме некоторых числовых свойств, которые не предназначены для суммирования, например StackRank в сущностях рабочего элемента. Поскольку стандарт агрегирования OData для агрегирования данных не предоставляет способ приведения свойства к другому типу, единственным способом решения этой проблемы является удаление проблемного свойства из агрегата.

✔️ Использование пакетной конечной точки для длинных запросов

Проблемы с длинными запросами могут возникнуть. В частности, могут возникнуть проблемы, когда:

  • Запрос проекта с большим количеством настраиваемых полей.
  • Запрос создается программным способом.

Текущее ограничение запросов OData, отправленных с HTTP GET 3000 символами. Если вы превышаете его, вы вернетесь к ответу "404 Не найдено".

HTTP/1.1 404 Not Found
Content-Length: 0

Чтобы устранить эту проблему, используйте конечную точку пакетной службы OData, как описано в спецификации OData версии 4.0. Часть 1. Протокол — 11.7 пакетных запросов. Пакетная функция была в основном предназначена для группировки нескольких операций в одну HTTP полезные данные запроса, но ее также можно использовать в качестве обходного решения для ограничения длины запроса. Отправив HTTP POST запрос произвольной длины, и служба правильно интерпретирует его.

❌ [ЗАБЛОКИРОВАНО] Не используйте пакетную конечную точку для отправки нескольких запросов

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

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

❌ [ЗАБЛОКИРОВАНО] Не используйте запросы, которые приводят к более чем 800 столбцам

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

VS403670. Указанный запрос возвращает столбцы N, превышающие допустимое ограничение в 800 столбцов. Используйте явные $select (включая в $expand) для ограничения количества столбцов.

Добавьте предложение $select в запрос и $expand операции в запросе, чтобы избежать превышения этого ограничения.

❌ Избегайте создания длинных запросов

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

Если запрос содержит много ключей сущностей в запросе (например, WorkItemId eq {id 1} or WorkItemId eq {id 2} or ...вы можете переписать его. Вместо передачи идентификаторов попробуйте определить другие критерии, которые выбирают тот же набор сущностей. Иногда может потребоваться изменить процесс (например, добавить новое поле или тег), но это обычно стоит. Запросы, использующие более абстрактные фильтры, проще поддерживать и иметь больший потенциал для улучшения работы.

Другой сценарий, который, как правило, создает длинные запросы, возникает при включении множества отдельных дат (например, DateSK eq {dateSK 1} or DateSK eq {dateSK 2} or ...). Найдите другой шаблон, который можно использовать для создания более абстрактного фильтра. Например, следующий запрос возвращает все рабочие элементы, созданные в понедельник.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedOn/DayOfWeek eq 2
  &$select=WorkItemId, Title, State

✔️ Задание часового пояса при фильтрации по столбцам дат

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

Недопустимый запрос, указанный в URI. Смещение даты и времени не указано. Чтобы указать смещение, используйте любой из этих форматов ггГГ-ММ-ддZ, чтобы указать все, начиная с полуночи или гггг-ММ-ддThh:mm-hh:mm (стандартное представление дат и времени ISO 8601).

Чтобы решить эту проблему, добавьте сведения о часовом поясе. Например, предположим, что организация настроена для отображения данных в часовом поясе "(UTC-08:00) Тихоокеанское время (США и Канада)", следующий запрос получает все рабочие элементы, созданные с начала 2020 года.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDate ge 2020-01-01T00:00:00-08:00
  &$select=WorkItemId, Title, State

То же решение работает для часовых поясов с положительными смещениями, однако знак плюса (+) имеет особое значение в URI, и его необходимо правильно обрабатывать. Если в качестве отправной точки указать 2020-01-01T00:00:00+08:00 (с символом + ), вы получите следующую ошибку.

Недопустимый запрос, указанный в URI. Синтаксическая ошибка в позиции 31 в "CreatedDate ge 2020-01-01T00000 08:00".

Чтобы решить эту проблему, замените + символ его закодированной версией %2B. Например, если организация настроена на отображение данных в часовом поясе "(UTC+08:00) Пекин, Чунгинг, Гонконг, Урумчи", следующий запрос возвращает все рабочие элементы, созданные с начала 2020 года.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDate ge 2020-01-01T00:00:00%2B08:00
  &$select=WorkItemId, Title, State

Альтернативный подход — использовать суррогатные ключевые свойства даты, так как они не хранят сведения часового пояса. Например, следующий запрос возвращает все рабочие элементы, созданные с начала 2020 года, независимо от параметров организации.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDateSK ge 20200101
  &$select=WorkItemId, Title, State

Рекомендации по повышению производительности

Do's

Не давайте

Consider

Избегайте

✔️ Измерение эффекта реализации руководства по производительности

Как и в случае с любыми рекомендациями по производительности, их не следует слепо реализовывать. Вместо этого всегда фиксируйте базовые показатели и измеряйте влияние внесенных изменений. Все рекомендации были созданы на основе взаимодействия с клиентами Аналитики, у которых были конкретные требования и проблемы. Эти рекомендации были рассмотрены как общие, так и потенциально полезны для тех, кто разрабатывает аналогичные запросы. Однако в редких случаях, следуя рекомендациям, не может оказать никакого влияния или даже негативного влияния на производительность. Вы должны измерить разницу, чтобы заметить это. Если это произойдет, предоставьте отзыв на портале Сообщество разработчиков.

Существует множество вариантов измерения производительности. Самым простым является запуск двух версий одного запроса непосредственно в браузере. Следите за временем, которое требуется в средствах разработчика. Например, можно использовать панель "Сеть" в средствах разработчика Microsoft Edge F12). Другим вариантом является запись этих сведений с помощью средства веб-отладки Fiddler.

Независимо от вашего подхода, выполните оба запроса несколько раз. Например, запустите запросы 30 раз каждый, чтобы иметь достаточно большой набор примеров. Затем определите характеристики производительности. Аналитика следует архитектуре с несколькими клиентами. Таким образом, другие операции, происходящие в то же время, могут повлиять на длительность запросов.

✔️ Использование расширений агрегирования

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

Дополнительные сведения см. в разделе "Статистические данные".

✔️ Do укажите столбцы в предложении $select

Укажите нужные столбцы в предложении $select . Аналитика основана на технологии Index Columnstore . Это означает, что данные являются как хранилищем, так и обработкой запросов, основаны на столбцах. Уменьшая набор свойств, вы ссылаетесь в $select предложении, можно уменьшить количество столбцов, которые необходимо сканировать и повысить общую производительность запроса.

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

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $select=WorkItemId, Title, State

Примечание.

Azure DevOps поддерживает настройку процесса. Некоторые администраторы используют эту функцию и создают сотни настраиваемых полей. Если опущено $select предложение, запрос возвращает все поля, включая настраиваемые поля.

✔️ Do указать столбцы в параметре $select развертывания внутри $expand предложения

$select Аналогично рекомендациям по предложению, укажите свойства в $select параметре развертывания в предложении$expand. Это легко забыть, но если вы опустите его, ответ содержит все свойства из развернутого объекта.

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

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $select=WorkItemId, Title, State
  &$expand=Parent($select=WorkItemId, Title, State)

✔️ Определите фильтр RevisedDateSK при запросе данных исторических рабочих элементов (WorkItemRevisions или WorkItemSnapshot наборов сущностей)

При запросе исторических данных вероятность того, что вы заинтересованы в последнем периоде (например, 30 дней, 90 дней). Из-за реализации сущностей рабочих элементов удобно писать такие запросы, чтобы получить большую производительность. Каждый раз, когда вы обновляете рабочий элемент, он создает новую редакцию и записывает это действие в System.RevisedDate поле, что делает его идеальным для фильтров журнала.

В Аналитике измененная дата отображается в RevisedDate свойствах (Edm.DateTimeOffset) и RevisedDateSK (Edm.Int32). Для обеспечения оптимальной производительности используйте последний. Это суррогатный ключ даты и представляет дату создания редакции или ее null наличие для активных неполных редакций. Если вы хотите все даты, начиная с инклюзивного {startDate} , добавьте следующий фильтр в запрос.

RevisedDateSK eq null or RevisedDateSK gt {startDateSK}

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

https://analytics.dev.azure.com/{OrganizationName}/_odata/v1.0/WorkItemSnapshot?
  $apply=
    filter(DateSK gt 20200101)/
    filter(RevisedDateSK eq null or RevisedDateSK gt 20200101)/
    groupby(
      (DateValue), 
      aggregate($count as Count)
    )

Примечание.

Мы придумали эту рекомендацию, когда мы работали над мини-приложениями Burndown. Первоначально мы определили фильтры только для DateSK того, чтобы получить этот запрос, чтобы масштабироваться хорошо для организаций с большими наборами данных. Во время профилирования запросов мы заметили, что DateSK не фильтрует исправления. Только после добавления фильтра RevisedDateSK мы смогли получить большую производительность в масштабе.
~ Группа продуктов

✔️ Использование еженедельных или ежемесячных моментальных снимков для запросов тренда, охватывающих длительный период времени

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

Это можно сделать с другими выражениями фильтра, чтобы удалить дни, которые не завершают указанную неделю или месяц. IsLastDayOfPeriod Используйте свойство, которое было добавлено в Аналитику с учетом этого сценария. Это свойство имеет тип Microsoft.VisualStudio.Services.Analytics.Model.Period и может определить, заканчивается ли день в разные периоды (например, недели, месяцы и т. д.).

<EnumType Name="Period" IsFlags="true">
  <Member Name="None" Value="0"/>
  <Member Name="Day" Value="1"/>
  <Member Name="WeekEndingOnSunday" Value="2"/>
  <Member Name="WeekEndingOnMonday" Value="4"/>
  <Member Name="WeekEndingOnTuesday" Value="8"/>
  <Member Name="WeekEndingOnWednesday" Value="16"/>
  <Member Name="WeekEndingOnThursday" Value="32"/>
  <Member Name="WeekEndingOnFriday" Value="64"/>
  <Member Name="WeekEndingOnSaturday" Value="128"/>
  <Member Name="Month" Value="256"/>
  <Member Name="Quarter" Value="512"/>
  <Member Name="Year" Value="1024"/>
  <Member Name="All" Value="2047"/>
</EnumType>

Так как Microsoft.VisualStudio.Services.Analytics.Model.Period определяется как перечисление с флагами, используйте оператор OData has и укажите полный тип для литералы периода.

IsLastDayOfPeriod has Microsoft.VisualStudio.Services.Analytics.Model.Period'Month'

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

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemSnapshot?
  $apply=
    filter(IsLastDayOfPeriod has Microsoft.VisualStudio.Services.Analytics.Model.Period'Month')/
    groupby(
      (DateValue), 
      aggregate($count as Count)
    )

✔️ Tags Использование свойства коллекции для рабочих элементов при фильтрации по тегам

Свойство можно использовать TagNames с contains функцией, чтобы определить, помечена ли работа определенным тегом. Однако этот подход может привести к медленным запросам, особенно при проверке нескольких тегов одновременно. Для повышения производительности и результатов используйте Tags вместо этого свойство навигации.

Например, следующий запрос получает все рабочие элементы, помеченные тегом {tag}.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq '{tag}')
  &$select=WorkItemId, Title, State

Этот подход также отлично подходит, если необходимо отфильтровать несколько тегов. Например, следующий запрос возвращает все рабочие элементы, помеченные или помеченные как {tag1} {tag2}

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq {tag1} or t/TagName eq {tag2})
  &$select=WorkItemId, Title, State

Эти фильтры также можно объединить с оператором "and". Например, следующий запрос получает все рабочие элементы, помеченные как и те {tag1} , и другие. {tag2}

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq {tag1}) and Tags/any(t:t/TagName eq {tag2})
  &$select=WorkItemId, Title, State

✔️ Свойство DO use TagNames , если вы хотите отобразить все теги в рабочем элементе в виде текста

Свойство Tagsнавигации, описанное в предыдущем разделе, отлично подходит для фильтрации. Однако при работе с ними возникают некоторые проблемы, так как запрос возвращает теги в вложенной коллекции. Модель данных также содержит примитивное TagNames свойство (Edm.String), которое мы добавили для упрощения сценариев потребления тегов. Это одно текстовое значение, содержащее список всех тегов, объединенных с запятой "; " разделитель. Используйте это свойство, когда все, что вам нужно, отображает теги вместе. Его можно объединить с фильтрами тегов, описанными ранее.

Например, следующий запрос получает все рабочие элементы, помеченные тегом {tag}. Он возвращает идентификатор рабочего элемента, название, состояние и текстовое представление объединенных тегов.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq '{tag}')
  &$select=WorkItemId, Title, State, TagNames

Внимание

Свойство TagNames имеет ограничение длины 1024 символов. Он содержит набор тегов, подходящих в пределах этого предела. Если рабочий элемент имеет много тегов или тегов очень длинны, то TagNames вместо этого не следует использовать полное набор Tag и свойство навигации.

❌ Не используйте tolower и toupper функции для сравнения без учета регистра

Если вы работали с другими системами, вы можете ожидать использования tolower или toupper функций для сравнения без учета регистра. При использовании Аналитики все сравнения строк не учитывают регистр по умолчанию, поэтому не нужно применять какие-либо функции для явной обработки.

Например, следующий запрос получает все рабочие элементы, помеченные как "QUALITY", "quality", или любое другое сочетание этого слова.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq 'quality')
  &$select=WorkItemId, Title, State, TagNames

❌ Не используйте несвязанное расширение с $levels=max

OData имеет возможность расширить все уровни иерархической структуры. Например, отслеживание рабочих элементов содержит некоторые сущности, в которых можно применить неограниченное расширение. Эта операция работает только для организаций с небольшим объемом данных. Он не хорошо масштабируется для больших наборов данных. Не используйте его вообще, если:

  • Вы работаете с большими наборами данных.
  • Вы разрабатываете мини-приложение и не контролируете установку мини-приложения.

✔️ Использование разбиения на страницах на основе сервера

Если вы запрашиваете слишком большой набор для отправки в одном ответе, аналитика применяет разбиение по страницам. Ответ включает только частичный набор и ссылку, которая позволяет получить следующий частичный набор элементов. Эта стратегия описана в спецификации OData — OData версии 4.0. Часть 1. Протокол — серверное разбиение на страницах. Позволяя службе управлять разбиением по страницам, вы получаете лучшую производительность, так как skiptoken она тщательно разрабатывается для каждой сущности, чтобы быть максимально эффективным.

Ссылка на следующую страницу включена @odata.nextLink в свойство.

{
  "@odata.context": "https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/$metadata#WorkItems(*)",
  "value": [
    ...
  ],
  "@odata.nextLink":"https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?$skiptoken=12345"}

Примечание.

Большинство существующих клиентов OData могут автоматически обрабатывать разбиение на страницах на основе сервера. Например, эта стратегия уже используется следующими средствами: Power BI, SQL Server Integration Services и Фабрика данных Azure.

❌ Не используйте $top и $skip не запрашивайте параметры для реализации разбиения по страницам, управляемым клиентом

С другими ИНТЕРФЕЙСами REST API, возможно, вы реализовали разбиение на страницах на основе клиента и $top $skip параметры запроса. Не используйте их с аналитикой. Существует несколько проблем с этим подходом и производительностью является одним из них. Вместо этого примите стратегию разбиения страниц на основе сервера, описанную в предыдущем разделе.

✔️ $top Использование параметра запроса для ограничения количества записей

Параметр $top запроса не рекомендуется использовать только при использовании вместе $skip. Если в вашем сценарии создания отчетов требуется только подмножество записей (например, пример), можно использовать $top параметр запроса. Кроме того, если вам нужно ранжировать записи в соответствии с некоторыми критериями, вы всегда должны использовать $top в сочетании с $orderby стабильным результатом с высоким рейтингом записей.

✔️ Рассмотрите возможность написания запроса для возврата небольшого количества записей

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

✔️ Рассмотрите возможность ограничения количества выбранных свойств минимальной

Некоторые администраторы проектов сильно настраивают свои процессы путем добавления настраиваемых полей. Высокая настройка может привести к проблемам с производительностью при получении всех доступных столбцов в широких сущностях (например, WorkItems). Аналитика основана на технологии Index Columnstore . Это означает, что данные являются как хранилищем, так и обработкой запросов, основаны на столбцах. Таким образом, чем больше свойств, ссылающихся на запросы, тем дороже, чем для обработки. Всегда стремимся ограничить набор свойств в запросах тем, что вы действительно заботитесь в сценарии создания отчетов.

✔️ РАССМОТРИМ фильтрацию по свойствам ключей с суррогатной датой (DateSK суффикс)

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

Например, следующий запрос получает все рабочие элементы, созданные с начала 2020 года.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDateSK ge 20200101

✔️ Рассмотрите возможность фильтрации по суррогатным ключевым столбцам

Если вы хотите отфильтровать данные по значению связанного объекта (например, фильтрация рабочего элемента по имени проекта), у вас всегда есть два варианта. Вы можете использовать свойство навигации (например, Project/ProjectName) или записать суррогатный ключ вперед и использовать его непосредственно в запросе (например, ProjectSK).

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

Например, следующие фильтры WorkItems запросов используют ProjectSK свойство, а не Project/ProjectName свойство навигации.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=ProjectSK eq {projectSK}

❌ИЗБЕГАЙТЕ использования Parentсвойств Childrenили Revisions свойств в $filter предложениях или $expand

Рабочие элементы — это самые дорогие сущности в модели данных. У них есть несколько свойств навигации, которые можно использовать для доступа к связанным рабочим элементам: Parent, , ChildrenRevisions. Однако каждый раз, когда вы используете их в запросе, ожидается снижение производительности. Всегда спрашивайте, действительно ли вам нужно одно из этих свойств и потенциально обновите дизайн.

Например, вместо расширения Parentможно получить больше рабочих элементов и использовать ParentWorkItemId свойство для восстановления полной иерархии на стороне клиента. Проводите такую оптимизацию на основе регистра.

✔️ Рассмотрите возможность передачи VSTS.Analytics.MaxSize предпочтений в заголовке

При выполнении запроса вы не знаете количество записей, возвращаемых запросом. Отправьте другой запрос с агрегатами или следуйте всем следующим ссылкам и получите весь набор данных. Аналитика учитывает VSTS.Analytics.MaxSize предпочтения, что позволяет быстро завершиться сбоем в тех экземплярах, которые набор данных больше, чем то, что может принять ваш клиент.

Этот параметр полезен в сценариях экспорта данных. Чтобы использовать его, необходимо добавить Prefer заголовок в HTTP-запрос и задать VSTS.Analytics.MaxSize значение, отличное от отрицательного значения. Значение VSTS.Analytics.MaxSize представляет максимальное количество записей, которые можно принять. Если задать значение нулю, используется значение по умолчанию 200 K.

Например, следующий запрос возвращает рабочие элементы, если набор данных меньше или равен 1000 записям.

GET https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems HTTP/1.1
User-Agent: {application}
Prefer: VSTS.Analytics.MaxSize=1000
OData-MaxVersion: 4.0
Accept: application/json;odata.metadata=minimal
Host: analytics.dev.azure.com/{OrganizationName}

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

Результат запроса содержит 1296 строк и превышает максимальный допустимый размер 1000. Уменьшите количество записей, применяя дополнительные фильтры.

Сведения о настройке максимального размера страницы см. в свойстве ODataPreferenceHeader.MaxPageSize.

Рекомендации по стилю запросов

✔️ $count Использование виртуального свойства в методах агрегирования

Некоторые сущности предоставляют Count свойство. Они упрощают некоторые сценарии создания отчетов при экспорте данных в другое хранилище. Однако эти столбцы не следует использовать в агрегатах в запросах OData. Вместо этого используйте виртуальное $count свойство.

Например, следующий запрос возвращает общее количество рабочих элементов.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $apply=aggregate($count as Count)

❌ Избегайте использования $count виртуального свойства в сегменте URL-адресов

Хотя стандарт OData позволяет использовать $count виртуальное свойство для наборов сущностей (например, _odata/v1.0/WorkItems/$countне все клиенты могут правильно интерпретировать ответ. Поэтому вместо этого рекомендуется использовать агрегаты.

Например, следующий запрос возвращает общее количество рабочих элементов.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $apply=aggregate($count as Count)

✔️ Рассмотрите возможность использования псевдонимов параметров для разделения переменных частей запроса

Псевдонимы параметров предоставляют элегантное решение для извлечения переменных, таких как значения параметров из основного текста запроса. Их можно использовать в выражениях, которые оценивают:

  • Примитивное значение
  • Сложное значение
  • Коллекция примитивных или сложных значений.

Дополнительные сведения см. в статье OData версии 4.0. Часть 2. Соглашения о URL-адресах — псевдонимы 5.1.1.13. Параметры полезны, если текст запроса используется в качестве шаблона, который можно создать с помощью предоставленных пользователем значений.

Например, следующий запрос использует @createdDateSK параметр для разделения значения от выражения фильтра.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDateSK ge @createdDateSK
  &$select=WorkItemId, Title, State
  &@createdDateSK=20200101

❌ ИЗБЕГАЙТЕ смешивания $apply и $filter предложений в одном запросе

Если вы хотите добавить filter запрос, у вас есть два варианта. Это можно сделать с предложением $filter или сочетанием $apply=filter() . Каждый из этих вариантов работает хорошо самостоятельно, но объединение их вместе может привести к некоторым непредвиденным результатам.

Несмотря на ожидание, может быть, OData четко определяет порядок оценки. Кроме того, предложение $apply имеет приоритет над $filter. По этой причине следует выбрать один или другой, но избежать этих двух параметров фильтра в одном запросе. Важно, если запросы создаются автоматически.

Например, следующий запрос сначала фильтрует рабочие элементы StoryPoint gt 5, агрегирует результаты по пути и, наконец, фильтрует результат по StoryPoints gt 2. В этом порядке оценки запрос всегда возвращает пустой набор.

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=StoryPoints gt 2
  $apply=
    filter(StoryPoints gt 5)/
    groupby(
      (Area/AreaPath),
      aggregate(StoryPoints with sum as StoryPoints)
    )

✔️ Рассмотрите возможность структурирования запроса в соответствии с порядком оценки OData

Так как сочетание $apply и filter предложения в одном запросе могут привести к потенциальной путанице, рекомендуется структурировать предложения запросов для сопоставления порядка оценки.

  1. $apply
  2. $filter
  3. $orderby
  4. $expand
  5. $select
  6. $skip
  7. $top

✔️ Рассмотрите возможность проверки возможностей OData, описанных в заметках метаданных

Если вы не уверены, какие возможности OData поддерживает Analytics, вы можете искать заметки в метаданных. Технический комитет по протоколу OData (OData) OASIS в репозитории TC GitHub содержит список доступных заметок.

Например, список поддерживаемых функций фильтра доступен в Org.OData.Capabilities.V1.FilterFunctions заметке в контейнере сущностей.

<Annotation Term="Org.OData.Capabilities.V1.FilterFunctions">
  <Collection>
  <String>contains</String>
  <String>endswith</String>
  [...]
  </Collection>
</Annotation>

Еще одна полезная заметка— Org.OData.Capabilities.V1.ExpandRestrictionsэто описание свойств навигации, которые нельзя использовать в предложении $expand . Например, следующая заметка объясняет, что Revisions в наборе WorkItems сущностей невозможно развернуть.

<EntitySet Name="WorkItems" EntityType="Microsoft.VisualStudio.Services.Analytics.Model.WorkItem">
  [...]
  <Annotation Term="Org.OData.Capabilities.V1.ExpandRestrictions">
    <Record>
      <PropertyValue Property="Expandable" Bool="true"/>
      <PropertyValue Property="NonExpandableProperties">
        <Collection>
          <NavigationPropertyPath>Revisions</NavigationPropertyPath>
        </Collection>
      </PropertyValue>
    </Record>
  </Annotation>
</EntitySet>