Guía de solicitudes limitadas en Azure Resource Graph

Al crear datos de Azure Resource Graph mediante programación y con frecuencia, se debe tener en cuenta cómo afecta la limitación a los resultados de las consultas. Cambiar la manera en que se solicitan los datos puede ayudar a evitar la limitación de la organización y mantener el flujo de datos puntuales en relación con los recursos de Azure.

En este artículo se describen cuatro áreas y patrones relacionados con la creación de consultas en Azure Resource Graph:

  • Comprenda los encabezados de limitación.
  • Agrupación de consultas.
  • Consultas sorprendentes.
  • Efecto de la paginación.

Descripción de los encabezados de limitación

Azure Resource Graph asigna un número de cuota para cada usuario en función de una ventana de tiempo. Por ejemplo, un usuario puede enviar como máximo 15 consultas dentro de cada ventana de 5 segundos sin que se vea limitado. El valor de la cuota se determina según varios factores y está sujeto a cambios.

En cada respuesta de consulta, Azure Resource Graph agrega dos encabezados de limitación:

  • x-ms-user-quota-remaining (int): la cuota de recurso restante para el usuario. Este valor se asigna al número de consultas.
  • x-ms-user-quota-resets-after (hh:mm:ss): el tiempo transcurrido hasta que se restablece el consumo de la cuota de un usuario.

Cuando una entidad de seguridad tiene acceso a más de 10 000 suscripciones en el ámbito de consulta del inquilino o del grupo de administración, la respuesta se limita a las primeras 10 000 suscripciones y el encabezado x-ms-tenant-subscription-limit-hit se devuelve como true.

Para ilustrar cómo funcionan los encabezados, echemos un vistazo a una respuesta de consulta que tiene el encabezado y los valores de x-ms-user-quota-remaining: 10 y x-ms-user-quota-resets-after: 00:00:03.

  • En los próximos 3 segundos, se pueden enviar como máximo 10 consultas sin limitarse.
  • En 3 segundos, los valores de x-ms-user-quota-remaining y x-ms-user-quota-resets-after se restablecen a 15 y 00:00:05 respectivamente.

Para ver un ejemplo del uso de los encabezados para revertir las solicitudes de consulta, consulte el ejemplo en Consulta en paralelo.

Agrupación de consultas

La agrupación de consultas según la suscripción, grupo de recursos o recurso individual es más eficaz que realizar consultas en paralelo. El coste de la cuota de una consulta de mayor tamaño con frecuencia es menor que el coste de la cuota de muchas consultas pequeñas y dirigidas. Se recomienda que el tamaño del grupo sea inferior a 300.

  • Ejemplo de un enfoque mal optimizado.

    // 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);
    
    // ...
    }
    
  • Ejemplo de un enfoque de agrupación optimizado.

    // 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);
    
      // ...
    }
    
  • Ejemplo de un enfoque de agrupación optimizado para obtener varios recursos en una 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);
    
      // ...
    }
    

Escalonamiento de las consultas

Debido a la manera en que se aplica la limitación, se recomienda que las consultas se escalonen. Por ejemplo, en lugar de enviar 60 consultas al mismo tiempo, escalone las consultas en cuatro ventanas de 5 segundos.

  • Programación de consultas no registrados.

    Número de consultas 60 0 0 0
    Intervalo de tiempo (s) 0-5 5-10 10-15 15-20
  • Programación de consultas escalonadas.

    Número de consultas 15 15 15 15
    Intervalo de tiempo (s) 0-5 5-10 10-15 15-20

El código siguiente es un ejemplo de respetar los encabezados de limitación al consultar 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 en paralelo

Aunque la agrupación es recomendable en comparación con la paralelización, hay ocasiones en las que las consultas no se pueden agrupar fácilmente. En estos casos, es posible que quiera consultar Azure Resource Graph mediante el envío de varias consultas de forma paralela. En el ejemplo siguiente se muestra cómo revertir en función de los encabezados de limitación.

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

Paginación

Dado que Azure Resource Graph devuelve un máximo de 1000 entradas en una única respuesta de consulta, es posible que tenga que paginar las consultas para obtener el conjunto de datos completo que desee. Pero algunos clientes de Azure Resource Graph controlan la paginación de forma diferente a otras.

Al usar el SDK de ResourceGraph, tendrá que controlar la paginación al pasar el token de omisión que se devuelve de la respuesta de la consulta anterior a la siguiente consulta paginada. Este diseño significa que necesita recopilar los resultados de todas las llamadas paginadas y combinarlos al final. En este caso, cada consulta paginada que envíe toma una cuota 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.
}

¿Todavía se está limitando?

Si ha usado las recomendaciones de este artículo y las consultas de Azure Resource Graph se siguen limitando, póngase en contacto con el equipo de Azure Resource Graph. El equipo admite Azure Resource Graph, pero no admite la limitación de Microsoft Graph.

Proporcione estos detalles al ponerse en contacto con el equipo de Azure Resource Graph:

  • Sus necesidades de controlador de casos prácticos y empresariales de un límite superior.
  • ¿A cuántos recursos tiene acceso? ¿Cuántos de ellos se devuelven desde una consulta única?
  • ¿Qué tipos de recursos le interesa?
  • ¿Cuál es el modelo de consulta? X consultas por Y segundo, etc.

Pasos siguientes