web-dev-qa-db-ja.com

インメモリテーブルのパフォーマンスがディスクベースのテーブルよりも悪い

SQL Server 2014に次のようなテーブルがあります。

CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)

(id1、id2)はPKです。基本的に、id1は、pkがid2である結果のセット(id2、col1、col2)をグループ化するための識別子です。

インメモリテーブルを使用して、ボトルネックとなっている既存のディスクベースのテーブルを削除しようとしています。

  • テーブル内のデータが書き込まれる->読み取られる->一度削除される。
  • 各id1値には、数千(数十/数十万)のid2があります。
  • データはテーブルに非常に短時間保存されます。 20秒。

このテーブルで実行されるクエリは次のとおりです。

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

-- DELETE:
DELETE FROM MyTable WHERE id1 = @value

これが、テーブルに使用した現在の定義です。

CREATE TABLE dbo.SearchItems
(
  [id1] [bigint] NOT NULL,
  [id2] [bigint] NOT NULL,
  [col1] [int] NOT NULL default(0),
  [col2] [int] NOT NULL default(0)

  CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
  INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)

残念ながら、この定義により、ディスクベースのテーブルでの以前の状況と比較してパフォーマンスが低下します。大きさの順序は多かれ少なかれ10%高くなります(場合によっては100%に達するため、2倍の時間になります)。

何よりも、マイクロソフトがアドバタイズしたロックフリーアーキテクチャを考えると、同時実行性の高いシナリオで非常に有利になると期待していました。代わりに、最悪のパフォーマンスは、テーブルで複数のクエリを実行している複数の同時ユーザーがいる場合に発生します。

質問:

  • 設定する正しいBUCKET_COUNTはいくつですか?
  • どの種類のインデックスを使用すればよいですか?
  • なぜディスクベースのテーブルよりもパフォーマンスが悪いのですか?

sys.dm_db_xtp_hash_index_statsのクエリは次を返します:

total_bucket_count = 131072 
 empty_bucket_count = 0 
 avg_chain_len = 873 
 max_chain_length = 1009

sys.dm_db_xtp_hash_index_statsからの出力が次のようになるようにバケット数を変更しました:

total_bucket_count = 134217728 
 empty_bucket_count = 131664087 
 avg_chain_len = 1 
 max_chain_length = 3

それでも、結果は悪くはないとしても、ほとんど同じです。

10

情報が不足しているため、この投稿は完全な答えにはなりませんが、適切な方向にあなたをポイントするか、後でコミュニティと共有できる洞察を得ることができるはずです。

残念ながら、この定義により、ディスクベースのテーブルでの以前の状況と比較してパフォーマンスが低下します。大きさの順序は多かれ少なかれ10%高くなります(場合によっては100%に達するため、倍の時間になります)。

何よりも、マイクロソフトがアドバタイズしたロックフリーアーキテクチャを考えると、同時実行性の高いシナリオで非常に有利になると期待していました。代わりに、最悪のパフォーマンスは、テーブルで複数のクエリを実行している複数の同時ユーザーがいる場合です。

それは間違いなくそうであるべきではないので、これは厄介です。特定のワークロードはメモリ内テーブル用ではなく(SQL 2014)、一部のワークロードはそれに適しています。ほとんどの状況では、適切なインデックスを移行して選択するだけで、パフォーマンスが最小限に抑えられます。

もともと私はこれに関するあなたの質問について非常に狭く考えていました:

質問:

  • 設定する正しいBUCKET_COUNTはいくつですか?
  • どの種類のインデックスを使用すればよいですか?
  • なぜディスクベースのテーブルよりもパフォーマンスが悪いのですか?

最初は、メモリ内の実際のテーブルとインデックスが最適でないことに問題があると思っていました。メモリ最適化されたハッシュインデックス定義にはいくつかの問題がありますが、実際の問題は使用されたクエリにあると考えています。

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

この挿入は、メモリ内のテーブルのみを含む場合、非常に高速である必要があります。ただし、これにはディスクベースのテーブルも含まれ、それに関連するすべてのロックとブロックの影響を受けます。したがって、ここでのリアルタイムの無駄はディスクベースのテーブルにあります。

データをメモリに読み込んだ後、ディスクベースのテーブルから100,000行の挿入に対して簡単なテストを行ったところ、応答時間は1秒未満でした。ただし、ほとんどのデータは、20秒未満の非常に短い時間しか保持されません。これは、実際にキャッシュに住んでいる時間を与えません。さらに、AnotherTableの実際の大きさがわからないため、値がディスクから読み取られているかどうかがわかりません。私たちはこれらの答えをあなたに頼らなければなりません。

Selectクエリの場合:

SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

繰り返しますが、我々は相互運用とディスクベースのテーブルのパフォーマンスに翻弄されています。さらに、ソートはHASHインデックスでは安価ではないため、非クラスター化インデックスを使用する必要があります。これは 索引ガイド で呼び出されていますコメントでリンクしました。

実際の調査に基づく事実を示すために、実際のサイズや統計がわからなかったため、SearchItemsをメモリテーブルに1000万行、AnotherTableを100,000行ロードしました。次に、上記の選択クエリを使用して実行しました。さらに、wait_completedで拡張イベントセッションを作成し、それをリングバッファーに入れました。それは各実行の後に掃除されました。私も走ったDBCC DROPCLEANBUFFERSデータのallがメモリに常駐していない可能性がある環境をシミュレートします。

それらを真空で見ると、結果は目を見張るものはありませんでした。これをテストしているラップトップはより高いグレードのSSDを使用しているので、VMを使用しているため、ディスクベースのパフォーマンスを意図的に下げました。

結果は、メモリ内のテーブルのみでクエリを5回実行した後、待機情報なしで発生しました(結合は削除され、サブクエリは削除されません)。これは予想通りです。

ただし、元のクエリを使用すると、待機がありました。この場合、データがディスクから読み取られているので意味のあるのはPAGEIOLATCH_SHでした。私はこのシステムのonlyユーザーであり、結合されたテーブルに対する挿入、更新、削除のための大規模なテスト環境を作成するのに時間を費やしなかったので、ロックは予期していませんでしたまたはブロックして有効にします。

この場合も、時間のかなりの部分がディスクベースのテーブルに費やされました。

最後に削除クエリ。 ID1のみに基づいて行を検索することは、hasインデックスでは非常に効率的ではありません。等価述語がハッシュインデックスに適していることは事実ですが、データが入るバケットはハッシュされた列全体に基づいています。したがって、id1 = 1、id2 = 2、id1 = 1、id2 = 3のid1、id2は、ハッシュが(1,2)と(1,3)にまたがるため、異なるバケットにハッシュされます。ハッシュインデックスが同じように構造化されていないため、これは単純なBツリー範囲スキャンではありません。次に、これがこの操作のidealインデックスではないことを期待しますが、経験したように桁違いに長くなることは期待していません。これについてwait_infoを見てみたいと思います。

何よりも、マイクロソフトがアドバタイズしたロックフリーアーキテクチャを考えると、同時実行性の高いシナリオで非常に有利になると期待していました。代わりに、最悪のパフォーマンスは、テーブルで複数のクエリを実行している複数の同時ユーザーがいる場合に発生します。

論理的な一貫性のためにロックが使用されることは事実ですが、操作は依然としてアトミックである必要があります。これは、特別なCPUベースの比較演算子を介して行われます(これが、インメモリが特定の[過去4年間に作成されたほとんどすべてのCPUであるにもかかわらず]プロセッサでのみ機能する理由です)。したがって、すべてを無料で入手できるわけではありません。これらの操作を完了するにはまだ時間がかかります。

もう1つの重要な点は、ほとんどすべてのクエリで使用されるインターフェイスがT-SQL(ネイティブにコンパイルされたSPROCではなく)であり、すべてが少なくとも1つのディスクベースのテーブルにアクセスするということです。結局、ディスクベースのテーブルのパフォーマンスにまだ制約があるため、結局、パフォーマンスは向上していません。

フォローアップ:

  1. Wait_completedの拡張イベントセッションを作成し、既知のSPIDを指定します。クエリを実行して出力を提供するか、内部で使用します。

  2. #1の出力の更新をお知らせください。

  3. ハッシュインデックスのバケット数を決定するためのマジックナンバーはありません。基本的に、バケットが完全にいっぱいにならず、行チェーンが3または4未満に留まる限り、パフォーマンスは許容範囲に留まるはずです。これは、「ログファイルを何に設定すればよいですか」と尋ねるようなものです。 -プロセスごと、データベースごと、使用タイプごとに異なります。

7
Sean Gallardy