非常に大量のレコードを収集して保存するSQL Serverバックエンドを使用してアプリケーションを作成しました。ピーク時の平均レコード量は、1日あたり30〜40億(20時間の操作)の範囲にあると計算しました。
私の元の解決策(データの実際の計算を行う前)は、クライアントが照会する同じテーブルにアプリケーションがレコードを挿入することでした。明らかに、多くのレコードが挿入されているテーブルをクエリすることは不可能だからです。
2番目のソリューションは、2つのデータベースを使用することでした。1つはアプリケーションが受信したデータ用で、もう1つはクライアント対応データ用です。
私のアプリケーションはデータを受け取り、それを〜10万レコードのバッチにチャンクし、ステージングテーブルに一括挿入します。〜100kの記録後、アプリケーションはその場で、以前と同じスキーマで別のステージングテーブルを作成し、そのテーブルへの挿入を開始します。それは、100kレコードを持つテーブルの名前でジョブテーブルにレコードを作成し、SQL Server側のストアドプロシージャは、ステージングテーブルからクライアント対応の本番テーブルにデータを移動してから、アプリケーションによって作成された一時テーブル。
両方のデータベースには、同じスキーマを持つ5つのテーブルの同じセットがありますが、ジョブテーブルがあるステージングデータベースは例外です。ステージングデータベースには、大量のレコードが存在するテーブルに整合性の制約、キー、インデックスなどがありません。以下に示すように、テーブル名はSignalValues_staging
です。目標は、できるだけ早くデータをSQL Serverにバタンと置くことでした。簡単に移行できるようにテーブルをオンザフライで作成するワークフローは非常にうまく機能します。
以下は、ステージングデータベースからの5つの関連テーブルと、jobsテーブルです。
私が作成したストアドプロシージャは、すべてのステージングテーブルからのデータの移動と本番環境への挿入を処理します。以下は、ステージングテーブルからプロダクションに挿入するストアドプロシージャの一部です。
-- 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
sp_executesql
ステージングテーブルのテーブル名は、ジョブテーブルのレコードからのテキストとして取得されるため、使用します。
このストアドプロシージャは、このdba.stackexchange.comの投稿から学んだトリックを使用して2秒ごとに実行されます。
私の人生で解決できない問題は、生産への挿入が実行される速度です。私のアプリケーションは、一時的なステージングテーブルを作成し、それらを非常に迅速にレコードで満たします。実稼働環境への挿入では、テーブルの量に対応できず、最終的には数千に及ぶテーブルの余剰があります。唯一私が今まで入ってくるデータについていくことができました方法は、生産上...すべてのキー、インデックス、制約などを削除することですSignalValues
テーブル。私が直面する問題は、テーブルが非常に多くのレコードで終わるため、クエリが不可能になることです。
を使用してテーブルを[Timestamp]
パーティション分割列として使用してみましたが、役に立ちませんでした。どんな形の索引付けでも、挿入が非常に遅くなり、追いつかなくなります。さらに、数千年前に何千ものパーティションを作成する必要があります(1分ごとに1時間?)。それらをその場で作成する方法がわかりませんでした
TimestampMinute
値がon INSERT
であるテーブルに計算列を追加して、パーティションを作成しようとしましたDATEPART(MINUTE, GETUTCDATE())
。まだ遅すぎる。
このMicrosoftの記事に従って、メモリ最適化テーブルにしようとしました。どうすればいいのか分からないかもしれませんが、MOTによって挿入が遅くなりました。
ストアドプロシージャの実行プランを確認しましたが、最も集中的な操作は
SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
私にはこれは意味がありません。それ以外の場合に証明されたストアドプロシージャにウォールクロックロギングを追加しました。
タイムロギングに関しては、上記の特定のステートメントは、10万レコードで約300ミリ秒で実行されます。
声明
INSERT INTO SignalValues SELECT * FROM #sValues
10万レコードで2500〜3000ミリ秒で実行されます。以下から影響を受けるレコードをテーブルから削除します。
DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId
さらに300msかかります。
どうすればこれを速くできますか?SQL Serverは1日に数十億件のレコードを処理できますか?
関連する場合、これはSQL Server 2014 Enterprise x64です。
ハードウェア構成:
この質問の最初のパスにハードウェアを含めるのを忘れました。私の悪い。
これには、これらのステートメントを前置きします。ハードウェア構成が原因でパフォーマンスが低下していることは知っています。私は何度も試しましたが、予算、Cレベル、惑星の配置などのために、残念ながら、より良いセットアップを得るためにできることは何もありません。サーバーは仮想マシン上で実行されており、メモリが増えないため、メモリを増やすことさえできません。
システム情報は次のとおりです。
ストレージは、NASボックスへのiSCSIインターフェイスを介してVMサーバーに接続されます(これによりパフォーマンスが低下します)。NASボックスには、RAID 10構成の4つのドライブがあります。6GB /秒のSATAインターフェースを備えた4TB WD WD4000FYYZ回転ディスクドライブです。サーバーには1つのデータストアのみが構成されているため、tempdbとデータベースは同じデータストアにあります。
最大DOPはゼロです。これを定数値に変更するか、SQL Serverに処理させるだけですか?RCSIを読みます:RCSIからの唯一の利点は行の更新にあると仮定して正しいですか?これらの特定のレコードは更新されず、INSERT
編集およびSELECT
編集されます。RCSIには引き続きメリットがありますか?
私のtempdbは8MBです。jyaoからの以下の回答に基づいて、#sValuesを通常のテーブルに変更して、tempdbを完全に回避しました。ただし、パフォーマンスはほぼ同じでした。tempdbのサイズと成長を増やしてみますが、#sValuesのサイズが常にほぼ同じサイズであることを考えると、あまり期待できません。
以下に添付した実行計画を取りました。この実行計画は、ステージングテーブルの1回の繰り返し、つまり100,000レコードです。クエリの実行は約2秒でかなり高速でしたが、これにはSignalValues
テーブルのインデックスがなくSignalValues
、のターゲットであるテーブルにはINSERT
レコードがないことに注意してください。