私はアプリケーションを設計していますが、その 1 つの側面は、大量のデータを SQL データベースに受け取ることができることです。次のような bigint ID を持つ単一のテーブルとしてデータベース構造を設計しました。
CREATE TABLE MainTable
(
_id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
field1, field2, ...
)
私が持っている質問とは関係がないので、どのようにクエリを実行するつもりなのかは省略します。
SqlBulkCopy を使用してこのテーブルにデータを挿入するプロトタイプを作成しました。研究室ではうまく機能しているように見えました。毎秒約 3K レコードの速度で数千万のレコードを挿入できました (完全なレコード自体はかなり大きく、約 4K です)。このテーブルの唯一のインデックスは自動インクリメント bigint であるため、大量の行がプッシュされた後でも速度が低下することはありませんでした。
ラボの SQL サーバーが比較的脆弱な構成の仮想マシン (4Gb RAM、他の VM ディスク sybsystem と共有) であることを考慮すると、物理マシンのスループットが大幅に向上することを期待していましたが、そうはなりませんでした。パフォーマンスの向上はごくわずかでした。物理マシンでの挿入が 25% 高速になる可能性があります。3 ドライブの RAID0 を構成した後でも、1 つのドライブよりも 3 倍高速でした (ベンチマーク ソフトウェアで測定) が、改善は見られませんでした。基本的に、より高速なドライブ サブシステム、専用の物理 CPU、および 2 倍の RAM は、パフォーマンスの向上にはほとんどつながりませんでした。
次に、Azure で最大のインスタンス (8 コア、16Gb) を使用してテストを繰り返しましたが、同じ結果が得られました。そのため、コアを追加しても挿入速度は変わりませんでした。
現時点では、次のソフトウェア パラメーターをいじってみましたが、パフォーマンスが大幅に向上することはありませんでした。
- SqlBulkInsert.BatchSize パラメータの変更
- 複数のスレッドから同時に挿入し、スレッド数を調整する
- SqlBulkInsert でテーブル ロック オプションを使用する
- 共有メモリ ドライバを使用してローカル プロセスから挿入することにより、ネットワーク レイテンシを排除する
私はパフォーマンスを少なくとも 2 ~ 3 倍向上させようとしています。当初の考えでは、より多くのハードウェアを投入するとうまくいくというものでしたが、今のところそうではありません。
だから、誰かが私を推薦できますか:
- ここで、どのリソースがボトルネックであると疑われる可能性がありますか? 確認方法は?
- 単一の SQL サーバー システムがあることを考慮して、確実にスケーラブルな一括挿入の改善を試みる方法はありますか?
更新load app は問題ではないと確信しています。別のスレッドの一時キューにレコードを作成するため、挿入があると次のようになります (簡略化):
===>start logging time
int batchCount = (queue.Count - 1) / targetBatchSize + 1;
Enumerable.Range(0, batchCount).AsParallel().
WithDegreeOfParallelism(MAX_DEGREE_OF_PARALLELISM).ForAll(i =>
{
var batch = queue.Skip(i * targetBatchSize).Take(targetBatchSize);
var data = MYRECORDTYPE.MakeDataTable(batch);
var bcp = GetBulkCopy();
bcp.WriteToServer(data);
});
====> end loging time
タイミングがログに記録され、キューを作成する部分が重要なチャンクを取得することはありません
UPDATE2そのサイクルの各操作にかかる時間を収集することを実装しました。レイアウトは次のとおりです。
queue.Skip().Take()
- 無視できるMakeDataTable(batch)
- 10%GetBulkCopy()
- 無視できるWriteToServer(data)
- 90%
UPDATE3 SQL の標準バージョン用に設計しているため、エンタープライズ バージョンでのみ利用可能なパーティション分割に依存できません。しかし、私はパーティション分割スキームの変種を試しました:
- 16 個のファイル グループ (G0 から G15) を作成し、
- 挿入専用の 16 個のテーブル (T0 から T15) を作成し、それぞれが個別のグループにバインドされました。テーブルにはインデックスがまったくなく、クラスター化された int ID さえありません。
- データを挿入するスレッドは、それぞれ 16 個のテーブルすべてを循環します。これにより、各一括挿入操作が独自のテーブルを使用することがほぼ保証されます
これにより、一括挿入で最大 20% の改善が得られました。CPU コア、LAN インターフェイス、ドライブ I/O は最大化されておらず、最大容量の約 25% で使用されていました。
UPDATE4今は最高の状態になっていると思います。次の手法を使用して、挿入を適切な速度でプッシュすることができました。
- 各一括挿入は独自のテーブルに入り、結果はメインのテーブルにマージされます
- テーブルは一括挿入ごとに新しく再作成され、テーブル ロックが使用されます
- DataTable の代わりにhereのIDataReader 実装を使用しました。
- 複数のクライアントからの一括挿入
- 各クライアントは、個々のギガビット VLAN を使用して SQL にアクセスしています
- メイン テーブルにアクセスするサイド プロセスは NOLOCK オプションを使用します
- sys.dm_os_wait_stats と sys.dm_os_latch_stats を調べて競合を排除しました
この時点で、回答された質問のクレジットを誰に与えるかを決めるのに苦労しています。「回答」が得られなかった皆さん、申し訳ありません。本当に苦渋の決断でした。皆さんに感謝します。
UPDATE5 : 次の項目は、いくつかの最適化を使用できます。
- DataTable の代わりにhereのIDataReader 実装を使用しました。
CPUコア数が多いマシンでプログラムを実行しない限り、リファクタリングが必要になる可能性があります。リフレクションを使用して get/set メソッドを生成しているため、CPU に大きな負荷がかかります。パフォーマンスが重要な場合は、リフレクションを使用する代わりに IDataReader を手動でコーディングしてコンパイルすると、パフォーマンスが大幅に向上します。