Comment pouvons-nous augmenter la vitesse de cette requête?
Nous avons environ 100 consommateurs dans le délai d' 1-2 minutes
exécution de la requête suivante. Chacune de ces exécutions représente 1 exécution d'une fonction de consommation.
TableQuery<T> treanslationsQuery = new TableQuery<T>()
.Where(
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
, TableOperators.Or,
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
)
);
Cette requête donnera environ 5000 résultats.
Code complet:
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);
}
Au cours de ces exécutions, lorsqu'il y a 100 consommateurs, comme vous pouvez le voir, les demandes se regroupent et forment des pics:
Lors de ces pics, les demandes prennent souvent plus d'une minute:
Comment pouvons-nous augmenter la vitesse de cette requête?
c#
azure
azure-table-storage
azure-virtual-network
azure-tablequery
l --''''''---------------- '' '' '' '' '' '' '
la source
la source
Réponses:
Voici l'un des problèmes: vous exécutez la requête, puis vous la filtrez à partir de la mémoire en utilisant ces "où". Déplacez les filtres avant l'exécution de la requête, ce qui devrait être très utile.
Deuxièmement, vous devez fournir une certaine limite de lignes à extraire de la base de données
la source
Il y a 3 choses que vous pouvez considérer:
1 . Tout d'abord, supprimez vos
Where
clauses que vous effectuez sur le résultat de la requête. Il est préférable d'inclure autant que possible des clauses dans la requête (encore mieux si vous avez des index sur vos tables, incluez-les également). Pour l'instant, vous pouvez modifier votre requête comme ci-dessous:Parce que vous avez une grande quantité de données à récupérer, il est préférable d'exécuter vos requêtes en parallèle. Donc, vous devez remplacer la méthode
do while
loop insideExecuteQueryAsync
par celle queParallel.ForEach
j'ai écrite en fonction de Stephen Toub Parallel.While ; De cette façon, cela réduira le temps d'exécution des requêtes. C'est un bon choix car vous pouvez le supprimerResult
lorsque vous appelez sur cette méthode, mais il y a une petite limitation que je vais en parler après cette partie de code:Et puis vous pouvez l'appeler dans votre
Get
méthode:Comme vous pouvez le voir, la méthode itselft n'est pas asynchrone (vous devez changer son nom) et
Parallel.ForEach
n'est pas compatible avec le passage d'une méthode asynchrone. C'est pourquoi je l'ai utilisé à laExecuteQuerySegmented
place. Mais, pour le rendre plus performant et utiliser tous les avantages de la méthode asynchrone, vous pouvez remplacer laForEach
boucle ci-dessus par laActionBlock
méthode Dataflow ou laParallelForEachAsync
méthode d'extension du package AsyncEnumerator Nuget .2. C'est un bon choix pour exécuter des requêtes parallèles indépendantes, puis fusionner les résultats, même si son amélioration des performances est d'au plus 10%. Cela vous donne le temps de trouver la meilleure requête adaptée aux performances. Mais n'oubliez jamais d'y inclure toutes vos contraintes et testez les deux façons de savoir laquelle convient le mieux à votre problème.
3 . Je ne suis pas sûr que ce soit une bonne suggestion ou non, mais faites-le et voyez les résultats. Comme décrit dans MSDN :
Vous pouvez donc jouer avec le timeout et vérifier s'il y a des améliorations de performances.
la source
Malheureusement, la requête ci-dessous introduit une analyse complète de la table :
Vous devez le diviser en deux filtres de clé de partition et les interroger séparément, ce qui deviendra deux analyses de partition et fonctionnera plus efficacement.
la source
Le secret réside donc non seulement dans le code, mais également dans la configuration de vos tables de stockage Azure.
a) L'une des options importantes pour optimiser vos requêtes dans Azure est d'introduire la mise en cache. Cela réduira considérablement vos temps de réponse globaux et évitera ainsi un goulot d'étranglement pendant l'heure de pointe que vous avez mentionnée.
b) En outre, lors de l'interrogation d'entités à partir d'Azure, le moyen le plus rapide de le faire est à la fois avec PartitionKey et RowKey. Ce sont les seuls champs indexés dans le stockage de table et toute requête qui utilise les deux sera retournée en quelques millisecondes. Assurez-vous donc d'utiliser à la fois PartitionKey et RowKey.
Voir plus de détails ici: https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query
J'espère que cela t'aides.
la source
Remarque: Il s'agit d'un conseil général d'optimisation de requête DB.
Il est possible que l'ORM fasse quelque chose de stupide. Lorsque vous effectuez des optimisations, vous pouvez quitter une couche d'abstraction. Je suggère donc de réécrire la requête dans le langage de requête (SQL?) Pour le rendre plus facile à voir ce qui se passe, et aussi plus facile à optimiser.
La clé pour optimiser les recherches est le tri! Garder une table triée est généralement beaucoup moins cher que de scanner la table entière à chaque requête! Donc, si possible, conservez la table triée par la clé utilisée dans la requête. Dans la plupart des solutions de base de données, ceci est réalisé en créant une clé d'index.
Une autre stratégie qui fonctionne bien s'il y a peu de combinaisons consiste à avoir chaque requête dans une table distincte (temporaire en mémoire) qui est toujours à jour. Ainsi, lorsqu'un élément est inséré, il est également "inséré" dans les tables "view". Certaines solutions de base de données appellent cela des "vues".
Une stratégie plus brute consiste à créer des répliques en lecture seule pour répartir la charge.
la source