Conception de base de données pour gérer 1 milliard de lignes et compter

10

Nous recevons des données GPS en temps réel à un taux d'environ 5000 pr. minute (à partir de 4 serveurs TCP). Chaque serveur utilise une connexion unique pour insérer les données et met en mémoire tampon les données entre les insertions. Toutes les 15 minutes environ, un service récupère ces données et les transforme en trajets. Une fois que les trajets ont été générés, les données GPS réelles ne sont généralement pas si importantes, uniquement si l'utilisateur souhaite voir l'itinéraire sur une carte.

Le problème est qu'il semble que la base de données ait du mal à suivre le rythme des données insérées. Parfois, lorsque la charge augmente, le temps d'insertion augmente soudainement de façon drastique (> 30 secondes), ce qui permet à son tour de stocker davantage de données, ce qui entraîne à son tour des insertions plus grandes et une durée d'insertion plus longue.

J'espère avoir quelques commentaires sur la conception actuelle, et certaines des idées que nous avons pour améliorer les performances, et des réponses à certaines de nos questions - et tout autre conseil que les gens pourraient avoir!

Conception actuelle

Les données sont actuellement séparées en tableaux représentant une semaine et les données datant de plus d'un an sont archivées dans une base de données secondaire. Le tout est réuni dans une vue modifiable, qui est utilisée à la fois pour les insertions et les lectures.

Conception de table

  • Id (PK, uniqueidentifier)
  • DeviceId (FK, int)
  • PersonId (FK, int)
  • VehicleId (FK, int)
  • TokenId (FK, int)
  • UtcTime (PK, datetime2 (3))
  • Latitude (flottant)
  • Longitude (flotteur)
  • Vitesse (smallint)
  • En-tête (smallint)
  • Satellites (tinyint)
  • IOData (varbinaire (100))
  • IgnitionState (tinyint)
  • UserInput (tinyint)
  • CreateTimeUtc (datetime2 (3))

Indices

  • DeviceId_CreateTimeUtc_Desc
  • DeviceId_UtcTime_Desc (Clustered)
  • PersonId_UtcTime_Desc
  • TokenId_UtcTime_Desc
  • VehicleId_UtcTime_Desc

Chaque semaine occupe actuellement environ 10 Go, y compris les indices, et il y a actuellement environ 300 Go de données dans la base de données principale.

Les tables de données de la base de données principale ont leur propre groupe de fichiers avec 1 fichier, mais il se trouve sur le même disque que toutes les autres tables de la base de données principale. La base de données secondaire se trouve sur un disque différent, mais sur la même machine.

Je pense que nous exécutons également un travail de reconstruction d'index chaque semaine, lorsqu'une nouvelle partition de table (semaine) est utilisée. Aucun rétrécissement n'est effectué.

La machine est un HP à 8 cœurs avec 12 Go de mémoire et le disque contenant la base de données principale exécute RAID 10.

Des idées

  • Limitez la quantité de données stockées dans la base de données primaire à par exemple 1 mois maximum. À tout le moins, cela rendrait la base de données plus gérable pour la sauvegarde / restauration, mais pourrions-nous nous attendre à une amélioration des performances en faisant cela?
  • Créez 2 fichiers dans un groupe de fichiers pour les données actuelles et distribuez-les sur 2 partitions physiques différentes
  • Créez des bases de données maître-esclave contenant les données actuelles, afin que les insertions et les lectures soient effectuées sur différentes bases de données
  • Placez les fichiers pour les données actuelles sur les disques SSD (la mise en miroir ferait-elle une différence de performances avec les disques SSD?)

Veuillez me faire savoir si plus d'informations sont nécessaires. Il existe horriblement de nombreux facteurs qui influencent les performances, et probablement autant de façons de les modifier.

sondergard
la source
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
Paul White 9

Réponses:

8

5000 inserts par minute sont environ 83 inserts par seconde. Avec 5 index c'est 400 lignes physiques insérées par seconde. Si la charge de travail était en mémoire, cela ne poserait pas de problème, même au plus petit des serveurs. Même s'il s'agissait d'un encart ligne par ligne utilisant la manière la plus inefficace que je puisse imaginer. 83 requêtes triviales par seconde ne sont tout simplement pas intéressantes du point de vue du processeur.

Vous êtes probablement lié au disque. Vous pouvez le vérifier en consultant les statistiques d'attente ou STATISTICS IO.

Vos requêtes touchent probablement un grand nombre de pages différentes afin que le pool de tampons n'ait pas d'espace pour toutes. Cela provoque des lectures de pages fréquentes et des écritures sur disque probablement aléatoires également.

Imaginez une table où vous n'insérez physiquement qu'à la fin en raison d'une clé toujours croissante. L'ensemble de travail serait une page: la dernière. Cela générerait des E / S séquentielles aussi bien lorsque l'écrivain paresseux ou le processus de point de contrôle écrit la «fin» de la table sur le disque.

Imaginez une table avec des insertions placées au hasard (exemple classique: une clé guid). Ici, toutes les pages sont le jeu de travail car une page aléatoire sera touchée pour chaque insert. Les entrées-sorties sont aléatoires. C'est le pire des cas quand il s'agit de travailler ensemble.

Tu es au milieu. Vos index sont de la structure (SomeValue, SequentialDateTime). Le premier composant randomise partiellement la séquentialité fournie par le second. Je suppose qu'il y a pas mal de valeurs possibles pour " SomeValue" afin que vous ayez de nombreux points d'insertion placés au hasard dans vos index.

Vous dites que les données sont divisées en tables de 10 Go par semaine. C'est un bon point de départ car l'ensemble de travail est désormais limité à 10 Go (sans tenir compte des lectures que vous pourriez faire). Avec 12 Go de mémoire serveur, il est peu probable, cependant, que toutes les pages pertinentes puissent rester en mémoire.

Si vous pouviez réduire la taille des "partitions" hebdomadaires ou augmenter un peu la mémoire du serveur, tout va bien.

Je m'attendrais à ce que les insertions au début de la semaine soient plus rapides qu'à la fin. Vous pouvez tester cette théorie sur un serveur de développement en exécutant un benchmark avec une certaine taille de données et en réduisant progressivement la mémoire du serveur jusqu'à ce que vous voyiez un réservoir de performances.

Maintenant, même si toutes les lectures et écritures tiennent dans la mémoire, vous pouvez toujours avoir des E / S de vidage de page sale aléatoires. La seule façon de s'en débarrasser est d'écrire dans des positions colocalisées dans vos index. Si vous pouvez convertir vos index pour utiliser (plus) de clés séquentielles, cela aiderait beaucoup.

Comme solution rapide, j'ajouterais une couche tampon entre les clients et la table principale. Peut-être accumuler 15 minutes d'écritures dans une table intermédiaire et la vider périodiquement. Cela supprime les pics de charge et utilise un plan plus efficace pour écrire sur la grande table.

usr
la source
1
@usr Merci pour la réponse très complète et bien expliquée! Nous avons en fait discuté de l'augmentation de la mémoire du serveur, sans savoir quel effet cela aurait - mais maintenant nous avons vraiment une raison très convaincante de le faire :) Vous avez raison que la "SomeValue" randomise partiellement les points d'insertion - il y a probablement environ 10000 identifiants d'appareil. Concernant la table intermédiaire, votre suggestion est-elle une table sans index, puis un travail à insérer dans la table principale toutes les X minutes?
sondergard
@usr Reg. votre suggestion pour convertir l'index clusterisé pour être séquentiel, nous pourrions ajouter un auto-inc. colonne d'identité (entier) et modifier l'index cluster pour cette colonne dans le seul but de le garder séquentiel? Ce ne serait pas unique d'une table à l'autre, mais tant que la clé primaire est, ça devrait aller.
sondergard
1
Si la table intermédiaire est petite et que vos requêtes peuvent vivre avec elle, vous n'avez pas du tout besoin d'indexer. Mais vous pourriez .; Une stratégie serait de faire le CI sur une colonne d'identité (comme vous le dites). Cela peut faire des merveilles si le CI est grand et les autres index sont petits. Parce que les CI sont des écritures qui sont maintenant séquentielles, ils contribuent beaucoup moins à votre problème. Cette stratégie est plus efficace s'il existe une différence de taille significative .; Une autre idée serait d'avoir une table par jour. Peut-être fusionner mensuellement.
usr
Ok, nous avons donc cherché à créer une colonne d'identité pour CI, mais malheureusement, ce n'est pas possible sur une vue partitionnée (aucune colonne d'identité autorisée, aucune valeur par défaut et toutes les colonnes doivent être incluses dans l'insertion). Peut-être que la vue partagée était une conception mal choisie, bien qu'elle ait été recommandée par un consultant
sondergard
2
Sérieusement, pour toute personne confrontée au même problème, si vous avez beaucoup d'écritures et seulement quelques lectures, vous voulez vraiment ajouter à la fin et retarder toute indexation. D'un autre côté, si vous voulez des lectures rapides et que vous ne vous souciez pas du temps nécessaire à l'insertion, vous avez besoin d'un index clusterisé.
tiktak du