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.
È possibile utilizzare un [processo programmato] (https://msdn.microsoft.com/en-us/library/ms191439.aspx) nello stesso server Sql per farlo. –
Sembra promettente, grazie! – Robert