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:
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_executesql
perché 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 SignalValues
tabella 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 TimestampMinute
cui 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:
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 INSERT
ed ed SELECT
ed. 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 SignalValues
tabella e la SignalValues
tabella, la destinazione della INSERT
, non ha record in essa.