ラッチに関する一連の記事を続けて、今回はAPPEND_ONLY_STORAGE_INSERT_POINTラッチについて説明し、いずれかの形式のスナップショットアイソレーションが使用されている大量の更新ワークロードの主要なボトルネックになる可能性があることを示します。
このシリーズの前にシリーズの最初の投稿を読むことを強くお勧めします。そうすれば、ラッチに関する一般的な背景知識をすべて身に付けることができます。
APPEND_ONLY_STORAGE_INSERT_POINTラッチとは何ですか?
このラッチを説明するために、スナップショットアイソレーションがどのように機能するかについて少し説明する必要があります。
2つの形式のバージョン管理のいずれかを有効にすると、SQLServerはバージョン管理と呼ばれるメカニズムを使用します。 変更前のバージョンを保持する バージョンストアのレコードの tempdbで。これは次のように行われます:
- レコードは変更されようとしているものとして識別されます。
- 現在のレコードがバージョンストアにコピーされます。
- レコードが変更されました。
- レコードにまだ14バイトのバージョン管理タグがない場合 、レコードの最後に1つ追加されます。タグには、タイムスタンプ(リアルタイムではない)と、バージョンストア内のレコードの前のバージョンへのポインターが含まれています。
- レコードにすでにバージョン管理タグが含まれている場合は、新しいタイムスタンプとバージョンストアポインタで更新されます。
インスタンス全体のバージョン管理タイムスタンプは、いずれかの形式のスナップショットアイソレーションが有効になっているデータベースで、新しいステートメントまたはバッチが開始されるか、レコードの新しいバージョンが作成されるたびに増分されます。このタイムスタンプは、クエリが正しいバージョンのレコードを処理することを確認するために使用されます。
たとえば、コミットされたスナップショットの読み取りが有効になっているデータベースを想像してください。これにより、各ステートメントは、ステートメントが開始された時点のレコードを参照することが保証されます。バージョン管理のタイムスタンプはステートメントの開始時に設定されるため、タイムスタンプが高いレコードはすべて「間違った」バージョンであるため、ステートメントのタイムスタンプより前のタイムスタンプを持つ「正しい」バージョンをから取得する必要があります。バージョンストア。このプロセスの仕組みは、この投稿の目的には関係ありません。
では、バージョンはどのようにバージョンストアに物理的に保存されますか?行外の列を含む変更前のレコード全体がバージョンストアにコピーされ、8,000バイトのチャンクに分割されます。これは、必要に応じて2ページにまたがることができます(たとえば、1ページの終わりに2,000バイト、次の始まり)。この専用ストレージは、追加専用割り当てユニットで構成されています。 バージョンストアの操作にのみ使用されます。新しいデータは、最後に入力されたバージョンの終了直後にのみ追加できるため、これは呼ばれます。新しいアロケーションユニットが頻繁に作成されます。これにより、不要なアロケーションユニットを簡単に削除できるため、定期的なバージョンストアのクリーンアップを非常に効率的に行うことができます。繰り返しになりますが、その仕組みはこの投稿の範囲を超えています。
次に、ラッチの定義について説明します。変更前のレコードをバージョンストアにコピーする必要があるスレッドは、挿入ポイントが現在の追加専用アロケーションユニットのどこにあるかを知る必要があります。この情報は、APPEND_ONLY_STORAGE_INSERT_POINTラッチによって保護されています。
ラッチはどのようにしてボトルネックになりますか?
問題は次のとおりです。APPEND_ONLY_STORAGE_INSERT_POINTラッチを取得できる許容可能なモードは、EXモード(排他的)のみです。また、シリーズの紹介記事を読むとわかるように、一度に1つのスレッドだけがEXモードでラッチを保持できます。
このすべての情報をまとめる:1つ以上のデータベースでスナップショットアイソレーションが有効になっていて、それらのデータベースへの更新の同時ワークロードが十分にある場合、さまざまな接続によって多くのバージョンが生成され、このラッチは少しボトルネックがあり、バージョン管理が関係する場所で更新ワークロードが増加するとボトルネックサイズが増加します。
ボトルネックを示す
ボトルネックを自分で簡単に再現できます。私は次のようにしました:
- cXXXという名前の整数列の束を含むテーブルを作成しました。ここで、XXXは数値であり、DocIDという名前のintID列にクラスター化インデックスがあります。
- すべての列にランダムな値を含む100,000レコードを挿入しました
- 1〜10,000の範囲のランダムなDocIDを選択し、ランダムな列名を選択し、列の値を1つインクリメントする無限ループのスクリプトを作成しました(したがって、バージョンを作成します)
- 9つの同一のスクリプトを作成しましたが、それぞれが異なる10,000値のクラスターキー範囲から選択しています
- DELAYED_DURABILITYをFORCEDに設定して、WRITELOGの待機を減らします(確かに、これを行うことはめったにありませんが、デモの目的でボトルネックを悪化させるのに役立ちます)
次に、10個のスクリプトすべてを同時に実行し、アクセス方法:インデックス検索/秒カウンターを測定して、発生した更新の数を追跡しました。各スクリプトには1つのバッチ(無限ループ)しかないため、データベース:バッチリクエスト/秒を使用できませんでした。また、内部トランザクションと各更新をラップするトランザクションをカウントする可能性があるため、トランザクション/秒を使用したくありませんでした。
スナップショットアイソレーションが有効になっていない場合、SQL Server2019を実行しているWindows10ラップトップでは、10回の接続で1秒あたり約80,000回の更新を取得していました。次に、データベースの設定READ_COMMMITED_SNAPSHOTをONにしてテストを再実行すると、ワークロードのスループットが1秒あたり約60,000更新に低下しました(スループットが25%低下)。待機統計を見ると、すべての待機の85%がLATCH_EXであり、ラッチ統計を見ると、100%がAPPEND_ONLY_STORAGE_INSERT_POINTでした。
最悪のボトルネックを示すシナリオを設定したことを覚えておいてください。ワークロードが混在する実際の環境では、スナップショットアイソレーションを使用した場合のスループット低下について一般的に受け入れられているガイダンスは10〜15%です。
概要
このボトルネックの影響を受ける可能性のあるもう1つの潜在的な領域は、可用性グループで読み取り可能なセカンダリです。データベースレプリカが読み取り可能に設定されている場合、それに対するすべてのクエリは自動的にスナップショットアイソレーションを使用し、プライマリからのログレコードのすべての再生でバージョンが生成されます。プライマリからの更新ワークロードが十分に高く、多くのデータベースが読み取り可能に設定されており、並列REDOが可用性グループのセカンダリの標準である場合、APPEND_ONLY_STORAGE_INSERT_POINTラッチは、可用性グループの読み取り可能なセカンダリでもボトルネックになる可能性があります。セカンダリはプライマリに遅れをとっています。私はこれをテストしていませんが、これは上記で説明したメカニズムとまったく同じであるため、可能性が高いようです。その場合、トレースフラグ3459を使用して並列REDOを無効にすることは可能ですが、これにより、セカンダリの全体的なスループットが低下する可能性があります。
残念ながら、可用性グループのシナリオを脇に置いて、スナップショットアイソレーションを使用しないことが、このボトルネックを完全に回避する唯一の方法です。これは、ワークロードがスナップショットアイソレーションによって提供されるセマンティクスに依存している場合、またはブロッキングの問題を軽減するために必要な場合、実行可能なオプションではありません。 (スナップショットアイソレーションとは、読み取りクエリが変更クエリをブロックする共有ロックを取得しないことを意味します)。
編集:以下のコメントから、SQL Server 2019でADRを使用してラッチのボトルネックを*取り除く*ことができますが、ADRのオーバーヘッドのためにパフォーマンスが大幅に低下します。更新のワークロードが高いためにラッチがボトルネックになるシナリオは、ADRの有効なユースケースではありません。