Diretrizes para solicitações limitadas no Azure Resource Graph

Ao criar um uso programático e frequente dos dados do Azure Resource Graph, deve-se levar em conta como a limitação afeta os resultados das consultas. Mudar a maneira como os dados são solicitados pode ajudar a evitar a limitação e manter o fluxo de dados oportunos sobre os recursos do Azure.

Este artigo aborda quatro áreas e padrões relacionados à criação de consultas no Azure Resource Graph:

  • Entenda os cabeçalhos de limitação.
  • Como agrupar consultas.
  • Consultas de escalonamento.
  • O efeito da paginação.

Noções básicas de cabeçalhos de limitação

O Azure Resource Graph aloca um número de cota para cada usuário com base em uma janela de tempo. Por exemplo, um usuário pode enviar no máximo 15 consultas dentro de cada janela de 5 segundos sem ser limitado. O valor da cota é determinado por muitos fatores e está sujeito a mudanças.

Em cada resposta de consulta, o Azure Resource Graph adiciona dois cabeçalhos de limitação:

  • x-ms-user-quota-remaining (int): A cota de recursos que resta para o usuário. Esse valor é mapeado para a contagem de consultas.
  • x-ms-user-quota-resets-after (hh:mm:ss): O tempo até que o consumo da cota do usuário seja redefinido.

Quando uma entidade de segurança tem acesso a mais de 10 mil assinaturas no escopo de consulta do locatário ou grupo de gerenciamento, a resposta é limitada às primeiras 10 mil assinaturas e o cabeçalho x-ms-tenant-subscription-limit-hit retorna como true.

Para ilustrar como os cabeçalhos funcionam, examinaremos a resposta de uma consulta que tem o cabeçalho e os valores de x-ms-user-quota-remaining: 10 e x-ms-user-quota-resets-after: 00:00:03.

  • Nos próximos 3 segundos, no máximo 10 consultas poderão ser enviadas sem serem limitadas.
  • Em 3 segundos, os valores de x-ms-user-quota-remaining e x-ms-user-quota-resets-after serão redefinidos para 15 e 00:00:05, respectivamente.

Para ver um exemplo de como usar os cabeçalhos para retirada em solicitações de consulta, confira a amostra em Consulta em paralelo.

Agrupar consultas

O agrupamento de consultas por assinatura, grupo de recursos ou recurso individual é mais eficiente do que paralelizar consultas. O custo de cota de uma consulta maior geralmente é menor do que o custo de cota de muitas consultas pequenas e direcionadas. É recomendável que o tamanho do grupo seja inferior a 300.

  • Exemplo de uma abordagem com otimização insuficiente.

    // 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);
    
    // ...
    }
    
  • Exemplo de uma abordagem de agrupamento otimizada.

    // 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);
    
      // ...
    }
    
  • Exemplo de uma abordagem de agrupamento otimizada para obter vários recursos em uma consulta.

    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);
    
      // ...
    }
    

Consultas de escalonamento

Devido à maneira como a limitação é imposta, recomendamos que as consultas sejam escalonadas. Por exemplo, em vez de enviar 60 consultas ao mesmo tempo, escalone as consultas em quatro janelas de 5 segundos.

  • Programação de consultas não escalonada.

    Contagem de consultas 60 0 0 0
    Intervalo de tempo (s) 0-5 5-10 10-15 15–20
  • Programação de consultas escalonada.

    Contagem de consultas 15 15 15 15
    Intervalo de tempo (s) 0-5 5-10 10-15 15–20

O código a seguir é um exemplo de como respeitar os cabeçalhos de limitação ao consultar o 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);
    }
}

Consultar em paralelo

Embora o agrupamento seja recomendado em detrimento da paralelização, há ocasiões em que as consultas não podem ser agrupadas facilmente. Nesses casos, você talvez queira consultar o Azure Resource Graph enviando várias consultas em paralelo. O exemplo a seguir mostra como fazer uma retirada baseada em cabeçalhos de limitação.

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);
        }
    }
}

Paginação

Como o Azure Resource Graph retorna um máximo de 1.000 entradas em uma única resposta de consulta, talvez você precise paginar suas consultas para obter o conjunto de dados completo que você quer. Mas alguns clientes do Azure Resource Graph lidam com a paginação de uma maneira diferente.

Ao usar o SDK do ResourceGraph, é preciso lidar com a paginação passando o token de omissão retornado na resposta da consulta anterior para a próxima consulta paginada. Isso significa que é preciso coletar resultados de todas as chamadas paginadas e combiná-las no final. Nesse caso, cada consulta paginada que você envia consome uma cota de consulta.

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.IsNullOrEmpty(azureOperationResponse.Body.SkipToken))
{
  queryRequest.Options ??= new QueryRequestOptions();
  queryRequest.Options.SkipToken = azureOperationResponse.Body.SkipToken;
  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.
}

Continua sendo limitado?

Se você usou as recomendações deste artigo e as consultas do Azure Resource Graph continuam sendo limitadas, entre em contato com a equipe do Azure Resource Graph. A equipe dá suporte ao Azure Resource Graph, mas não dá suporte à limitação do Microsoft Graph.

Forneça os seguintes detalhes quando entrar em contato com a equipe do Azure Resource Graph:

  • Seu caso de uso específico e o driver de negócios precisam de um limite de limitação mais alto.
  • A quantos recursos você tem acesso? Quantos deles são retornados por uma única consulta?
  • Em quais tipos de recursos você está interessado?
  • Qual é seu padrão de consulta? X consultas por Y segundos e assim por diante.

Próximas etapas