Conseils pour les requêtes limitées dans Azure Resource Graph

Lors de la création programmatique et de l’utilisation fréquente des données Azure Resource Graph, vous devez prendre en compte la façon dont la limitation affecte les résultats des requêtes. Changer la manière dont les données sont demandées peut vous aider, vous et votre organisation, à éviter toute limitation et à maintenir le flux de données en temps voulu concernant vos ressources Azure.

Cet article traite de quatre domaines et modèles en rapport avec la création de requêtes dans Azure Resource Graph :

  • Comprenez les en-têtes de limitation.
  • Regroupement des requêtes.
  • Échelonnement des requêtes.
  • L’effet de la pagination.

Présentation des en-têtes de limitation

Azure Resource Graph alloue un quota à chaque utilisateur en fonction d’une fenêtre de temps. Par exemple, un utilisateur peut envoyer au maximum 15 requêtes durant chaque fenêtre de cinq secondes sans être limité. La valeur de quota est déterminée par de nombreux facteurs et est susceptible de changer.

Dans chaque réponse de requête, Azure Resource Graph ajoute deux en-têtes de limitation :

  • x-ms-user-quota-remaining (int) : quota de ressources restant pour l'utilisateur. Cette valeur correspond au nombre de requêtes.
  • x-ms-user-quota-resets-after (hh:mm:ss) : délai à attendre avant la réinitialisation de la consommation du quota d’un utilisateur.

Quand un principal de sécurité a accès à plus de 10 000 abonnements au sein du client ou du groupe d’administration étendue de la requête, la réponse est limitée aux 10 000 premiers abonnements et l’en-tête x-ms-tenant-subscription-limit-hit est retourné en tant que true.

Pour illustrer le fonctionnement des en-têtes, jetons un œil à une réponse de requête qui a les en-têtes et valeurs x-ms-user-quota-remaining: 10 et x-ms-user-quota-resets-after: 00:00:03.

  • Pendant les trois secondes suivantes, 10 requêtes au plus peuvent être envoyées sans limitation.
  • En trois secondes, les valeurs de x-ms-user-quota-remaining et x-ms-user-quota-resets-after sont réinitialisées respectivement vers les valeurs 15 et 00:00:05.

Pour voir un exemple d’utilisation des en-têtes pour effectuer une interruption lors des demandes d’interrogation, consultez l’exemple fourni dans Interroger en parallèle.

Regroupement des requêtes

Regrouper les requêtes d’après l’abonnement, le groupe de ressources ou la ressource est plus efficace que paralléliser les requêtes. Le coût du quota d’une requête plus grande est souvent inférieur à celui de nombreuses requêtes petites et ciblées. Nous vous recommandons de choisir une taille de groupe inférieure à 300.

  • Exemple d’approche mal optimisée.

    // 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);
    
    // ...
    }
    
  • Exemple d’approche de regroupement optimisée.

    // 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);
    
      // ...
    }
    
  • Exemple d’approche de regroupement optimisée afin d’obtenir plusieurs ressources dans une même requête.

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

Échelonnement des requêtes

En raison de la manière dont la limitation est appliquée, nous vous recommandons d’échelonner les requêtes. Par exemple, au lieu d’envoyer 60 requêtes en même temps, échelonnez-les dans quatre fenêtres de cinq secondes.

  • Planification de requêtes sans échelonnement.

    Nombre de requêtes 60 0 0 0
    Intervalle de temps (sec) 0-5 5-10 10-15 15-20
  • Planification de requêtes avec échelonnement.

    Nombre de requêtes 15 15 15 15
    Intervalle de temps (sec) 0-5 5-10 10-15 15-20

Le code suivant présente un exemple montrant comment respecter les en-têtes de limitation lors de l’interrogation d’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);
    }
}

Interrogation en parallèle

Bien que le regroupement soit préférable à la parallélisation, il arrive parfois que les requêtes soient difficiles à regrouper. Dans ces cas-là, vous souhaitez peut-être interroger Azure Resource Graph en envoyant plusieurs requêtes de manière parallèle. L’exemple suivant montre comment effectuer une interruption en fonction des en-têtes de limitation.

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

Pagination

Azure Resource Graph retournant au maximum 1 000 entrées dans une unique réponse de requête, vous devrez peut-être paginer vos requêtes afin d’obtenir le jeu de données complet que vous voulez. Mais certains clients Azure Resource Graph gèrent la pagination différemment des autres.

Quand vous utilisez le SDK ResourceGraph, vous devez gérer la pagination en passant le jeton d’évitement ($skiptoken) retourné à partir de la réponse de requête précédente à la requête paginée suivante. Cette conception signifie que vous devez recueillir les résultats de tous les appels paginés et les combiner à la fin. Dans ce cas, chaque requête paginée que vous envoyez prend un quota de requête.

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

Vous rencontrez toujours des limitations ?

Si vous avez utilisé les recommandations de cet article et si vos requêtes Azure Resource Graph sont toujours limitées, contactez l’équipe Azure Resource Graph. L’équipe prend en charge Azure Resource Graph, mais pas la limitation affectant Microsoft Graph.

Fournissez ces informations lorsque vous contactez l’équipe Azure Resource Graph :

  • Votre cas d’usage spécifique et les axes stratégiques qui imposent de disposer d’une limite plus élevée.
  • À combien de ressources avez-vous accès ? Combien d’entre elles sont retournées par une requête unique ?
  • Quels sont les types de ressources qui vous intéressent ?
  • Quel est votre modèle de requête ? X requêtes toutes les Y secondes, etc.

Étapes suivantes