5

Attualmente stiamo sviluppando un'applicazione web che gestisce enormi quantità di dati di archivio che si trovano in una tabella di database. Una riga di dati nella tabella è composta da un ID di riga univoco, due ID che identificano una macchina e un punto dati, un valore e un timestamp. Ogni macchina invia i suoi dati a questa tabella ogni volta che una variazione di valore supera una determinata soglia. La tabella di solito contiene da milioni a centinaia di milioni di voci.Miglior approccio all'aggregazione automatica dei dati nel DB

Per scopi di visualizzazione, ho creato una stored procedure che accetta i due ID necessari per identificare una macchina e un datapoint e anche un datetime di inizio e fine. Quindi aggrega i valori tra inizio e fine in blocchi di lunghezza variabile (in genere 15 minuti, 1 ora, 7 giorni ecc.) E restituisce i valori medio, minimo e massimo per ogni blocco nell'intervallo di tempo specificato.

Questo metodo funziona, ma richiede molto tempo, anche con molte ottimizzazioni e indici DB. Quindi sulla pagina del frontend grafico ci vogliono dai 10 ai 60 secondi per visualizzare i dati per l'intervallo e la macchina selezionati, il che è troppo credo.

Così ho iniziato a pensare a creare una nuova tabella che contenga dati pre-aggregati per ogni macchina per ogni "blocco". Per raggiungere questo obiettivo, la procedura di aggregazione dovrebbe essere chiamata automaticamente ogni [chunksize] minuti/ore/giorni per ogni macchina. Pezzi più ruvidi potrebbero quindi essere facilmente creati da pezzi più fini, ecc. Per quanto posso vedere, ciò accrescerebbe drammaticamente il tutto.

La domanda è: qual è il modo migliore per implementare un'aggregazione periodica? C'è un modo per fare in modo che il database faccia il lavoro da solo? O devo implementare una soluzione basata su timer all'interno dell'applicazione Web ASP.NET MVC? Quest'ultimo richiede che l'app Web funzioni sempre, il che probabilmente non è il modo migliore in quanto potrebbe essere inattivo per vari motivi. Un'altra opzione sarebbe un'applicazione o un servizio indipendente che si occupa di questo compito. Ci sono altri modi a cui non ho pensato? Come affronteresti questo problema?

+1

È possibile utilizzare un [processo programmato] (https://msdn.microsoft.com/en-us/library/ms191439.aspx) nello stesso server Sql per farlo. –

+0

Sembra promettente, grazie! – Robert

risposta

6

Nel nostro sistema abbiamo una tabella con dati grezzi originali. Questi dati grezzi sono riepilogati in intervalli orari, giornalieri e settimanali (somma, min, max di valori non elaborati per ogni intervallo).

Conserviamo i dati grezzi per 30 giorni (4 settimane), ogni ora per 43 giorni (6 settimane), giornalieri per 560 giorni (18 mesi), settimanali per 10 anni. Ogni notte queste quattro tabelle vengono "pulite" e i dati più vecchi della soglia vengono cancellati. La tabella oraria ha circa 30 milioni di righe, ogni giorno 18 milioni di righe. Alcuni rapporti/grafici utilizzano dati orari, la maggior parte utilizza dati giornalieri. Occasionalmente abbiamo bisogno di guardare i dati grezzi per un'indagine dettagliata di un problema.

Ho un'applicazione dedicata scritta in C++, che gira sul server 24/7 e raccoglie i dati grezzi da ~ 200 altri server e li inserisce nel database centrale. All'interno dell'applicazione periodicamente (ogni 10 minuti) viene richiamata una stored procedure che ricalcola i riepiloghi. Questa procedura memorizzata potrebbe anche essere eseguita dall'utente finale in qualsiasi momento, se l'utente desidera visualizzare i dati più recenti. Di solito ci vogliono circa 10 secondi per funzionare, quindi normalmente l'utente vede un riepilogo in ritardo. Quindi, tecnicamente potrebbe esserci un lavoro programmato sul server che esegue la procedura ogni 10 minuti. Quando lo faccio attraverso un'applicazione ho un controllo più preciso su altri thread che raccolgono i dati. Essenzialmente, metto in pausa i tentativi di inserire nuovi dati mentre vengono riepilogati. Ma è possibile ottenere lo stesso effetto usando solo stored procedure indipendenti.

Nel mio caso potrei rendere il ricalcolo dei sommari piuttosto efficiente.

  • Come nuovi flussi di dati nel database durante questa finestra di 10 minuti, inserisco i dati grezzi direttamente nella tabella principale. I punti di dati grezzi non vengono mai aggiornati, vengono solo aggiunti (inseriti).Quindi, questo passaggio è semplice ed efficiente. Io uso la stored procedure con un parametro con valori di tabella e passo un chunk di nuovi dati in una chiamata. Quindi molte righe sono inserite in una dichiarazione INSERT, che è buona.

  • Le tabelle di riepilogo vengono aggiornate con i nuovi dati ogni 10 minuti utilizzando una seconda procedura memorizzata. Alcune delle righe esistenti devono essere aggiornate, alcune righe vengono aggiunte. Per fare ciò in modo efficiente, ho una tabella separata di "staging" con ID della macchina, data-ora oraria, data-ora giornaliera, colonne settimanali data-ora. Mentre inserisco i dati grezzi nella tabella principale, inserisco anche gli ID macchina interessati e gli intervalli di tempo interessati su questa tabella di staging.

Quindi, ci sono due procedure memorizzate principali. L'applicazione esegue il loop su 200 server remoti utilizzando diversi thread e scarica nuovi dati da ciascun server in un ciclo infinito. Non appena viene scaricato un nuovo batch di dati da un server remoto, viene richiamata la prima stored procedure. Questo succede frequentemente. Questa procedura inserisce batch di dati non elaborati nella tabella non elaborata così com'è e inserisce l'elenco degli intervalli di tempo interessati nella tabella "staging".

Say, il lotto in entrata dei dati grezzi assomiglia a questo:

ID timestamp   raw_value 
1 2015-01-01 23:54:45 123 
1 2015-01-01 23:57:12 456 
1 2015-01-02 00:03:23 789 
2 2015-01-02 02:05:21 909 

4 righe vengono inserite nella tabella principale è (ID, timestamp, valore).

3 righe vengono inserite nella tabella di gestione temporanea (generalmente ci sono molti valori con il timestamp dal medesimo ore, quindi ci sono un sacco di righe prime, ma pochi nella tabella di gestione temporanea):

ID hourlytimestamp  dailytimestamp  weeklytimestamp 
1 2015-01-01 23:00:00 2015-01-01 00:00:00 2014-12-29 00:00:00 
1 2015-01-02 00:00:00 2015-01-02 00:00:00 2014-12-29 00:00:00 
2 2015-01-02 00:00:00 2015-01-02 00:00:00 2014-12-29 00:00:00 

Nota: qui raccolgo/condensa/unisco tutti gli ID e i timestamp in un set univoco e questa tabella di staging non ha affatto i valori, contiene solo ID interessati e intervalli di tempo (StatsToRecalc è la tabella di staging, @ParamRows è parametro della stored procedure che ha un batch di righe con nuovi dati):

DECLARE @VarStart datetime = '20000103'; -- it is Monday 
INSERT INTO dbo.StatsToRecalc 
    (ID 
    ,PeriodBeginLocalDateTimeHour 
    ,PeriodBeginLocalDateTimeDay 
    ,PeriodBeginLocalDateTimeWeek) 
SELECT DISTINCT 
    TT.[ID], 
    -- Truncate time to 1 hour. 
    DATEADD(hour, DATEDIFF(hour, @VarStart, TT.PlaybackStartedLocalDateTime), @VarStart), 
    -- Truncate time to 1 day. 
    DATEADD(day, DATEDIFF(day, @VarStart, TT.PlaybackStartedLocalDateTime), @VarStart), 
    -- Truncate time to 1 week. 
    DATEADD(day, ROUND(DATEDIFF(day, @VarStart, TT.PlaybackStartedLocalDateTime)/7, 0, 1) * 7, @VarStart) 
FROM @ParamRows AS TT; 

Quindi c'è il semplice INSERT nella tabella non elaborata da @ParamRows.

Quindi, ci sono molte INSERTS nelle tabelle raw e di staging utilizzando questa procedura da molti thread per 10 minuti.

Ogni 10 minuti viene chiamata una seconda procedura che ricalcola i riepiloghi.

prima cosa che fa è avvia una transazione e chiude la tabella di gestione temporanea fino alla fine della transazione:

SELECT @VarCount = COUNT(*) 
FROM dbo.StatsToRecalc 
WITH (HOLDLOCK) 

Se la tabella di gestione temporanea StatsToRecalc non è vuoto, abbiamo bisogno di fare qualcosa. Poiché questa tabella è bloccata, tutti i thread di lavoro non interferirebbero e attenderebbero il ricalcolo prima di aggiungere altri dati.

Utilizzando questa tabella di staging, è possibile determinare rapidamente quali ore, giorni e settimane per cui ID devono essere ricalcolati. Il calcolo del riepilogo effettivo viene eseguito nell'istruzione MERGE, che elabora tutti gli ID e gli intervalli interessati in un colpo solo. Corro tre MERGEs per sommare i dati grezzi in riepilogo orario, poi ogni ora in giorno e poi giornalmente in settimana. Quindi la tabella di staging viene svuotata (ogni 10 minuti), quindi non diventa mai troppo grande.

Ogni MERGE in un primo momento fa un elenco di ID e timestamp che sono stati colpiti dallo scorso ricalcolo (per esempio, per l'aggiornamento tavolo tutti i giorni dalle oraria):

WITH 
CTE_Changed (ID, PeriodBeginLocalDateTimeDay) 
AS 
(
    SELECT 
     dbo.StatsToRecalc.ID 
     , dbo.StatsToRecalc.PeriodBeginLocalDateTimeDay 
    FROM 
     dbo.StatsToRecalc 
    GROUP BY 
     dbo.StatsToRecalc.ID 
     ,dbo.StatsToRecalc.PeriodBeginLocalDateTimeDay 
) 

e poi si unisce tabella oraria con questo CTE in MERGE:

MERGE INTO dbo.StatsDay AS Dest 
USING 
(
    SELECT 
     ...     
    FROM 
     dbo.StatsHour 
     INNER JOIN CTE_Changed ON 
      CTE_Changed.ID = dbo.StatsHour.ID AND 
      CTE_Changed.PeriodBeginLocalDateTimeDay = dbo.StatsHour.PeriodBeginLocalDateTimeDay 
) 
... 

per aiutare con questo multi-fase sommando ho colonne di supporto in grezzo, ogni ora e tavole quotidiane. Ad esempio, la tabella oraria ha una colonna PeriodBeginLocalDateTimeHour che contiene i valori come questi:

2015-01-01 22:00:00 
2015-01-01 23:00:00 
2015-01-02 00:00:00 
2015-01-02 01:00:00 
... 

, cioè confini di un'ora. Allo stesso tempo, v'è una seconda colonna che contiene questi timestamp "troncato" alla giornata di confine: PeriodBeginLocalDateTimeDay, che detiene i valori come questi:

2015-01-01 00:00:00 
2015-01-02 00:00:00 
... 

, vale a dire i confini di una giornata. La seconda colonna viene utilizzata solo quando riassumo le ore in giorni: non è necessario calcolare l'indicatore del giorno al volo, ma piuttosto utilizzare i valori indicizzati persistenti.

Dovrei aggiungere, che nel mio caso è OK se quell'applicazione C++ dedicata era inattiva per un po '. Significa solo che i dati saranno ritardati per più di 10 minuti, ma nulla andrebbe perso.

+0

Grazie per la tua ampia risposta! Questo è un approccio interessante. Al momento sto giocando con i lavori programmati che Zohar ha menzionato sopra. Un commento sul layout della tabella: non sarebbe più performante se crei i tuoi valori "settimanali" da valori "giornalieri" già aggregati (che sono calcolati dai valori "orari"), in modo da non dover ricalcolare da tutte le righe stagingtable più e più volte? – Robert

+1

@Robert, questo è esattamente quello che faccio. Ho aggiunto più dettagli alla risposta. La tabella di staging viene utilizzata per unire le tabelle raw, quindi ogni ora e ogni giorno per trovare in modo efficiente quegli ID e quelle ore, giorni e settimane che devono essere aggiornati. I dati provenienti dai server remoti hanno iniziato a scorrere per 10 minuti. Quali server sono stati elaborati durante questo periodo? Il server remoto potrebbe essere inattivo da alcuni giorni: quali sono i timestamp ricevuti dal server? (Possono avere 20 minuti o 2 giorni) La tabella di staging risponde in modo efficiente a queste domande. Forse "messa in scena" non è un buon nome per questo ... –

+0

Hai ragione, ora ho capito. Grazie per aver condiviso la tua soluzione, questo è un buon punto di partenza per gli altri. – Robert

Problemi correlati