web-dev-qa-db-fra.com

Comment optimiser la base de données MySQL de 250 millions de lignes pour inserts en vrac et sélectionne

Je suis un étudiant qui a reçu la tâche de concevoir une base de données pour les données de capteur. Mon université dispose actuellement d'une grande base de données qui est remplie de ces données, mais beaucoup de ce qui est stocké n'est pas nécessaire. Ils veulent que je extrait certains des champs de la base de données existante et insérez-le dans un nouveau, qui ne tiendra que "Essentials". J'aurai besoin d'extraire toutes les rangées de l'ancien, ainsi que de chercher de nouvelles données une fois par jour.

  • Il y a 1500 capteurs.
  • Ils génèrent une lecture chaque minute.
  • Environ 2,1 millions de lectures tous les jours
  • La base de données actuelle compte environ 250 millions de lignes.

Les requêtes qui seront effectuées seront généralement de sélectionner des lectures de capteurs pour un ensemble de capteurs entre une période donnée.

J'étais initialement naïf en ce qui concerne la complexité supplémentaire de grandes quantités de données introduites, donc je sous-estimé grossièrement le temps nécessaire à cette tâche. Pour cette raison, et le fait que je n'ai pas accès au serveur à la maison, je suis ici demandant de l'aide et de l'entrée.

La conception initiale ressemble à ceci:

CREATE TABLE IF NOT EXISTS SENSORS (
    ID smallint UNSIGNED NOT NULL AUTO_INCREMENT,
    NAME varchar(500) NOT NULL UNIQUE,
    VALUEFACETS varchar(500) NOT NULL,
    PRIMARY KEY (ID)
); 

CREATE TABLE IF NOT EXISTS READINGS (
    ID int UNSIGNED AUTO_INCREMENT,
    TIMESTAMP int UNSIGNED INDEX NOT NULL,
    VALUE float NOT NULL,
    STATUS int NOT NULL,
    SENSOR_ID smallint UNSIGNED NOT NULL,
    PRIMARY KEY (ID),
    FOREIGN KEY (SENSOR_ID) REFERENCES SENSORS(ID)
);

Question de conception

Ma première question est de savoir si je devrais conserver une clé auto-incrémentée pour les lectures ou s'il serait plus utile d'avoir une clé composite sur l'horodatage (Epoch UNIX) et SENSOR_ID ?

Cette question s'applique à la fois au fait que je dois insérer 2,1 millions de lignes par jour, ainsi que le fait que je souhaite optimiser pour les requêtes susmentionnées.

Insert en vrac initial:

Après beaucoup d'essais et d'erreurs et de trouver un guide Online, j'ai constaté que l'insertion à l'aide de la charge infilière, conviendra le mieux à cet effet. J'ai écrit un script qui sélectionnera 500 000 lignes à l'époque de l'ancienne dB et écrivez-les (tous les 250 millions) à un fichier CSV, qui ressemblera à ceci:

TIMESTAMP,SENSOR_ID,VALUE,STATUS
2604947572,1399,96.434564,1432543

Mon plan est alors de trier avec GNU Trier et diviser en fichiers contenant 1 million de lignes.

Avant d'insérer ces fichiers, je supprimerai l'index sur l'horodatage, ainsi que d'exécuter ces commandes:

SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET SESSION tx_isolation='READ-UNCOMMITED';
SET sql_log_bin = 0;

Après avoir inséré, je vais bien sûr rétablir ces changements.

  • Ce plan est-il viable ?

  • Les inserts peuvent-ils être accélérés si je trie le CSV en fonction de Sensor_ID et d'horodatage au lieu de TimeStamp et Sensor_ID ?

  • Après avoir retourné l'indexage après l'insertion en vrac, l'insertion de 2 millions de lignes sera possible chaque jour ?

  • est-il possible de faire les inserts quotidiens avec des relevés d'insertion réguliers ou devrons-je utiliser la charge infilière afin de continuer à suivre
    [.____] avec la charge d'entrée ?

my.cnf

Chaque configuration est par défaut sauf pour ceux-ci:

  innodb_flush_log_at_trx_commit=2
  innodb_buffer_pool_size=5GB
  innodb_flush_method=O_DIRECT
  innodb_doublewrite = 0

Y a-t-il d'autres optimisations dont j'ai besoin pour cet objectif particulier?

Le serveur a 8 Go de RAM. mysqld ver.0.22 ubuntu 20.04

Toute pensée, idées ou intrants serait grandement appréciée.

1
Kent Odde

Recommandations générales pour un jeu de données "capteur":

  • Minimiser les fichiers de données
  • Minimiser les index
  • Inserts de lot
  • 25 rangées par seconde est rapide, mais ne nécessite pas d'étapes plus drastiques

Détails:

  • STATUS int NOT NULL - 4 octets? Quelles valeurs cela pourrait-il avoir? (Rendez-le plus petit si pratique.)
  • Je suggère que PRIMARY KEY(sensor_id, timestamp) serait unique et adéquat. Ensuite, débarrassez-vous de id complètement. Le résultat est un indice secondaire inférieur à mettre à jour. Je recommande cette commande pour les colonnes. Mais le vrai choix doit être basé sur le SELECTs qui sera effectué.
  • Rassemblez les 1500 lignes dans un seul INSERT. Ou utiliser LOAD DATA INFILE (Sauf que cela nécessite plus de hits de disque). C'est-à-dire que vous aurez un INSERT par minute. Il ne devrait y avoir aucun problème pour un seul fil à suivre. Load 'continuellement', je ne vois aucun avantage à attendre jusqu'à la fin de la journée. (Ou je manque quelque chose?)
  • Déboguer votre code, puis vous débarrasser du FOREIGN KEY; La vérification du FK implique des efforts supplémentaires.
  • Les données de charge initiales pour mettre des millions de lignes dans la table - cela frappera probablement un délai d'attente et/ou de la limite de mémoire tampon. Dans ce cas, rompez-la en morceaux - mais pas de rangées de 1 m; Au lieu de cela peut-être des rangées de 10k. Et faites-le un seul fileté. J'hésite à réfléchir aux hoques que vous pourriez rencontrer si vous essayez de le faire multi-filetés. En outre, cela pourrait être principalement des I/O-liés, ce qui ne bénéficie pas beaucoup de multi-threading.
  • Avoir autocommit sur; De cette façon, chaque morceau sera commis. Sinon, le journal de Redo deviendra énorme, prenant un espace disque supplémentaire et ralentir les choses.
  • Données pré-tri - cela aide certains. Dans votre schéma d'origine, trier par la clé secondaire (timestamp); Le AUTO_INCREMENT prendra soin de lui-même. Si vous supprimez l'auto_inc, triez par la fonction PRIMARY KEY(sensor_id, timestamp), si vous prenez ma suggestion.
  • Avoir le PRIMARY KEY En place lorsque vous chargez les données. Sinon, il aura besoin de copier la table lors de la construction de la PK.
  • S'il y a des touches secondaires, ALTER TABLE .. ADD INDEX .. Après le chargement initial.
  • Les paramètres ont l'air bien. Cependant, je quitterais innodb_doublewrite On - cela protège contre une corruption rare, mais catastrophique et catastrophique.

Commentaires liés au lien que vous avez fourni:

  • Le lien a 8 ans. Mais la majeure partie est toujours valide. (Cependant, je suis en désaccord avec certains détails.)
  • Si vous avez besoin de supprimer éventuellement Old , planifiez-le maintenant . Voir ceci pour une série chronologique qui accélère grandement les suppressions mensuelles d'anciennes données à l'aide de PARTITION BY RANGE(): http://mysql.rjweb.org/doc.php/parttitionmaint Remarque: Le PK que je suggère est le bon pour partitionnement par jour (ou semaine ou mois).
  • Le seulement Avantage de la partition du partitionnement (sur la base de ce qui a été discuté jusqu'à présent) est quand il s'agit de DELETEing via DROP PARTITION. Non SELECTs sont susceptibles de courir plus vite (ou plus lentement).
  • Si vous utilisez mysqldump, utilisez simplement les valeurs par défaut. Cela produira gérable INSERTs avec beaucoup de lignes. TSV/CSV n'est pas nécessaire sauf si vous venez d'une autre source.
  • Je ne connais pas son débat entre le fichier de charge et l'insert en vrac et le nombre de lignes par morceau. Peut-être qu'il avait des indices secondaires qui ralentissaient les choses. Le "Change Buffer" est une sorte de cache en écriture pour les index secondaires. Une fois que cela est plein, l'insertion est nécessairement ralentit, en attente de mises à jour à rougir. En se débarrassant des indices secondaires, ce problème s'en va. Ou, en utilisant ADD INDEX Après avoir chargé les données, le coût est reporté.

Attraper un emploi cron brisé ...

Si vous avez un travail cron capturant les données de "hier" basées sur l'horodatage de la source, il reste un risque d'échec du travail de cron ou non même en cours d'exécution (lorsque votre serveur est en panne au moment où le travail doit être exécuté. ).

Avec le PK (capteur, TS) que je recommande, ce qui suit est raisonnablement efficace:

SELECT sensor, MAX(ts) AS max_ts
    FROM dest_table GROUP BY sensor;

L'efficacité vient de sauter à travers la table, frappant seulement 1500 taches.

Au début du travail cron, courez ceci (sur la destination) pour "savoir où tu as laissé":

SELECT MAX(max_ts) AS left_off FROM
       ( SELECT sensor, MAX(ts) AS max_ts
            FROM dest_table GROUP BY sensor ) AS x;

Puis chercher de nouvelles lignes de la source

    WHERE ts > left_off
      AND ts < CURDATE()

Normalement, left_off Sera peu de temps avant la minuit d'hier matin. Quand il y a un hoquet, ce sera un jour plus tôt.

Vous pouvez également utiliser cette sous-requête pour voir si des capteurs sont partis hors ligne et quand.

0
Rick James