Istruzioni per le richieste con limitazioni in Azure Resource Graph

Quando si pianifica un uso programmatico e frequente dei dati di Azure Resource Graph, è necessario considerare il modo in cui la limitazione influisce sui risultati delle query. La modifica del modo in cui i dati vengono richiesti può aiutare l'utente e l'organizzazione a evitare limitazioni e mantenere il flusso di dati tempestivi sulle risorse di Azure.

Questo articolo illustra quattro aree e modelli correlati alla creazione di query in Azure Resource Graph:

  • Informazioni sulle intestazioni di limitazione.
  • Raggruppamento di query.
  • Distribuzione di query.
  • Effetto dell'impaginazione.

Informazioni sulle intestazioni di limitazione

Azure Resource Graph alloca un numero di quota per ogni utente in base a un intervallo di tempo. Ad esempio, un utente può inviare al massimo 15 query all'interno di ogni intervallo di 5 secondi senza limitazioni. Il valore della quota è determinato da molti fattori ed è soggetto a modifiche.

In ogni risposta alle query, Azure Resource Graph aggiunge due intestazioni di limitazione:

  • x-ms-user-quota-remaining (int): quota di risorse rimanenti per l'utente. Questo valore è associato al conteggio delle query.
  • x-ms-user-quota-resets-after (hh:mm:ss): durata temporale fino alla reimpostazione del consumo della quota dell'utente.

Quando un'entità di sicurezza ha accesso a più di 10.000 sottoscrizioni all'interno dell'ambito di query del tenant o del gruppo di gestione, la risposta è limitata alle prime 10.000 sottoscrizioni e l'intestazione x-ms-tenant-subscription-limit-hit viene restituita come true.

Per illustrare il funzionamento delle intestazioni, viene ora esaminata una risposta di query con l'intestazione e i valori di x-ms-user-quota-remaining: 10 e x-ms-user-quota-resets-after: 00:00:03.

  • Nei prossimi 3 secondi, è possibile inviare al massimo 10 query senza limitazioni.
  • In 3 secondi, i valori di x-ms-user-quota-remaining e x-ms-user-quota-resets-after vengono reimpostati rispettivamente su 15 e 00:00:05.

Per un esempio dell'uso delle intestazioni per il backoff delle richieste di query, vedere l'esempio in Query in parallelo.

Raggruppamento di query

Il raggruppamento delle query in base alla sottoscrizione, al gruppo di risorse o a una singola risorsa è più efficiente della parallelizzazione delle query. Il costo della quota di una query di dimensioni maggiori è spesso inferiore al costo della quota di molte query piccole e mirate. È consigliabile che le dimensioni del gruppo siano inferiori a 300.

  • Esempio di approccio poco ottimizzato.

    // 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);
    
    // ...
    }
    
  • Esempio di approccio di raggruppamento ottimizzato.

    // 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);
    
      // ...
    }
    
  • Esempio di un approccio di raggruppamento ottimizzato per ottenere più risorse in un'unica query.

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

Distribuzione di query

A causa del modo in cui viene applicata la limitazione, è consigliabile sfalsare le query. Ad esempio, anziché inviare contemporaneamente 60 query, suddividere le query in quattro finestre di 5 secondi.

  • Pianificazione delle query non pianificate.

    Conteggio query 60 0 0 0
    Intervallo di tempo (secondi) 0-5 5-10 10-15 15-20
  • Pianificazione di query sfalsate.

    Conteggio query 15 15 15 15
    Intervallo di tempo (secondi) 0-5 5-10 10-15 15-20

Il codice seguente è un esempio di rispetto delle intestazioni di limitazione durante l'esecuzione di query su 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);
    }
}

Query in parallelo

Anche se è consigliabile eseguire il raggruppamento piuttosto che la parallelizzazione, in alcuni casi non è possibile raggruppare facilmente le query. In questi casi, è possibile eseguire query in Azure Resource Graph inviando più query in modo parallelo. Nell'esempio seguente viene illustrato come eseguire il backoff in base alle intestazioni di limitazione.

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

Paginazione

Poiché Azure Resource Graph restituisce un massimo di 1.000 voci in una singola risposta di query, potrebbe essere necessario impaginare le query per ottenere il set di dati completo desiderato. Tuttavia, alcuni client di Azure Resource Graph gestiscono l'impaginazione in modo diverso rispetto ad altri.

Quando si usa l’SDK ResourceGraph, è necessario gestire la paginazione passando lo skip token restituito dalla risposta della query precedente alla query impaginata successiva. Questo significa che è necessario raccogliere i risultati di tutte le chiamate impaginate e combinarli insieme alla fine. In questo caso, ogni query impaginata inviata prende una quota di query.

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.
}

Ancora in fase di limitazione?

Se sono state usate le raccomandazioni di questo articolo e le query di Azure Resource Graph sono ancora limitate, contattare il team di Azure Resource Graph. Il team supporta Azure Resource Graph, ma non supporta la limitazione delle richieste di Microsoft Graph.

Specificare questi dettagli quando si contatta il team di Azure Resource Graph:

  • Il caso d'uso specifico e le esigenze aziendali per cui è necessario un limite superiore per la limitazione.
  • Il numero di risorse a cui si ha accesso. Quanti di essi vengono restituiti da una singola query?
  • I tipi di risorse rilevanti.
  • Il modello di query usato. X query per Y secondi, e così via.

Passaggi successivi