Orientação para solicitações limitadas no Azure Resource Graph

Ao criar o uso programático e frequente de dados do Azure Resource Graph, deve-se considerar como a limitação afeta os resultados das consultas. Alterar a maneira como os dados são solicitados pode ajudar você e sua organização a evitar ser limitado e manter o fluxo de dados oportunos sobre seus 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.
  • Agrupamento de consultas.
  • Consultas escalonadas.
  • O efeito da paginação.

Compreender os cabeçalhos de limitação

O Azure Resource Graph aloca um número de quota para cada utilizador com base numa janela temporal. Por exemplo, um usuário pode enviar no máximo 15 consultas a cada janela de 5 segundos sem ser limitado. O valor da quota é determinado por muitos fatores e está sujeito a alterações.

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 restante para o usuário. Esse valor é mapeado para a contagem de consultas.
  • x-ms-user-quota-resets-after (hh:mm:ss): A duração do tempo até que o consumo de cota de um usuário seja redefinido.

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

Para ilustrar como os cabeçalhos funcionam, vamos examinar uma resposta de consulta que tenha 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 podem ser enviadas sem serem limitadas.
  • Em 3 segundos, os valores de x-ms-user-quota-remaining e são redefinidos para 15 e 00:00:05x-ms-user-quota-resets-after respectivamente.

Para ver um exemplo de como usar os cabeçalhos para recuar em solicitações de consulta, consulte o exemplo em Consulta em paralelo.

Agrupamento de consultas

Agrupar consultas pela assinatura, grupo de recursos ou recurso individual é mais eficiente do que paralelizar consultas. O custo de quota de uma consulta maior é muitas vezes inferior ao custo de quota de inúmeras consultas pequenas e direcionadas. Recomenda-se que o tamanho do grupo seja inferior a 300.

  • Exemplo de uma abordagem mal otimizada.

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

Escalonamento de consultas

Devido à forma como a limitação é aplicada, 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.

  • Agenda de consulta não escalonada.

    Contagem de consultas 60 0 0 0
    Intervalo de tempo (seg) 0-5 5-10 10-15 15-20
  • Agenda de consulta escalonada.

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

O código a seguir é um exemplo de respeito à limitação de cabeçalhos 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);
    }
}

Consulta em paralelo

Embora o agrupamento seja recomendado em vez da paralelização, há momentos em que as consultas não podem ser facilmente agrupadas. Nesses casos, talvez você queira consultar o Gráfico de Recursos do Azure enviando várias consultas de forma paralela. O exemplo a seguir mostra como recuar com base 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 seja necessário paginar suas consultas para obter o conjunto de dados completo desejado. Mas alguns clientes do Azure Resource Graph lidam com a paginação de forma diferente de outros.

Ao usar o SDK do ResourceGraph, você precisa manipular a paginação passando o token de pulo que está sendo retornado da resposta de consulta anterior para a próxima consulta paginada. Esse design significa que você precisa coletar resultados de todas as chamadas paginadas e combiná-los no final. Nesse caso, cada consulta paginada enviada leva 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.
}

Ainda está sendo estrangulado?

Se você usou as recomendações deste artigo e suas consultas do Azure Resource Graph ainda estão sendo limitadas, entre em contato com a equipe do Azure Resource Graph. A equipa suporta o Azure Resource Graph, mas não suporta a limitação do Microsoft Graph.

Forneça estes detalhes ao entrar em contato com a equipe do Azure Resource Graph:

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

Próximos passos