Рекомендации по работе с регулируемыми запросами в Azure Resource Graph

При создании средств для активного программного доступа к данным Azure Resource Graph следует учитывать, как на результаты запросов влияет регулирование. Изменив процесс запроса данных, вы и ваша организация сможете избежать регулирования и получать поток актуальных данных о ресурсах Azure.

В этой статье рассматриваются четыре области и шаблона, связанных с созданием запросов к Azure Resource Graph:

  • общие сведения о заголовках регулирования;
  • группирование запросов;
  • поочередная отправка запросов;
  • влияние разбиения на страницы.

Общие сведения о заголовках регулирования

Azure Resource Graph выделяет определенную квоту для каждого пользователя на определенный период времени. Например, пользователь не может отправить более 15 запросов в течение 5 секунд, иначе применяется регулирование. Значение квоты определяется множеством факторов и может изменяться.

В каждый ответ на запрос Azure Resource Graph добавляет следующие два заголовка регулирования:

  • x-ms-user-quota-remaining (целое число): оставшаяся квота ресурсов для пользователя. Это значение соответствует количеству запросов.
  • x-ms-user-quota-resets-after (чч:мм:сс): время, оставшееся до сброса квоты потребления этого пользователя.

Если субъект безопасности имеет доступ к более чем 5 000 подпискам в области запросовклиента или группы управления, ответ ограничен первыми 5 000 подписками, а заголовок возвращается как true .

Чтобы продемонстрировать работу этих заголовков, давайте рассмотрим ответ на запрос со следующими значениями заголовков: x-ms-user-quota-remaining: 10 и x-ms-user-quota-resets-after: 00:00:03.

  • Это означает, что за следующие 3 секунды можно без регулирования отправить не более 10 запросов.
  • По прошествии этих трех секунд значения x-ms-user-quota-remaining и x-ms-user-quota-resets-after будут сброшены на 15 и 00:00:05 соответственно.

Пример использования заголовков для откладывания запросов см. в разделе Запрос в параллельном режиме.

Группирование запросов

Группирование запросов по подпискам, группам ресурсов или конкретному ресурсу работает более эффективно, чем параллельное выполнение. Более крупный запрос часто меньше влияет на квоту, чем множество мелких и конкретных запросов. Мы рекомендуем создавать группы размером не более 300 элементов.

  • Пример плохо оптимизированного подхода

    // NOT RECOMMENDED
    var header = /* your request header */
    var subscriptionIds = /* A big list of subscriptionIds */
    
    foreach (var subscriptionId in subscriptionIds)
    {
        var userQueryRequest = new QueryRequest(
            subscriptions: new[] { subscriptionId },
            query: "Resoures | project name, type");
    
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);
    
    // ...
    }
    
  • Пример №1 по оптимизации подхода методом группирования

    // RECOMMENDED
    var header = /* your request header */
    var subscriptionIds = /* A big list of subscriptionIds */
    
    const int groupSize = 100;
    for (var i = 0; i <= subscriptionIds.Count / groupSize; ++i)
    {
        var currSubscriptionGroup = subscriptionIds.Skip(i * groupSize).Take(groupSize).ToList();
        var userQueryRequest = new QueryRequest(
            subscriptions: currSubscriptionGroup,
            query: "Resources | project name, type");
    
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);
    
      // ...
    }
    
  • Пример №2 по оптимизации подхода методом группирования для получения нескольких ресурсов в одном запросе

    Resources | where id in~ ({resourceIdGroup}) | project name, type
    
    // RECOMMENDED
    var header = /* your request header */
    var resourceIds = /* A big list of resourceIds */
    
    const int groupSize = 100;
    for (var i = 0; i <= resourceIds.Count / groupSize; ++i)
    {
        var resourceIdGroup = string.Join(",",
            resourceIds.Skip(i * groupSize).Take(groupSize).Select(id => string.Format("'{0}'", id)));
        var userQueryRequest = new QueryRequest(
            subscriptions: subscriptionList,
            query: $"Resources | where id in~ ({resourceIdGroup}) | project name, type");
    
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);
    
      // ...
    }
    

Поочередная отправка запросов

С учетом того, как применяется регулирование, есть смысл отправлять запросы поочередно. Например, лучше отправлять не 60 запросов сразу, а поэтапно в четырех 5-секундных окнах:

  • Расписание без применения очередности запросов

    Число запросов 60 0 0 0
    Интервал времени (с) 0-5 5-10 10-15 15–20
  • Расписание с применением очередности запросов

    Число запросов 15 15 15 15
    Интервал времени (с) 0-5 5-10 10-15 15–20

Ниже приведен пример, в котором учитываются заголовки регулирования при запросах к Azure Resource Graph:

while (/* Need to query more? */)
{
    var userQueryRequest = /* ... */
    // Send post request to Azure Resource Graph
    var azureOperationResponse = await this.resourceGraphClient
        .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
        .ConfigureAwait(false);

    var responseHeaders = azureOperationResponse.response.Headers;
    int remainingQuota = /* read and parse x-ms-user-quota-remaining from responseHeaders */
    TimeSpan resetAfter = /* read and parse x-ms-user-quota-resets-after from responseHeaders */
    if (remainingQuota == 0)
    {
        // Need to wait until new quota is allocated
        await Task.Delay(resetAfter).ConfigureAwait(false);
    }
}

Параллельная отправка запросов

Хотя мы рекомендуем чаще применять группирование, чем распараллеливание, в некоторых случаях группирование выполнить сложно. В таких случаях вы можете отправлять несколько запросов к Azure Resource Graph параллельно. Ниже приведен пример откладывания запросов с учетом заголовков регулирования для такого сценария:

IEnumerable<IEnumerable<string>> queryGroup = /* Groups of queries  */
// Run groups in parallel.
await Task.WhenAll(queryGroup.Select(ExecuteQueries)).ConfigureAwait(false);

async Task ExecuteQueries(IEnumerable<string> queries)
{
    foreach (var query in queries)
    {
        var userQueryRequest = new QueryRequest(
            subscriptions: subscriptionList,
            query: query);
        // Send post request to Azure Resource Graph.
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(userQueryRequest, header)
            .ConfigureAwait(false);

        var responseHeaders = azureOperationResponse.response.Headers;
        int remainingQuota = /* read and parse x-ms-user-quota-remaining from responseHeaders */
        TimeSpan resetAfter = /* read and parse x-ms-user-quota-resets-after from responseHeaders */
        if (remainingQuota == 0)
        {
            // Delay by a random period to avoid bursting when the quota is reset.
            var delay = (new Random()).Next(1, 5) * resetAfter;
            await Task.Delay(delay).ConfigureAwait(false);
        }
    }
}

Разбиение на страницы

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

  • Пакет SDK для C#

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

    var results = new List<object>();
    var queryRequest = new QueryRequest(
        subscriptions: new[] { mySubscriptionId },
        query: "Resources | project id, name, type");
    var azureOperationResponse = await this.resourceGraphClient
        .ResourcesWithHttpMessagesAsync(queryRequest, header)
        .ConfigureAwait(false);
    while (!string.Empty(azureOperationResponse.Body.SkipToken))
    {
        queryRequest.SkipToken = azureOperationResponse.Body.SkipToken;
        // Each post call to ResourceGraph consumes one query quota
        var azureOperationResponse = await this.resourceGraphClient
            .ResourcesWithHttpMessagesAsync(queryRequest, header)
            .ConfigureAwait(false);
        results.Add(azureOperationResponse.Body.Data.Rows);
    
        // Inspect throttling headers in query response and delay the next call if needed.
    }
    

Не удалось избавиться от регулирования?

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

Укажите следующие сведения:

  • Подробности о вашем варианте использования и бизнес-обоснование для повышения предела регулирования.
  • К какому объему ресурсов у вас есть доступ? Сколько из них возвращаются одним запросом?
  • Какие типы ресурсов вас интересуют?
  • Какой шаблон запросов вы используете? Данные о числе выполненных запросов в секунду и т. п.

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