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

Lors de la création d’une utilisation programmatique et fréquente de 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 :

  • Comprendre les en-têtes de limitation.
  • Regroupement de requêtes.
  • Requêtes décalées.
  • 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.

  • Dans les 3 secondes suivantes, au plus 10 requêtes peuvent être envoyées sans être limitées.
  • En 3 secondes, les valeurs et x-ms-user-quota-remaining sont réinitialisées respectivement 1500:00:05.x-ms-user-quota-resets-after

Pour voir un exemple d’utilisation des en-têtes pour reculer sur les requêtes de requête, consultez l’exemple de requête 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 pour obtenir plusieurs ressources dans une 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, placez les requêtes dans quatre fenêtres de 5 secondes.

  • Planification de requête non mise en place.

    Nombre de requêtes 60 0 0 0
    Intervalle de temps (sec) 0-5 5-10 10-15 15-20
  • Planification des requêtes décalées.

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

Le code suivant est un exemple de respect des 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);
    }
}

Requête 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, vous pouvez interroger Azure Resource Graph en envoyant plusieurs requêtes de manière parallèle. L’exemple suivant montre comment revenir en arrière 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

Étant donné qu’Azure Resource Graph retourne un maximum de 1 000 entrées dans une seule réponse de requête, vous devrez peut-être paginer vos requêtes pour obtenir le jeu de données complet souhaité. Toutefois, 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é 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.
}

Toujours limité ?

Si vous avez utilisé les recommandations de cet article et que 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 ne prend pas en charge la limitation de 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