D'énormes données et performances dans SQL Server

20

J'ai écrit une application avec un backend SQL Server qui collecte et stocke une très grande quantité d'enregistrements. J'ai calculé que, au sommet, le nombre moyen d'enregistrements se situe quelque part entre 3 et 4 milliards par jour (20 heures de fonctionnement).

Ma solution initiale (avant de faire le calcul réel des données) était que mon application insère des enregistrements dans la même table interrogée par mes clients. Cela s'est écrasé et brûlé assez rapidement, évidemment, car il est impossible d'interroger une table contenant autant d'inscriptions.

Ma deuxième solution consistait à utiliser 2 bases de données, une pour les données reçues par l'application et une pour les données prêtes pour le client.

Mon application recevait des données, les fragmentait en lots d'environ 100 000 enregistrements et les insérait en bloc dans la table de transfert. Après environ 100 000 enregistrements, l'application créerait à la volée une autre table intermédiaire avec le même schéma qu'avant et commencerait à l'insérer dans cette table. Il créerait un enregistrement dans une table de travaux avec le nom de la table contenant 100 000 enregistrements et une procédure stockée côté SQL Server déplacerait les données de la ou des tables de transfert vers la table de production prête pour le client, puis supprimerait le table table temporaire créée par mon application.

Les deux bases de données ont le même ensemble de 5 tables avec le même schéma, à l'exception de la base de données intermédiaire qui contient la table des travaux. La base de données intermédiaire n'a aucune contrainte d'intégrité, clé, index, etc. sur la table où résidera la majeure partie des enregistrements. Ci-dessous, le nom de la table est SignalValues_staging. L'objectif était que mon application claque les données dans SQL Server le plus rapidement possible. Le flux de travail de création de tables à la volée afin qu'elles puissent facilement être migrées fonctionne plutôt bien.

Voici les 5 tables pertinentes de ma base de données intermédiaire, plus ma table des travaux:

Tables de mise en scène La procédure stockée que j'ai écrite gère le déplacement des données de toutes les tables de transfert et leur insertion dans la production. Ci-dessous, la partie de ma procédure stockée qui s'insère dans la production à partir des tables de transfert:

-- Signalvalues jobs table.
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM 
(
    SELECT JobId 
           ,ProcessingComplete  
           ,SignalValueStagingTableName AS 'TableName'
           ,(DATEDIFF(SECOND, (SELECT last_user_update
                              FROM sys.dm_db_index_usage_stats
                              WHERE database_id = DB_ID(DB_NAME())
                                AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
                     ,GETUTCDATE())) SecondsSinceLastUpdate
    FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
   OR cte.SecondsSinceLastUpdate >= 120

DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)

DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128) 
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)

WHILE @i > 0
BEGIN

    SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
    FROM #JobsToProcess 
    WHERE RowIndex = @i 

    SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'

    SET @sqlStatement = N'

        --Signal values staging table.
        SELECT svs.* INTO #sValues
        FROM '+ @qualifiedTableName +' svs
        INNER JOIN SignalMetaData smd
            ON smd.SignalId = svs.SignalId  


        INSERT INTO SignalValues SELECT * FROM #sValues

        SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues

        DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

        DROP TABLE #sValues
        DROP TABLE #uniqueIdentifiers

        IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
        BEGIN
            -- processing is completed so drop the table and remvoe the entry
            IF @processingComplete = 1 
            BEGIN 
                DELETE FROM SignalValueJobs WHERE JobId = @currentJob

                IF '''+@currentTable+''' <> ''SignalValues_staging''
                BEGIN
                    DROP TABLE '+ @qualifiedTableName +'
                END
            END
        END 
    '

    EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;

    SET @i = @i - 1
END

DROP TABLE #JobsToProcess

J'utilise sp_executesqlparce que les noms de table pour les tables intermédiaires proviennent sous forme de texte des enregistrements dans la table des travaux.

Cette procédure stockée s'exécute toutes les 2 secondes à l'aide de l'astuce que j'ai apprise de ce message dba.stackexchange.com .

Le problème que je ne peux pas résoudre toute ma vie est la vitesse à laquelle les insertions en production sont effectuées. Mon application crée des tables de transfert temporaires et les remplit incroyablement rapidement. L'insert dans la production ne peut pas suivre le nombre de tables et finalement il y a un surplus de tables par milliers. La seule façon dont j'ai pu suivre les données entrantes est de supprimer toutes les clés, index, contraintes etc ... sur la SignalValuestable de production . Le problème auquel je suis confronté est que la table se retrouve avec autant d'enregistrements qu'il devient impossible d'interroger.

J'ai essayé de partitionner la table en utilisant la [Timestamp]colonne de partitionnement en vain. Toute forme d'indexation ralentit tellement les insertions qu'elles ne peuvent pas suivre. De plus, je devrais créer des milliers de partitions (une par minute? Heure?) Des années à l'avance. Je ne savais pas comment les créer à la volée

J'ai essayé de créer le partitionnement en ajoutant une colonne calculée à la table appelée TimestampMinutedont la valeur était, sur INSERT, DATEPART(MINUTE, GETUTCDATE()). Encore trop lent.

J'ai essayé d'en faire une table optimisée en mémoire selon cet article Microsoft . Peut-être que je ne comprends pas comment le faire, mais le MOT a ralenti les inserts d'une manière ou d'une autre.

J'ai vérifié le plan d'exécution de la procédure stockée et constaté que (je pense?) L'opération la plus intensive est

SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
    ON smd.SignalId = svs.SignalId

Pour moi, cela n'a pas de sens: j'ai ajouté la journalisation de l'horloge murale à la procédure stockée qui a prouvé le contraire.

En termes de journalisation du temps, cette instruction particulière ci-dessus s'exécute en ~ 300 ms sur 100 000 enregistrements.

La déclaration

INSERT INTO SignalValues SELECT * FROM #sValues

s'exécute en 2500-3000 ms sur 100 000 enregistrements. Supprimer du tableau les enregistrements concernés, par:

DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

prend encore 300 ms.

Comment puis-je accélérer cela? SQL Server peut-il gérer des milliards d'enregistrements par jour?

S'il est pertinent, il s'agit de SQL Server 2014 Enterprise x64.

Configuration matérielle:

J'ai oublié d'inclure du matériel dans la première partie de cette question. Ma faute.

Je préfère ceci avec ces déclarations: je sais que je perds des performances à cause de ma configuration matérielle. J'ai essayé plusieurs fois mais à cause du budget, du niveau C, de l'alignement des planètes, etc ... je ne peux malheureusement rien faire pour obtenir une meilleure configuration. Le serveur fonctionne sur une machine virtuelle et je ne peux même pas augmenter la mémoire car nous n'en avons tout simplement plus.

Voici mes informations système:

Information système

Le stockage est attaché au serveur VM via l'interface iSCSI à un boîtier NAS (cela dégradera les performances). Le boîtier NAS dispose de 4 disques dans une configuration RAID 10. Ce sont des disques durs de 4 To WD WD4000FYYZ avec une interface SATA de 6 Go / s. Le serveur n'a qu'un seul magasin de données configuré, donc tempdb et ma base de données sont sur la même banque de données.

Le DOP max est nul. Dois-je changer cela en une valeur constante ou simplement laisser SQL Server s'en occuper? J'ai lu sur RCSI: ai-je raison de supposer que le seul avantage de RCSI vient des mises à jour des lignes? Il n'y aura jamais de mise à jour de ces enregistrements particuliers, ils seront INSERTédités et SELECTédités. Est-ce que RCSI me bénéficiera toujours?

Mon tempdb est de 8 Mo. Sur la base de la réponse ci-dessous de jyao, j'ai changé les #sValues ​​en une table régulière pour éviter complètement tempdb. Cependant, les performances étaient à peu près les mêmes. J'essaierai d'augmenter la taille et la croissance de tempdb, mais étant donné que la taille de #sValues ​​sera plus ou moins toujours la même taille, je n'anticipe pas beaucoup de gain.

J'ai pris un plan d'exécution que j'ai joint ci-dessous. Ce plan d'exécution est une itération d'une table intermédiaire - 100 000 enregistrements. L'exécution de la requête a été assez rapide, environ 2 secondes, mais gardez à l'esprit que cela est sans index sur la SignalValuestable et que la SignalValuestable, la cible de la INSERT, ne contient aucun enregistrement.

Plan d'exécution

Brandon
la source
3
Avez-vous déjà expérimenté une durabilité retardée?
Martin Smith
2
Quels indices étaient en place avec des insertions de production lentes?
paparazzo
Jusqu'à présent, je ne pense pas qu'il y ait suffisamment de données ici pour savoir ce qui consomme réellement autant de temps. Est-ce du CPU? Est-ce IO? Étant donné que vous semblez obtenir 30 000 lignes par seconde, cela ne me semble pas E / S. Dois-je comprendre ce droit que vous êtes tout près d'atteindre votre objectif de perf? Vous avez besoin de 50 000 lignes par seconde, donc un lot de 100 000 toutes les 2 secondes devrait suffire. À l'heure actuelle, un lot semble prendre 3 secondes. Publiez le plan d'exécution réel d'une exécution représentative. Toute suggestion qui n'attaque pas les opérations les plus longues est sans objet.
usr
J'ai publié le plan d'exécution.
Brandon

Réponses:

7

J'ai calculé que, au sommet, le nombre moyen d'enregistrements se situe quelque part entre 3 et 4 milliards par jour (20 heures de fonctionnement).

À partir de votre capture d'écran, vous disposez uniquement de 8 Go de mémoire RAM totale et de 6 Go alloués à SQL Server. C'est trop bas pour ce que vous essayez de réaliser.

Je vous suggère de mettre à niveau la mémoire à une valeur plus élevée - 256 Go et d'augmenter également vos processeurs VM.

À ce stade, vous devez investir dans du matériel pour votre charge de travail.

Reportez-vous également au guide des performances de chargement des données - il décrit des moyens intelligents de charger efficacement les données.

Mon tempdb est de 8 Mo.

Sur la base de votre modification .. vous devriez avoir un tempdb raisonnable - de préférence plusieurs fichiers de données tempdb de taille égale avec TF 1117 et 1118 activé pour toute l'instance.

Je vous suggère d'obtenir un bilan de santé professionnel et de commencer à partir de là.

Recommande fortement

  1. Augmentez vos spécifications de serveur.

  2. Demandez à un professionnel * de faire un bilan de santé de votre instance de serveur de base de données et suivez les recommandations.

  3. Une fois par. et B. sont terminés, puis plongez-vous dans l'optimisation des requêtes et d'autres optimisations telles que la consultation des statistiques d'attente, des plans de requête, etc.

Remarque: Je suis un expert professionnel du serveur SQL chez hackhands.com - une entreprise pluridisciplinaire, mais je ne vous suggère en aucun cas de m'engager pour obtenir de l'aide. Je vous suggère simplement de demander une aide professionnelle en fonction de vos modifications uniquement.

HTH.

Kin Shah
la source
J'essaie de mettre sur pied une proposition (lire: mendier pour) plus de matériel pour cela. Avec cela à l'esprit et votre réponse ici, il n'y a rien d'autre du point de vue de la configuration de SQL Server ou de l'optimisation des requêtes que vous suggéreriez pour rendre cela plus rapide?
Brandon
1

Conseils généraux pour de tels problèmes avec les mégadonnées, face à un mur et rien ne fonctionne:

Un œuf va cuire 5 minutes environ. 10 œufs seront cuits en même temps si suffisamment d'électricité et d'eau.

Ou, en d'autres termes:

Tout d'abord, regardez le matériel; deuxièmement, séparez la logique du processus (remodelage des données) et faites-le en parallèle.

Il est tout à fait possible de créer un partitionnement vertical personnalisé de manière dynamique et automatisée, par nombre de tables et par taille de table; Si j'ai Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... et je ne sais pas où sont mes enregistrements et combien de partitions j'ai, exécutez la même requête (s) sur toutes les partitions personnalisées en même temps, sessions et assemblage séparés le résultat à traiter pour ma logique.

Goran Mancevski
la source
Le problème du PO semble être de gérer l'insertion et l'accès aux données nouvellement entrées, plus que de traiter les données d'il y a des semaines ou des mois. OP mentionne le partitionnement des données à la minute sur son horodatage (donc 60 partitions, divisant les données actuelles en compartiments séparés); le fractionnement par trimestre ne serait probablement pas d'une grande utilité. Votre point est bien compris en général, mais il est peu probable qu'il aide quelqu'un dans cette situation spécifique.
RDFozz
-1

Je ferai la vérification / optimisation suivante:

  1. Assurez-vous que les données et les fichiers journaux de la base de données de production ne se développent pas pendant l'opération d'insertion (faites-les augmenter au besoin)

  2. Ne pas utiliser

    select * into [dest table] from [source table];

    mais à la place, prédéfinissez la [table dest]. De plus, au lieu de supprimer la [table dest] et de la recréer, je tronquerai la table. De cette façon, si nécessaire, au lieu d'utiliser une table temporaire, j'utiliserais une table régulière. (Je peux également créer l'index sur [table dest] pour faciliter les performances de la requête de jointure)

  3. Au lieu d'utiliser SQL dynamique, je préfère utiliser des noms de table codés en dur avec une logique de codage pour choisir la table à utiliser.

  4. Je surveillerai également les performances d'E / S de la mémoire, du processeur et du disque pour voir s'il y a des famines de ressources pendant une grosse charge de travail.

  5. Puisque vous avez mentionné que vous pouvez gérer l'insertion en supprimant les index côté production, je vérifierais s'il y a de nombreux fractionnements de page, si c'est le cas, je diminuerais le facteur de remplissage des index et reconstruirais les index avant d'envisager de supprimer les index.

Bonne chance et j'adore votre question.

jyao
la source
Merci d'avoir répondu. J'avais fixé la taille de la base de données à 1 Go et augmenté de 1 Go en anticipant que les opérations de croissance prendraient du temps, ce qui a aidé à la vitesse au départ. Je vais essayer de mettre en œuvre la pré-croissance aujourd'hui. J'ai implémenté la table [dest] comme une table régulière mais je n'ai pas vu beaucoup de gain de performances. Je n'ai pas eu beaucoup de temps ces derniers jours mais je vais essayer de passer aux autres aujourd'hui.
Brandon