web-dev-qa-db-fra.com

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

J'ai écrit une application avec un backend SQL Server qui collecte et stocke et une très grande quantité d'enregistrements. J'ai calculé qu'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 la 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:

Staging tables 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_executesql Parce que les noms des tables des tables intermédiaires proviennent du texte des enregistrements de la table des travaux.

Cette procédure stockée s'exécute toutes les 2 secondes en utilisant l'astuce que j'ai apprise de ce post 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 production SignalValues table. 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 le [Timestamp] Comme 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 un partitionnement en ajoutant une colonne calculée à la table appelée TimestampMinute dont 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 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:

System Info

Le stockage est attaché au serveur VM via l'interface iSCSI à une boîte NAS (cela dégradera les performances). Le NAS = la boîte a 4 disques dans une configuration RAID 10. Ce sont des disques à disque tournant 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é, tempdb et ma base de données se trouvent 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 le gérer? 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 INSERTed et SELECTed. 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 ceci est sans index sur la table SignalValues et la table SignalValues, la cible de la INSERT, ne contient aucun enregistrement.

Execution plan

20
Brandon

J'ai calculé qu'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 totale RAM et 6 Go alloués à SQL Server. C'est beaucoup trop faible pour ce que vous essayez d'atteindre.

Je vous suggère de mettre à niveau la mémoire vers une valeur supérieure - 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 à 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 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.

7
Kin Shah

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

Un œuf va être cuit environ 5 minutes. 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ù se trouvent 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, des sessions séparées et un assemblage le résultat à traiter pour ma logique.

1
Goran Mancevski