Riesige Daten und Leistung in SQL Server

20

Ich habe eine Anwendung mit einem SQL Server-Backend geschrieben, die sehr viele Datensätze sammelt und speichert. Ich habe berechnet, dass die durchschnittliche Anzahl von Datensätzen zu Spitzenzeiten zwischen 3 und 4 Milliarden pro Tag liegt (20 Betriebsstunden).

Meine ursprüngliche Lösung (bevor ich die eigentliche Berechnung der Daten durchgeführt hatte) bestand darin, dass meine Anwendung Datensätze in dieselbe Tabelle einfügte, die von meinen Kunden abgefragt wurde. Das stürzte ab und brannte ziemlich schnell, offensichtlich, weil es unmöglich ist, eine Tabelle abzufragen, in die so viele Datensätze eingefügt wurden.

Meine zweite Lösung bestand darin, zwei Datenbanken zu verwenden, eine für die von der Anwendung empfangenen Daten und eine für clientfähige Daten.

Meine Anwendung empfing Daten, teilte sie in Gruppen von ~ 100.000 Datensätzen auf und fügte sie als Masseneintrag in die Staging-Tabelle ein. Nach ~ 100.000 Datensätzen erstellt die Anwendung im Handumdrehen eine weitere Staging-Tabelle mit demselben Schema wie zuvor und beginnt mit dem Einfügen in diese Tabelle. Es wird ein Datensatz in einer Auftragstabelle mit dem Namen der Tabelle erstellt, die 100.000 Datensätze enthält, und eine gespeicherte Prozedur auf der SQL Server-Seite verschiebt die Daten von den Staging-Tabellen in die clientfähige Produktionstabelle und löscht dann die Tabelle Tabelle temporäre Tabelle von meiner Anwendung erstellt.

Beide Datenbanken haben den gleichen Satz von 5 Tabellen mit dem gleichen Schema, mit Ausnahme der Staging-Datenbank, die die Auftragstabelle enthält. Die Staging-Datenbank hat keine Integritätsbeschränkungen, Schlüssel, Indizes usw. für die Tabelle, in der sich der Großteil der Datensätze befinden wird. Im Folgenden wird der Tabellenname angezeigt SignalValues_staging. Das Ziel war, dass meine Anwendung die Daten so schnell wie möglich in SQL Server überträgt. Der Workflow zum Erstellen von Tabellen im laufenden Betrieb, damit sie leicht migriert werden können, funktioniert ziemlich gut.

Im Folgenden sind die 5 relevanten Tabellen aus meiner Staging-Datenbank sowie meine Jobtabelle aufgeführt:

Staging-Tabellen Die gespeicherte Prozedur, die ich geschrieben habe, übernimmt das Verschieben der Daten aus allen Staging-Tabellen und das Einfügen in die Produktion. Unten sehen Sie den Teil meiner gespeicherten Prozedur, der aus den Staging-Tabellen in die Produktion eingefügt wird:

-- 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

Ich verwende, sp_executesqlweil die Tabellennamen für die Staging-Tabellen als Text aus den Datensätzen in der Jobtabelle stammen.

Diese gespeicherte Prozedur wird alle 2 Sekunden mit dem Trick ausgeführt, den ich aus diesem dba.stackexchange.com-Beitrag gelernt habe .

Das Problem, das ich für mein ganzes Leben nicht lösen kann, ist die Geschwindigkeit, mit der die Beilagen in der Produktion ausgeführt werden. Meine Anwendung erstellt temporäre Staging-Tabellen und füllt sie unglaublich schnell mit Datensätzen. Der Einsatz in der Produktion kann nicht mit der Anzahl der Tabellen mithalten, und schließlich gibt es einen Überschuss an Tabellen zu Tausenden. Die einzige Möglichkeit, mit den eingehenden Daten Schritt zu halten, besteht darin, alle Schlüssel, Indizes, Einschränkungen usw. auf dem Produktionstisch zu entfernen SignalValues. Das Problem, dem ich dann gegenüberstehe, ist, dass die Tabelle so viele Datensätze enthält, dass keine Abfrage mehr möglich ist.

Ich habe versucht, die Tabelle unter Verwendung der [Timestamp]Spalte als Partitionierung ohne Erfolg zu partitionieren. Jede Form der Indizierung verlangsamt die Einfügungen so sehr, dass sie nicht mithalten können. Außerdem müsste ich Tausende von Partitionen (jede Minute eine? Stunde?) Jahre im Voraus erstellen. Ich konnte nicht herausfinden, wie man sie im laufenden Betrieb erstellt

Ich versuchte , durch die Schaffung von Partitionieren eines berechneten Spalte der Tabelle bezeichnet das Hinzufügen , TimestampMinutedessen Wert war, auf INSERT, DATEPART(MINUTE, GETUTCDATE()). Immer noch zu langsam.

Ich habe versucht, eine speicheroptimierte Tabelle gemäß diesem Microsoft-Artikel zu erstellen . Vielleicht verstehe ich nicht, wie es geht, aber der TÜV hat die Einsätze irgendwie langsamer gemacht.

Ich habe den Ausführungsplan der gespeicherten Prozedur überprüft und festgestellt, dass (glaube ich?) Die intensivste Operation ist

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

Für mich macht das keinen Sinn: Ich habe der gespeicherten Prozedur die Protokollierung der Wanduhr hinzugefügt, die das Gegenteil bewiesen hat.

In Bezug auf die Zeitprotokollierung wird diese bestimmte Anweisung in ~ 300 ms bei 100.000 Datensätzen ausgeführt.

Die Aussage

INSERT INTO SignalValues SELECT * FROM #sValues

Wird in 2500-3000 ms auf 100.000 Datensätzen ausgeführt. Löschen der betroffenen Datensätze aus der Tabelle nach:

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

dauert weitere 300ms.

Wie kann ich das schneller machen? Kann SQL Server Milliarden von Datensätzen pro Tag verarbeiten?

Wenn es relevant ist, ist dies SQL Server 2014 Enterprise x64.

Hardwarekonfiguration:

Ich habe vergessen, Hardware in den ersten Durchgang dieser Frage aufzunehmen. Mein Fehler.

Ich gehe mit den folgenden Aussagen vor: Ich weiß, dass ich aufgrund meiner Hardwarekonfiguration etwas an Leistung verliere. Ich habe es schon oft versucht, aber aufgrund des Budgets, des C-Levels, der Ausrichtung der Planeten usw. kann ich leider nichts tun, um ein besseres Setup zu erzielen. Der Server läuft auf einer virtuellen Maschine und ich kann nicht einmal den Speicher vergrößern, weil wir einfach keine mehr haben.

Hier sind meine Systeminformationen:

Systeminformationen

Der Speicher wird über die iSCSI-Schnittstelle an eine NAS-Box an den VM-Server angeschlossen (dies beeinträchtigt die Leistung). Die NAS-Box verfügt über 4 Laufwerke in einer RAID 10-Konfiguration. Es handelt sich um 4 TB WD WD4000FYYZ-Laufwerke mit 6 GB / s SATA-Schnittstelle. Auf dem Server ist nur ein Datenspeicher konfiguriert, sodass sich tempdb und meine Datenbank im selben Datenspeicher befinden.

Max DOP ist Null. Sollte ich dies in einen konstanten Wert ändern oder einfach SQL Server damit umgehen lassen? Ich habe über RCSI nachgelesen: Stimmt die Annahme, dass der einzige Vorteil von RCSI mit Zeilenaktualisierungen verbunden ist? Es wird niemals Aktualisierungen für einen dieser bestimmten Datensätze geben, sie werden INSERTbearbeitet und SELECTbearbeitet. Kommt mir RCSI trotzdem zugute?

Mein tempdb ist 8mb. Basierend auf der Antwort von jyao unten habe ich die #sValues ​​in eine reguläre Tabelle geändert, um Tempdb insgesamt zu vermeiden. Die Leistung war jedoch in etwa gleich. Ich werde versuchen, die Größe und das Wachstum von Tempdb zu erhöhen, aber da die Größe von #sValues ​​mehr oder weniger immer dieselbe ist, erwarte ich keinen großen Gewinn.

Ich habe einen Ausführungsplan erstellt, den ich unten angefügt habe. Dieser Ausführungsplan ist eine Iteration einer Staging-Tabelle - 100.000 Datensätze. Die Ausführung der Abfrage war mit ca. 2 Sekunden relativ schnell. Beachten Sie jedoch, dass dies keine Indizes für die SignalValuesTabelle enthält und die SignalValuesTabelle, deren Ziel die INSERTist, keine Datensätze enthält.

Ausführungsplan

Brandon
quelle
3
Haben Sie schon mit verzögerter Haltbarkeit experimentiert?
Martin Smith
2
Welche Indizes waren mit langsamen Produktionseinsätzen vorhanden?
Paparazzo
Bisher glaube ich nicht, dass es hier genügend Daten gibt, um herauszufinden, was tatsächlich so viel Zeit kostet. Ist es CPU? Ist es IO? Da Sie 30.000 Zeilen pro Sekunde zu bekommen scheinen, sieht es für mich nicht nach IO aus. Verstehe ich das richtig, dass Sie Ihrem perfekten Ziel ziemlich nahe sind? Sie benötigen 50.000 Zeilen pro Sekunde, daher sollte eine Charge von 100.000 alle 2 Sekunden ausreichen. Momentan scheint eine Charge 3 Sekunden zu dauern. Buchen Sie den tatsächlichen Ausführungsplan eines repräsentativen Laufs. Jeder Vorschlag, der die zeitaufwändigsten Vorgänge nicht angreift, ist umstritten.
usr
Ich habe den Ausführungsplan veröffentlicht.
Brandon

Antworten:

7

Ich habe berechnet, dass die durchschnittliche Anzahl von Datensätzen zu Spitzenzeiten zwischen 3 und 4 Milliarden pro Tag liegt (20 Betriebsstunden).

In Ihrem Screenshot sind NUR 8 GB RAM und 6 GB SQL Server zugeordnet. Dies ist viel zu wenig für das, was Sie erreichen wollen.

Ich schlage vor, dass Sie den Speicher auf einen höheren Wert aktualisieren - 256 GB und auch Ihre VM-CPUs aufrüsten.

Sie müssen zu diesem Zeitpunkt für Ihre Arbeitsbelastung in Hardware investieren.

Lesen Sie auch den Leitfaden zum Laden von Daten - er beschreibt intelligente Methoden zum effizienten Laden der Daten.

Mein tempdb ist 8mb.

Basierend auf Ihrer Bearbeitung sollten Sie ein vernünftiges Tempdb haben - vorzugsweise mehrere Tempdb-Datendateien mit gleicher Größe und instanzweitem TF 1117- und 1118-Zugriff.

Ich würde Ihnen vorschlagen, einen professionellen Gesundheitscheck zu machen und von dort aus zu beginnen.

Sehr empfehlenswert

  1. Erhöhen Sie Ihre Serverspezifikation.

  2. Lassen Sie Ihre Datenbankserverinstanz von einer professionellen * Person überprüfen und befolgen Sie die Empfehlungen.

  3. Einmal. und B. sind fertig, dann tauchen Sie ein in die Abfrageoptimierung und andere Optimierungen wie das Betrachten von Wartestatistiken, Abfrageplänen usw.

Hinweis: Ich bin ein professioneller SQL Server-Experte bei hackhands.com - einem pluralsight-Unternehmen, empfehle Ihnen jedoch keinesfalls, mich für Hilfe zu engagieren. Ich schlage Ihnen lediglich vor, professionelle Hilfe nur aufgrund Ihrer Änderungen in Anspruch zu nehmen.

HTH.

Kin Shah
quelle
Ich versuche, einen Vorschlag (sprich: betteln um) mehr Hardware dafür zusammenzustellen. In Anbetracht dessen und Ihrer Antwort hier gibt es unter dem Gesichtspunkt der SQL Server-Konfiguration oder der Abfrageoptimierung nichts anderes, das Sie vorschlagen würden, um dies zu beschleunigen?
Brandon
1

Allgemeiner Rat für solche Probleme mit Big Data, wenn Sie vor einer Wand stehen und nichts funktioniert:

Ein Ei wird ungefähr 5 Minuten gekocht. 10 Eier werden gleichzeitig gekocht, wenn genügend Strom und Wasser vorhanden sind.

Oder mit anderen Worten:

Schauen Sie sich zuerst die Hardware an. Zweitens: Trennen Sie die Prozesslogik (Daten-Remodeling) und führen Sie dies parallel durch.

Es ist durchaus möglich, eine benutzerdefinierte vertikale Partitionierung dynamisch und automatisiert pro Tabellenzahl und pro Tabellengröße zu erstellen. Wenn ich Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... und nicht weiß, wo sich meine Datensätze befinden und wie viele Partitionen ich habe, führen Sie dieselben Abfragen für alle benutzerdefinierten Partitionen in derselben Zeit aus, trennen Sie Sitzungen und Assembly Das Ergebnis soll für meine Logik weiterverarbeitet werden.

Goran Mancevski
quelle
Das Problem des OP scheint darin zu bestehen, das Einfügen und den Zugriff auf neu eingegebene Daten zu regeln, anstatt Daten von vor Wochen oder Monaten zu verarbeiten. OP erwähnt die minutengenaue Partitionierung von Daten auf seinem Zeitstempel (also 60 Partitionen, die aktuelle Daten in separate Bereiche aufteilen); Eine Aufteilung nach Quartalen wäre wahrscheinlich keine große Hilfe. Ihr Standpunkt wird im Allgemeinen gut aufgenommen, aber es ist unwahrscheinlich, dass jemandem in dieser speziellen Situation geholfen wird.
RDFozz
-1

Ich werde folgende Prüfung / Optimierung durchführen:

  1. Stellen Sie sicher, dass die Daten- und Protokolldatei der Produktionsdatenbank während des Einfügevorgangs nicht vergrößert wird (vergrößern Sie sie bei Bedarf vorab).

  2. Verwende nicht

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

    Definieren Sie stattdessen die [Zieltabelle] im Voraus. Anstatt die [Zieltabelle] zu löschen und neu zu erstellen, werde ich auch die Tabelle abschneiden. Auf diese Weise würde ich bei Bedarf anstelle einer temporären Tabelle eine reguläre Tabelle verwenden. (Ich kann auch den Index für [Zieltabelle] erstellen, um die Ausführung der Verknüpfungsabfrage zu erleichtern.)

  3. Anstatt dynamisches SQL zu verwenden, würde ich lieber hartcodierte Tabellennamen mit einer Codierungslogik verwenden, um die zu verwendende Tabelle auszuwählen.

  4. Ich werde auch die Arbeitsspeicher-, CPU- und Festplatten-E / A-Leistung überwachen, um festzustellen, ob es bei hoher Arbeitsbelastung zu Ressourcenmangel kommt.

  5. Da Sie erwähnt haben, dass Sie die Einfügung durch Löschen der Indizes auf der Produktionsseite bewältigen können, würde ich prüfen, ob viele Seitenteile auftreten. In diesem Fall würde ich den Füllfaktor der Indizes verringern und die Indizes neu erstellen, bevor ich das Löschen in Betracht ziehe die Indizes.

Viel Glück und liebe deine Frage.

Jyao
quelle
Danke für die Antwort. Ich hatte die Datenbankgröße auf 1 GB festgelegt und war davon ausgegangen, dass Wachstumsvorgänge einige Zeit in Anspruch nehmen würden, was anfänglich zu einer Beschleunigung beitrug. Ich werde versuchen, das Vorwachstum heute umzusetzen. Ich habe die Tabelle [dest] als reguläre Tabelle implementiert, aber keinen großen Leistungszuwachs festgestellt. Ich hatte in den letzten Tagen nicht viel Zeit, aber ich werde versuchen, heute zu den anderen zu kommen.
Brandon