Dati e prestazioni enormi in SQL Server


20

Ho scritto un'applicazione con un back-end di SQL Server che raccoglie e archivia e una quantità estremamente elevata di record. Ho calcolato che, al culmine, la quantità media di record è da qualche parte nel viale di 3-4 miliardi al giorno (20 ore di funzionamento).

La mia soluzione originale (prima che avessi fatto il calcolo effettivo dei dati) era che la mia applicazione inserisse i record nella stessa tabella richiesta dai miei clienti. Ciò si è schiantato e bruciato abbastanza rapidamente, ovviamente, perché è impossibile eseguire una query su una tabella in cui sono stati inseriti molti record.

La mia seconda soluzione era quella di utilizzare 2 database, uno per i dati ricevuti dall'applicazione e uno per i dati pronti per il cliente.

La mia applicazione riceveva dati, li divideva in lotti di ~ 100k record e li inseriva in blocco nella tabella di gestione temporanea. Dopo ~ 100k registrazioni l'applicazione avrebbe creato al volo un'altra tabella di gestione temporanea con lo stesso schema di prima e avrebbe iniziato a inserirla in quella tabella. Creerebbe un record in una tabella dei lavori con il nome della tabella con 100.000 record e una procedura memorizzata sul lato SQL Server sposterebbe i dati dalle tabelle di gestione temporanea alla tabella di produzione pronta per il client, quindi rilasciare il tabella tabella temporanea creata dalla mia applicazione.

Entrambi i database hanno lo stesso set di 5 tabelle con lo stesso schema, ad eccezione del database di gestione temporanea che ha la tabella dei lavori. Il database di gestione temporanea non ha vincoli di integrità, chiave, indici ecc ... nella tabella in cui risiederà la maggior parte dei record. Di seguito, il nome della tabella è SignalValues_staging. L'obiettivo era che la mia applicazione colpisse i dati in SQL Server il più rapidamente possibile. Il flusso di lavoro di creazione di tabelle al volo in modo che possano essere facilmente migrate funziona abbastanza bene.

Di seguito sono riportate le 5 tabelle pertinenti dal mio database di gestione temporanea, oltre alla mia tabella dei lavori:

Tabelle di gestione temporanea La procedura memorizzata che ho scritto gestisce lo spostamento dei dati da tutte le tabelle di gestione temporanea e l'inserimento in produzione. Di seguito è la parte della mia procedura memorizzata che inserisce in produzione dalle tabelle di gestione temporanea:

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

Uso sp_executesqlperché i nomi delle tabelle per le tabelle di gestione temporanea provengono come testo dai record nella tabella dei lavori.

Questa procedura memorizzata viene eseguita ogni 2 secondi utilizzando il trucco che ho imparato da questo post di dba.stackexchange.com .

Il problema che non posso risolvere per la vita di me è la velocità con cui vengono eseguiti gli inserti in produzione. La mia applicazione crea tabelle temporanee di staging e le riempie di record in modo incredibilmente rapido. L'inserimento in produzione non può tenere il passo con la quantità di tabelle e alla fine c'è un surplus di tabelle in migliaia. L' unico modo in cui sono mai stato in grado di tenere il passo con i dati in arrivo è quello di rimuovere tutte le chiavi, indici, vincoli ecc ... sulla SignalValuestabella di produzione . Il problema che devo affrontare è che la tabella finisce con così tanti record che diventa impossibile interrogare.

Ho provato a partizionare la tabella usando [Timestamp]come colonna di partizionamento senza alcun risultato. Qualsiasi forma di indicizzazione rallenta così tanto gli inserti che non riescono a tenere il passo. Inoltre, avrei bisogno di creare migliaia di partizioni (una ogni minuto? Ora?) Anni prima. Non sono riuscito a capire come crearli al volo

Ho cercato di creare partizionamento aggiungendo una colonna calcolata alla tabella denominata TimestampMinutecui valore è stato, in INSERT, DATEPART(MINUTE, GETUTCDATE()). Ancora troppo lento.

Ho provato a renderlo una tabella ottimizzata per la memoria secondo questo articolo di Microsoft . Forse non capisco come farlo, ma il MOT ha in qualche modo rallentato gli inserti.

Ho controllato il piano di esecuzione della procedura memorizzata e ho scoperto che (penso?) L'operazione più intensa è

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

Per me questo non ha senso: ho aggiunto la registrazione dell'orologio da parete alla procedura memorizzata che ha dimostrato il contrario.

In termini di time-logging, quella particolare istruzione sopra viene eseguita in ~ 300ms su 100k record.

La dichiarazione

INSERT INTO SignalValues SELECT * FROM #sValues

viene eseguito in 2500-3000 ms su record da 100k. Eliminare dalla tabella i record interessati, per:

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

richiede altri 300ms.

Come posso renderlo più veloce? SQL Server può gestire miliardi di record al giorno?

Se è pertinente, si tratta di SQL Server 2014 Enterprise x64.

Configurazione hardware:

Ho dimenticato di includere l'hardware nel primo passaggio di questa domanda. Colpa mia.

Premetto questo con queste affermazioni: so che sto perdendo alcune prestazioni a causa della mia configurazione hardware. Ci ho provato molte volte, ma a causa del budget, del livello C, dell'allineamento dei pianeti, ecc ... purtroppo non posso fare nulla per ottenere una configurazione migliore. Il server è in esecuzione su una macchina virtuale e non riesco nemmeno ad aumentare la memoria perché semplicemente non ne abbiamo più.

Ecco le mie informazioni di sistema:

Informazioni di sistema

Lo spazio di archiviazione è collegato al server VM tramite l'interfaccia iSCSI a un NAS box (ciò peggiorerà le prestazioni). La scatola NAS ha 4 unità in una configurazione RAID 10. Sono unità disco rotanti WD WD4000FYYZ da 4 TB con interfaccia SATA da 6 GB / s. Il server ha un solo archivio dati configurato, quindi tempdb e il mio database si trovano nello stesso archivio dati.

Il massimo DOP è zero. Devo cambiarlo in un valore costante o lasciare che sia SQL Server a gestirlo? Ho letto su RCSI: ho ragione nel ritenere che l'unico vantaggio di RCSI sia rappresentato dagli aggiornamenti delle righe? Non ci saranno mai aggiornamenti per nessuno di questi record particolari, saranno INSERTed ed SELECTed. RCSI mi gioverà ancora?

Il mio tempdb è 8mb. Sulla base della risposta seguente di jyao, ho cambiato i #sValues ​​in una tabella normale per evitare del tutto tempdb. Le prestazioni erano quasi le stesse. Proverò ad aumentare la dimensione e la crescita di tempdb, ma dato che la dimensione di #sValues ​​sarà più o meno sempre la stessa dimensione, non prevedo un grande guadagno.

Ho preso un piano di esecuzione che ho allegato di seguito. Questo piano di esecuzione è un'iterazione di una tabella di gestione temporanea: 100.000 record. L'esecuzione della query è stata abbastanza rapida, circa 2 secondi, ma tieni presente che questo è senza indici sulla SignalValuestabella e la SignalValuestabella, la destinazione della INSERT, non ha record in essa.

Progetto esecutivo


3
Hai già sperimentato una durata ritardata?
Martin Smith,

2
Quali indici erano in atto con inserti di produzione lenti?
paparazzo,

Finora non credo che ci siano abbastanza dati qui per scoprire cosa sta effettivamente consumando così tanto tempo. È CPU? È IO? Dal momento che sembra che tu stia ottenendo 30k righe al secondo, non mi sembra IO. Capisco questo diritto che sei abbastanza vicino a raggiungere il tuo obiettivo perfetto? Sono necessarie 50.000 righe al secondo, quindi dovrebbe essere sufficiente un batch di 100.000 ogni 2 secondi. In questo momento un batch sembra richiedere 3 secondi. Registrare il piano di esecuzione effettivo di una corsa rappresentativa. Qualsiasi suggerimento che non attacca le operazioni che richiedono più tempo è controverso.
usr

Ho pubblicato il piano di esecuzione.
Brandon,

Risposte:


7

Ho calcolato che, al culmine, la quantità media di record è da qualche parte nel viale di 3-4 miliardi al giorno (20 ore di funzionamento).

Dal tuo screenshot, hai SOLO 8 GB di memoria RAM totale e 6 GB allocati a SQL Server. Questo è troppo basso per quello che stai cercando di ottenere.

Ti suggerisco di aggiornare la memoria a un valore più alto - 256 GB e aumentare anche le CPU della VM.

A questo punto è necessario investire in hardware per il carico di lavoro.

Consulta anche la guida alle prestazioni di caricamento dei dati : descrive i modi intelligenti per caricare in modo efficiente i dati.

Il mio tempdb è 8mb.

In base alla tua modifica .. dovresti avere un tempdb sensibile - preferibilmente più file di dati tempdb di dimensioni uguali insieme a TF 1117 e 1118 abilitati a livello di istanza.

Vorrei suggerire di ottenere un controllo sanitario professionale e iniziare da lì.

Altamente raccomandato

  1. Aumenta le specifiche del tuo server.

  2. Chiedi a una persona professionale * di eseguire un controllo dello stato dell'istanza del tuo server di database e seguire i consigli.

  3. Una volta a. e B. è fatto, quindi immergiti nell'ottimizzazione delle query e in altre ottimizzazioni come guardare le statistiche di attesa, i piani di query, ecc.

Nota: sono un esperto di SQL Server professionale su hackhands.com - una società pluralsight, ma in nessun modo ti consiglio di assumermi per chiedere aiuto. Ti sto semplicemente suggerendo di richiedere un aiuto professionale basato esclusivamente sulle tue modifiche.

HTH.


Sto cercando di mettere insieme una proposta (leggi: accattonaggio) più hardware per questo. Con questo in mente e la tua risposta qui, non c'è nient'altro da una configurazione di SQL Server o dal punto di vista dell'ottimizzazione delle query che suggeriresti di renderlo più veloce?
Brandon,

1

Consigli generali per tali problemi con i big-data, di fronte a un muro e niente funziona:

Un uovo verrà cotto 5 minuti circa. 10 uova saranno cotte contemporaneamente se abbastanza elettricità e acqua.

O, in altre parole:

Innanzitutto, guarda l'hardware; in secondo luogo, separare la logica di processo (rimodellamento dei dati) e farlo in parallelo.

È del tutto possibile creare partizioni verticali personalizzate dinamicamente e automatizzate, per conteggio delle tabelle e dimensioni delle tabelle; Se ho Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... e non so dove siano i miei record e quante partizioni ho, eseguo le stesse query su tutte le partizioni personalizzate nello stesso tempo, sessioni separate e assembly il risultato da elaborare in avanti per la mia logica.


Il problema del PO sembra gestire l'inserimento e l'accesso ai dati appena immessi, più che l'elaborazione dei dati di settimane o mesi fa. OP menziona i dati di partizionamento al minuto sul suo timestamp (quindi 60 partizioni, suddividendo i dati correnti in bucket separati); dividere per trimestre non sarebbe probabilmente di grande aiuto. Il tuo punto è ben accolto in generale, ma è improbabile che aiuti qualcuno in questa situazione specifica.
RDFozz,

-1

Farò il seguente controllo / ottimizzazione:

  1. Assicurarsi che sia il file di dati che i log del database di produzione non crescano durante l'operazione di inserimento (pre-crescere se necessario)

  2. Non usare

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

    ma invece, pre-definire la [tabella dest]. Inoltre, invece di eliminare la [tabella dest] e ricrearla, troncerò la tabella. In questo modo, se necessario, invece di utilizzare la tabella temporanea, utilizzerei la tabella normale. (Potrei anche creare l'indice su [tabella dest] per facilitare l'esecuzione della query di join)

  3. Invece di usare sql dinamico, preferirei usare nomi di tabella hardcoded con qualche logica di codifica per scegliere quale tabella operare.

  4. Monitorerò anche le prestazioni di I / O di memoria, CPU e disco per vedere se ci sono carenze di risorse durante un grosso carico di lavoro.

  5. Dato che hai menzionato che puoi gestire l'inserimento rilasciando gli indici dal lato della produzione, controllerei se si verificano molte suddivisioni di pagina, in tal caso, diminuirei il fattore di riempimento degli indici e ricostruire gli indici prima di prendere in considerazione la possibilità di eliminare gli indici.

Buona fortuna e ama la tua domanda.


Grazie per la risposta. Avevo impostato la dimensione del database su 1 GB e cresciuto di 1 GB prevedendo che le operazioni di crescita avrebbero richiesto del tempo, il che inizialmente ha contribuito alla velocità. Proverò ad attuare la pre-crescita oggi. Ho implementato la tabella [dest] come una tabella normale ma non ho visto molti miglioramenti delle prestazioni. Non ho avuto molto tempo negli ultimi giorni, ma cercherò di arrivare agli altri oggi.
Brandon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.