Diretrizes para solicitações limitadas no Azure Resource Graph

Ao criar o uso programático e frequente de dados do Gráfico de Recursos do Azure, deve-se considerar 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.
  • Agrupando consultas.
  • Consultas estapafúrdias.
  • 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 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 uso dos cabeçalhos para recuar em solicitações de consulta, consulte o exemplo 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 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);
    
      // ...
    }
    

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.

  • Agenda de consulta não escalonada.

    Contagem de consultas 60 0 0 0
    Intervalo de tempo (s) 0-5 5-10 10-15 15–20
  • Agenda de consulta 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 respeito aos cabeçalhos de limitação ao consultar o Gráfico de Recursos do Azure.

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 detrimento da paralelização, há ocasiões em que as consultas não podem ser agrupadas facilmente. 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 Gráfico de Recursos do Azure 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 Gráfico de Recursos do Azure lidam com a paginação de forma diferente de outros.

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 enviada recebe 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 Gráfico de Recursos do Azure ainda estão sendo limitadas, entre em contato com a equipe do Gráfico de Recursos do Azure. A equipe dá suporte ao Gráfico de Recursos do Azure, mas não oferece suporte à limitação do Microsoft Graph.

Forneça esses detalhes ao entrar em contato com a equipe do Gráfico de Recursos do Azure:

  • 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 em 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