¿Cómo aumentamos la velocidad de esta consulta?
Tenemos aproximadamente 100 consumidores dentro del lapso de 1-2 minutes
ejecución de la siguiente consulta. Cada una de estas ejecuciones representa 1 ejecución de una función de consumo.
TableQuery<T> treanslationsQuery = new TableQuery<T>()
.Where(
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
, TableOperators.Or,
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
)
);
Esta consulta arrojará aproximadamente 5000 resultados.
Código completo:
public static async Task<IEnumerable<T>> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query) where T : ITableEntity, new()
{
var items = new List<T>();
TableContinuationToken token = null;
do
{
TableQuerySegment<T> seg = await table.ExecuteQuerySegmentedAsync(query, token);
token = seg.ContinuationToken;
items.AddRange(seg);
} while (token != null);
return items;
}
public static IEnumerable<Translation> Get<T>(string sourceParty, string destinationParty, string wildcardSourceParty, string tableName) where T : ITableEntity, new()
{
var acc = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("conn"));
var tableClient = acc.CreateCloudTableClient();
var table = tableClient.GetTableReference(Environment.GetEnvironmentVariable("TableCache"));
var sourceDestinationPartitionKey = $"{sourceParty.ToLowerTrim()}-{destinationParty.ToLowerTrim()}";
var anySourceDestinationPartitionKey = $"{wildcardSourceParty}-{destinationParty.ToLowerTrim()}";
TableQuery<T> treanslationsQuery = new TableQuery<T>()
.Where(
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
, TableOperators.Or,
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
)
);
var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
return over1000Results.Where(x => x.expireAt > DateTime.Now)
.Where(x => x.effectiveAt < DateTime.Now);
}
Durante estas ejecuciones, cuando hay 100 consumidores, como puede ver, las solicitudes se agruparán y formarán picos:
Durante estos picos, las solicitudes suelen tardar más de 1 minuto:
¿Cómo aumentamos la velocidad de esta consulta?
c#
azure
azure-table-storage
azure-virtual-network
azure-tablequery
l --''''''--------- '' '' '' '' '' ''
fuente
fuente
Respuestas:
Este es uno de los problemas: está ejecutando la consulta y luego la está filtrando de la memoria utilizando estos "wheres". Mueva los filtros antes de que se ejecute la consulta, lo que debería ayudar mucho.
En segundo lugar, debe proporcionar un límite de filas para recuperar de la base de datos
fuente
Hay 3 cosas que puede considerar:
1 . En primer lugar, elimine las
Where
cláusulas que realiza en el resultado de la consulta. Es mejor incluir cláusulas en la consulta tanto como sea posible (incluso mejor si tiene algún índice en sus tablas, inclúyalos también). Por ahora, puede cambiar su consulta de la siguiente manera:Debido a que tiene una gran cantidad de datos para recuperar, es mejor ejecutar sus consultas en paralelo. Por lo tanto, debe reemplazar el método
do while
loop insideExecuteQueryAsync
con elParallel.ForEach
que escribí basado en Stephen Toub Parallel . De esta manera, reducirá el tiempo de ejecución de la consulta. Esta es una buena opción porque puede eliminarlaResult
cuando realiza una llamada a este método, pero tiene una pequeña limitación de la que hablaré después de esta parte del código:Y luego puedes llamarlo en tu
Get
método:Como puede ver, el método itselft no es asíncrono (debe cambiar su nombre) y
Parallel.ForEach
no es compatible con pasar un método asíncrono. Es por eso que he usado en suExecuteQuerySegmented
lugar. Pero, para hacerlo más eficiente y usar todos los beneficios del método asincrónico, puede reemplazar elForEach
bucle anterior con elActionBlock
método en Flujo de datos oParallelForEachAsync
método de extensión del paquete Nuget de AsyncEnumerator .2. Es una buena opción ejecutar consultas paralelas independientes y luego combinar los resultados, incluso si su mejora del rendimiento es como máximo del 10 por ciento. Esto le da tiempo para poder encontrar la mejor consulta amigable de rendimiento. Pero, nunca olvide incluir todas sus restricciones, y pruebe ambas formas para saber cuál se adapta mejor a su problema.
3 . No estoy seguro de si es una buena sugerencia o no, pero hazlo y mira los resultados. Como se describe en MSDN :
Para que pueda jugar con tiempo de espera y comprobar si hay mejoras en el rendimiento.
fuente
Desafortunadamente, la consulta a continuación presenta un escaneo completo de la tabla :
Debe dividirlo en dos filtros de Clave de partición y consultarlos por separado, lo que se convertirá en dos escaneos de partición y funcionará de manera más eficiente.
fuente
Por lo tanto, el secreto no solo está en el código sino también en la configuración de las tablas de almacenamiento de Azure.
a) Una de las opciones destacadas para optimizar sus consultas en Azure es introducir el almacenamiento en caché. Esto reducirá drásticamente sus tiempos de respuesta generales y, por lo tanto, evitará el cuello de botella durante la hora pico que ha mencionado.
b) Además, cuando se consultan entidades fuera de Azure, la forma más rápida de hacerlo es con PartitionKey y RowKey. Estos son los únicos campos indexados en Table Storage y cualquier consulta que utilice ambos se devolverá en cuestión de unos pocos milisegundos. Así que asegúrese de usar PartitionKey y RowKey.
Ver más detalles aquí: https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query
Espero que esto ayude.
fuente
nota: Este es un consejo general de optimización de consultas de base de datos.
Es posible que el ORM esté haciendo algo estúpido. Al hacer optimizaciones, está bien bajar una capa de abstracción. Por lo tanto, sugiero reescribir la consulta en el lenguaje de consulta (SQL?) Para que sea más fácil ver lo que está sucediendo y también más fácil de optimizar.
¡La clave para optimizar las búsquedas es la clasificación! ¡Mantener una tabla ordenada suele ser mucho más barato en comparación con escanear la tabla completa en cada consulta! Entonces, si es posible, mantenga la tabla ordenada por la clave utilizada en la consulta. En la mayoría de las soluciones de bases de datos, esto se logra creando una clave de índice.
Otra estrategia que funciona bien si hay pocas combinaciones es tener cada consulta como una tabla separada (temporal en la memoria) que siempre esté actualizada. Entonces, cuando se inserta algo, también se "inserta" en las tablas de "vista". Algunas soluciones de bases de datos llaman a esto "vistas".
Una estrategia más bruta es crear réplicas de solo lectura para distribuir la carga.
fuente