web-dev-qa-db-fra.com

Des méthodes efficaces pour stocker des dizaines de millions d'objets à interroger, avec un nombre élevé d'insertions par seconde?

Il s'agit essentiellement d'une application de journalisation/comptage qui compte le nombre de paquets et le type de paquet, etc. sur un réseau de discussion p2p. Cela équivaut à environ 4 à 6 millions de paquets sur une période de 5 minutes. Et parce que je ne prends qu'un "instantané" de ces informations, je ne supprime que les paquets de plus de 5 minutes toutes les cinq minutes. Donc, le nombre maximum d'articles qui seront dans cette collection est de 10 à 12 millions.

Parce que je dois établir 300 connexions avec différents super-utilisateurs, il est possible que chaque paquet essaie d'être inséré au moins 300 fois (c'est probablement la raison pour laquelle la conservation de ces données en mémoire est la seule option raisonnable).

Actuellement, j'utilise un dictionnaire pour stocker ces informations. Mais en raison de la grande quantité d'éléments que j'essaie de stocker, je rencontre des problèmes avec le tas d'objets volumineux et la quantité d'utilisation de la mémoire augmente continuellement au fil du temps.

Dictionary<ulong, Packet>

public class Packet
{
    public ushort RequesterPort;
    public bool IsSearch;
    public string SearchText;
    public bool Flagged;
    public byte PacketType;
    public DateTime TimeStamp;
}

J'ai essayé d'utiliser mysql, mais il n'a pas pu suivre la quantité de données que j'ai besoin d'insérer (tout en vérifiant qu'il ne s'agissait pas d'un doublon), et c'était lors de l'utilisation de transactions.

J'ai essayé mongodb, mais l'utilisation du processeur pour cela était folle et je ne l'ai pas gardée non plus.

Mon principal problème survient toutes les 5 minutes, car je supprime tous les paquets qui datent de plus de 5 minutes et je prends un "instantané" de ces données. Comme j'utilise des requêtes LINQ pour compter le nombre de paquets contenant un certain type de paquet. J'appelle également une requête distinct () sur les données, où je supprime 4 octets (adresse IP) de la clé de keyvaluepair, et la combine avec la valeur de requestingport dans la valeur de keyvalupair et l'utilise pour obtenir un nombre distinct de pairs de tous les paquets.

L'application oscille actuellement autour de 1,1 Go d'utilisation de la mémoire et lorsqu'un instantané est appelé, il peut aller jusqu'à doubler l'utilisation.

Maintenant, ce ne serait pas un problème si j'ai une quantité folle de RAM, mais la vm sur laquelle je tourne est limitée à 2 Go de RAM pour le moment.

Existe-t-il une solution simple?

15
Josh

Au lieu d'avoir un dictionnaire et de rechercher dans ce dictionnaire des entrées trop anciennes; avoir 10 dictionnaires. Toutes les 30 secondes environ, créez un nouveau dictionnaire "actuel" et jetez le plus ancien dictionnaire sans aucune recherche.

Ensuite, lorsque vous supprimez le dictionnaire le plus ancien, placez tous les anciens objets dans une file d'attente FILO pour plus tard, et au lieu d'utiliser "nouveau" pour créer de nouveaux objets, retirez un ancien objet de la file d'attente FILO et utilisez une méthode pour reconstruire l'ancien objet (sauf si la file d'attente des anciens objets est vide). Cela peut éviter beaucoup d'allocations et beaucoup de frais généraux de collecte de déchets.

12
Brendan

La première pensée qui vous vient à l'esprit est la raison pour laquelle vous attendez 5 minutes. Pourriez-vous faire les clichés plus souvent et ainsi réduire la grosse surcharge que vous voyez à la limite de 5 minutes?

Deuxièmement, LINQ est idéal pour le code concis, mais en réalité LINQ est du sucre syntaxique sur C # "normal" et il n'y a aucune garantie qu'il générera le code le plus optimal. En tant qu'exercice, vous pouvez essayer de réécrire les points chauds sans LINQ, vous n'améliorerez peut-être pas les performances, mais vous aurez une idée plus claire de ce que vous faites et cela faciliterait le travail de profilage.

Une autre chose à considérer est la structure des données. Je ne sais pas ce que vous faites de vos données, mais pourriez-vous simplifier les données que vous stockez de quelque manière que ce soit? Pourriez-vous utiliser une chaîne ou un tableau d'octets, puis extraire les parties pertinentes de ces éléments selon vos besoins? Pourriez-vous utiliser une structure au lieu d'une classe et même faire quelque chose de mal avec stackalloc pour mettre de côté la mémoire et éviter les exécutions GC?

3
Steve

Approche simple: essayez memcached .

  • Il est optimisé pour exécuter des tâches comme celle-ci.
  • Il peut réutiliser de la mémoire disponible sur des boîtiers moins occupés, pas seulement sur votre box dédiée.
  • Il a un mécanisme d'expiration de cache intégré, qui est paresseux donc pas de hoquet.

L'inconvénient est qu'il est basé sur la mémoire et n'a aucune persistance. Si une instance est en panne, les données disparaissent. Si vous avez besoin de persistance, sérialisez les données vous-même.

Approche plus complexe: essayez Redis .

  • Il est optimisé pour exécuter des tâches comme celle-ci.
  • Il a intégré mécanisme d'expiration du cache .
  • Il se balance/éclate facilement.
  • Il a de la persistance.

L'inconvénient est qu'il est légèrement plus complexe.

3
9000

(Je sais que c'est une vieille question, mais je l'ai rencontrée en cherchant une solution à un problème similaire où la passe de collecte des ordures de deuxième génération suspendait l'application pendant plusieurs secondes, donc l'enregistrement pour d'autres personnes dans une situation similaire).

Utilisez une structure plutôt qu'une classe pour vos données (mais rappelez-vous qu'elle est traitée comme une valeur avec une sémantique passe-par-copie). Cela supprime un niveau de recherche que le GC doit faire à chaque passe.

Utilisez des tableaux (si vous connaissez la taille des données que vous stockez) ou List - qui utilise des tableaux en interne. Si vous avez vraiment besoin d'un accès aléatoire rapide, utilisez un dictionnaire d'indices de tableaux. Cela supprime un autre couple de niveaux (ou une douzaine ou plus si vous utilisez un SortedDictionary) pour que le GC doive rechercher.

Selon ce que vous faites, la recherche d'une liste de structures peut être plus rapide que la recherche de dictionnaire (en raison de la localisation de la mémoire) - le profil de votre application particulière.

La combinaison de struct & list réduit à la fois l'utilisation de la mémoire et la taille du ramasse-miettes.

1
Malcolm

Vous n'avez pas besoin de stocker tous les packages pour les requêtes que vous avez mentionnées. Par exemple - compteur de type de package:

Vous avez besoin de deux tableaux:

int[] packageCounters = new int[NumberOfTotalTypes];
int[,] counterDifferencePerMinute = new int[6, NumberOfTotalTypes];

Le premier tableau conserve la trace du nombre de packages dans différents types. Le deuxième tableau conserve la trace du nombre de packages supplémentaires ajoutés toutes les minutes de manière à savoir combien de packages doivent être supprimés à chaque minute d'intervalle. J'espère que vous pouvez dire que le deuxième tableau est utilisé comme une file ronde FIFO.

Ainsi, pour chaque package, les opérations suivantes sont effectuées:

packageCounters[packageType] += 1;
counterDifferencePerMinute[current, packageType] += 1;
if (oneMinutePassed) {
  current = (current + 1) % 6;
  for (int i = 0; i < NumberOfTotalTypes; i++) {
    packageCounters[i] -= counterDifferencePerMinute[current, i];
    counterDifferencePerMinute[current, i] = 0;
}

À tout moment, les compteurs de packages peuvent être récupérés par l'index instantanément et nous ne stockons pas tous les packages.

1
Codism