SQL Serverバックエンドを使用して、非常に大量のレコードを収集および保存するアプリケーションを作成しました。ピーク時に、平均レコード数は1日あたり30億から40億(20時間の運用)の大通りのどこかにあると計算しました。
(データの実際の計算を行う前の)私の元の解決策は、クライアントから照会されたのと同じテーブルにアプリケーションがレコードを挿入することでした。クラッシュしてかなり早く焼けたのは明らかです。これだけ多くのレコードが挿入されているテーブルをクエリすることは不可能だからです。
2つ目の解決策は、2つのデータベースを使用することでした。1つはアプリケーションが受信したデータ用、もう1つはクライアント対応データ用です。
私のアプリケーションはデータを受信し、それを10万件以下のレコードのバッチに分割し、ステージングテーブルに一括挿入します。アプリケーションが10万件を超えると、アプリケーションはその場で、前と同じスキーマで別のステージングテーブルを作成し、そのテーブルへの挿入を開始します。それは100kレコードを持つテーブルの名前でジョブテーブルにレコードを作成し、SQL Server側のストアドプロシージャはステージングテーブルからクライアント対応の実稼働テーブルにデータを移動し、次にドロップしますアプリケーションによって作成された一時テーブル。
両方のデータベースには、jobsテーブルを持つステージングデータベースを除いて、同じスキーマを持つ同じ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
_
ステージングテーブルのテーブル名は、jobsテーブルのレコードからのテキストとして取得されるため、_sp_executesql
_を使用します。
このストアドプロシージャは、 このdba.stackexchange.comの投稿 から学んだトリックを使用して2秒ごとに実行されます。
私の人生で解決できない問題は、本番環境への挿入が実行される速度です。私のアプリケーションは、一時的なステージングテーブルを作成し、信じられないほど迅速にレコードで埋めます。本番環境への挿入はテーブルの量に追いつくことができず、最終的には数千のテーブルの余剰が生じます。 only受信データに追いつくことができた方法は、プロダクションのすべてのキー、インデックス、制約などを削除することですSignalValues
テーブル。次に直面する問題は、テーブルが非常に多くのレコードで終わり、クエリできなくなることです。
_[Timestamp]
_をパーティション列として使用してテーブルをパーティション分割してみましたが、役に立ちませんでした。どのような形式のインデックス作成でも、挿入が遅くなりすぎて、追いつけなくなります。さらに、何千年も前に何千ものパーティション(毎分?時間?)を作成する必要があります。私はそれらをその場で作成する方法を理解できませんでした
TimestampMinute
の値がDATEPART(MINUTE, GETUTCDATE())
であるINSERT
というテーブルに計算カラムを追加して、パーティションを作成してみました。まだ遅すぎる。
このMicrosoftの記事 に従って、メモリ最適化テーブルにしてみました。多分私はそれをする方法を理解していませんが、MOTはどういうわけか挿入を遅くしました。
ストアドプロシージャの実行プランを確認したところ、(私が思うに)最も集中的な操作は
_SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
ON smd.SignalId = svs.SignalId
_
私にはこれは意味がありません:ストアドプロシージャに壁時計のログを追加しました。
時間ログに関しては、上記の特定のステートメントは100kレコードで約300msで実行されます。
ステートメント
_INSERT INTO SignalValues SELECT * FROM #sValues
_
100kレコードで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です。
ハードウェア構成:
この質問の最初のパスにハードウェアを含めるのを忘れました。私の悪い。
私はこれらのステートメントでこれを前置きします:Iknow私はハードウェア構成のためにいくつかのパフォーマンスを失っています。私は何度も試しましたが、予算、Cレベル、惑星の配置などにより、残念ながら、より良い設定を行うために私ができることは何もありません。サーバーは仮想マシン上で実行されており、メモリがないため、メモリを増やすこともできません。
これが私のシステム情報です:
ストレージはVMサーバーへのiSCSIインターフェースを介してNASボックスに接続されます(これによりパフォーマンスが低下します。)NAS =ボックスには、RAID 10構成の4台のドライブがあります。これらは、4GB WD WD4000FYYZ回転ディスクドライブで、6GB /秒のSATAインターフェイスを備えています。サーバーには1つのデータストアのみが構成されているため、tempdbと私のデータベースは同じデータストアにあります。
最大DOPはゼロです。これを定数値に変更する必要がありますか、それともSQL Serverに処理させますか?私はRCSIについて読みました:RCSIからの唯一の利点は行の更新にあると想定するのは正しいですか?これらの特定のレコードの更新はありません。それらはINSERT
edおよびSELECT
edになります。 RCSIはまだ私に利益をもたらしますか?
Tempdbは8MBです。以下のjyaoからの回答に基づいて、tempdbを完全に回避するために#sValuesを通常のテーブルに変更しました。パフォーマンスはほぼ同じでした。 tempdbのサイズと成長を増やしてみますが、#sValuesのサイズは常に同じサイズになるので、あまり期待できません。
以下に添付した実行計画を採用しました。この実行プランは、ステージングテーブルの1回の反復です-100kレコード。クエリの実行は約2秒とかなり高速でしたが、これにはSignalValues
テーブルのインデックスがなく、SignalValues
テーブル(INSERT
のターゲット)にレコードがないことに注意してください。
ピーク時に、平均レコード数は1日あたり30億から40億(20時間の運用)の大通りのどこかにあると計算しました。
スクリーンショットから、合計8 GBのメモリのみRAMおよびSQL Serverに6 GBが割り当てられています。これは、達成しようとしていることに対して低すぎる方法です。
メモリをより高い値(256GB)にアップグレードして、VM CPUも同様に増やすことをお勧めします。
この時点で、ワークロードのためにハードウェアに投資する必要があります。
データロードパフォーマンスガイド も参照してください。データを効率的にロードするためのスマートな方法について説明しています。
Tempdbは8MBです。
編集に基づいて..賢明なtempdbが必要です。できれば、TF 1117および1118対応のインスタンス全体で同じサイズの複数のtempdbデータファイルを使用してください。
プロの健康診断を受けて、そこから始めることをお勧めします。
強くお勧めします
サーバーのスペックを引き上げます。
専門家*にデータベースサーバーインスタンスのヘルスチェックを依頼し、推奨事項に従ってください。
一度およびb。完了したら、クエリのチューニングや、待機統計、クエリプランなどの他の最適化に没頭します。
注:私は プロのsqlサーバーエキスパートhackhands.com の多元的な会社ですが、私に助けを求めるように勧めるわけではありません。私は単にあなたの編集だけに基づいて専門家の助けを借りることを提案しています。
HTH。
ビッグデータに関するこのような問題に対する一般的なアドバイスは、壁に直面しても何も機能しない場合です。
たまご1個5分くらいで炊きます。十分な電気と水があれば、10個の卵が同時に調理されます。
または、言い換えると:
まず、ハードウェアを見てください。次に、プロセスロジックを分離し(データの再構築)、それを並行して行います。
テーブル数およびテーブルサイズごとにカスタムの垂直パーティションを動的かつ自動で作成することは十分に可能です。 Quarter_1_2017、Quarter_2_2017、Quarter_3_2017、Quarter_4_2017、Quarter_1_2018 ...があり、レコードの場所とパーティションの数がわからない場合、すべてのカスタムパーティションに対して同じクエリを同じ時間に実行し、個別のセッションとアセンブリを実行します私のロジックのために前方に処理される結果。