はい、私の古いラップトップは、実稼働サーバーよりも数倍強力です。

これらは私が開発者から聞いた主張です。 最も興味深いのは、これが真実であることが判明し、長期にわたる調査が行われたことです。 VMware 上で実行される SQL サーバーについて説明します。

はい、私の古いラップトップは、実稼働サーバーよりも数倍強力です。

実際、実稼働サーバーをラップトップの背後に配置するのは絶望的に簡単です。 次のコードを実行します (tempdb 上や遅延持続性が有効になっているデータベース上ではありません)。

set nocount on
create table _t (v varchar(100))
declare @n int=300000
while @n>0 begin 
  insert into _t select 'What a slowpoke!'
  delete from _t
  set @n=@n-1
  end
GO
drop table _t

私のデスクトップでは 5 秒かかりますが、運用サーバーでは 28 秒かかります。 SQL はトランザクション ログへの書き込みが物理的に終了するまで待つ必要があり、ここでは非常に短いトランザクションを実行しているためです。 大まかに言えば、私たちは大型の強力なトラックを市内の交通に運転し、スクーターに乗ったピザの配達員がそれを追い越していく様子を観察しました。ここではスループットは重要ではなく、レイテンシだけが重要です。 また、価格にゼロがいくつあっても、遅延の点でローカル SSD を上回るネットワーク ストレージは XNUMX つもありません。

(コメントの中で、私が嘘をついていたことが判明しました。両方の場所で耐久性を遅らせていました。耐久性を遅らせないと、次のようになります。
デスクトップ - 39 秒、15 tr/秒、0.065 ミリ秒 /io ラウンドトリップ
PROD - 360 秒、1600 tr/秒、0.6 ミリ秒
速すぎることに気づくべきだった)

ただし、この場合は、リーマン ゼータ関数の自明な零点を自明な例で扱っています。 開発者が私に持ってきた例では、それは異なりました。 私は彼らが正しいと確信し、ビジネス ロジックに関連するすべての詳細を例から削除し始めました。 ある時点で、私は彼らのコードを完全に捨てて、自分のコードを書くことができることに気付きました。これは同じ問題を示していますが、実稼働環境では 3 ~ 4 倍遅く実行されます。

create function dbo.isPrime (@n bigint)
returns int
as
  begin
  if @n = 1 return 0
  if @n = 2 return 1
  if @n = 3 return 1
  if @n % 2 = 0 return 0
  declare @sq int
  set @sq = sqrt(@n)+1 -- check odds up to sqrt
  declare @dv int = 1
  while @dv < @sq 
    begin
	set @dv=@dv+2
	if @n % @dv = 0 return 0
	end
  return 1
  end
GO
declare @dt datetime set @dt=getdate()
select dbo.isPrime(1000000000000037)
select datediff(ms,@dt,getdate()) as ms
GO

すべて問題なければ、数値の単純さのチェックには 6 ~ 7 ~ 8 秒かかります。 これは多くのサーバーで発生しました。 ただし、チェックに 25 ~ 40 秒かかる場合もありました。 興味深いことに、実行にたとえば 14 秒かかるサーバーはありませんでした。コードは非常に速く動作するか、または非常に遅く動作するかのどちらかでした。つまり、問題は、たとえば、白黒はっきりしていました。

私が何をしてしまったのか? VMware メトリクスについて学びました。 そこではすべて問題ありませんでした。リソースは十分にあり、準備完了時間 = 0 で、テスト中はすべてが十分でした。テスト中は、高速サーバーと低速サーバーの両方で CPU = 100 (XNUMX つの vCPU 上) でした。 Pi の数を計算するテストを受けました。テストでは、どのサーバーでも同じ結果が示されました。 黒魔術の匂いがどんどん強くなっていった。

DEV ファームから出た後、私はサーバーをいじり始めました。 ホストからホストへの vMotion はサーバーを「修復」できるが、「高速」サーバーを「低速」サーバーに変えることもできることが判明しました。 これだけのようです - 一部のホストに問題があります...しかし...いいえ。 一部の仮想マシンはホスト A では速度が低下しましたが、ホスト B では高速に動作しました。そして、他の仮想マシンは逆に、A では高速に動作し、B では低速になりました。 「速い」車と「遅い」車の両方がホスト上で頻繁に回転していました。

その瞬間から、空気中に独特の硫黄の匂いが漂ってきました。 結局のところ、この問題はどの仮想マシン (Windows パッチなど) に起因するものではなく、結局のところ、vMotion を使用すると「高速」の問題に変わりました。 しかし、この問題の原因がホストにあるとは考えられません。結局のところ、ホストには「高速」マシンと「低速」マシンの両方が存在する可能性があります。 また、これは負荷とは関係ありませんでした。ホスト上で「遅い」マシンを取得することができましたが、それ以外には何もありませんでした。

絶望的な気持ちから、私は Sysinternals の Process Explorer を起動して SQL スタックを調べました。 遅いマシンでは、次の行がすぐに目に留まりました。

ntoskrnl.exe!KeSynchronizeExecution+0x5bf6
ntoskrnl.exe!KeWaitForMultipleObjects+0x109d
ntoskrnl.exe!KeWaitForMultipleObjects+0xb3f
ntoskrnl.exe!KeWaitForSingleObject+0x377
ntoskrnl.exe!KeQuerySystemTimePrecise+0x881 < - !!!
ntoskrnl.exe!ObDereferenceObjectDeferDelete+0x28a
ntoskrnl.exe!KeSynchronizeExecution+0x2de2
sqllang.dll!CDiagThreadSafe::PxlvlReplace+0x1a20
…スキップしました
sqldk.dll!SystemThread::MakeMiniSOSThread+0xa54
KERNEL32.DLL!BaseThreadInitThunk+0x14
ntdll.dllの!RtlUserThreadStart + 0x21

それはすでに何かでした。 プログラムは次のように書かれていました。

    class Program
    {
        [DllImport("kernel32.dll")]
        static extern void GetSystemTimePreciseAsFileTime(out FILE_TIME lpSystemTimeAsFileTime);

        [StructLayout(LayoutKind.Sequential)]
        struct FILE_TIME
        {
            public int ftTimeLow;
            public int ftTimeHigh;
        }

        static void Main(string[] args)
        {
            for (int i = 0; i < 16; i++)
            {
                int counter = 0;

                var stopwatch = Stopwatch.StartNew();

                while (stopwatch.ElapsedMilliseconds < 1000)
                {
                    GetSystemTimePreciseAsFileTime(out var fileTime);
                    counter++;
                }

                if (i > 0)
                {
                    Console.WriteLine("{0}", counter);
                }
            }
        }
    }

このプログラムでは、さらに顕著な速度の低下が見られました。「高速」マシンでは 16 秒あたり 18 ~ 700 万サイクルであるのに対し、遅いマシンでは 10 万、さらには 20 万サイクルです。 つまり、XNUMX〜XNUMX倍の差があります(!!!)。 これはすでに小さな勝利でした。いずれにせよ、Microsoft と VMware のサポートの間で行き詰まり、両社が相互に方向転換する恐れはありませんでした。

その後、進歩は止まりました - 休暇、重要なこと、ウイルス性のヒステリー、そして仕事量の急激な増加。 私はこの魔法の問題についてよく同僚に話していましたが、必ずしも信じてもらえなかったようです。VMware がコードの速度を 10 ~ 20 倍遅くしたという発言はあまりにもひどいものでした。

何が速度を遅らせているのかを自分で調べてみました。 時々、解決策を見つけたように思えました。ホットプラグのオンとオフを切り替えたり、メモリの量やプロセッサの数を変更したりすると、マシンが「高速」になることがよくありました。 しかし、永遠ではありません。 しかし、真実であることが判明したのは、外に出てハンドルをノックするだけで十分だということです。つまり、変化するには 任意の 仮想マシンのパラメータ

最後に、アメリカ人の同僚が突然根本原因を発見しました。

はい、私の古いラップトップは、実稼働サーバーよりも数倍強力です。

ホストによって頻度が異なります。

  • 原則として、これは怖いことではありません。 ただし、「ネイティブ」ホストから「異なる」周波数のホストに移動する場合、VMware は GetTimePrecise の結果を調整する必要があります。
  • SQL サーバーのように、XNUMX 秒間に何百万回も正確な時刻を要求するアプリケーションがない限り、これは問題にはなりません。
  • ただし、SQL サーバーが常にこれを実行するとは限らないため、これも怖いことではありません (「結論」を参照)。

しかし、この熊手は痛い場合があります。 そして、はい、ハンドルをノックすることによって (VM 設定で何かを変更することによって)、VMware に構成を強制的に「再計算」させ、現在のホストの周波数がマシンの「ネイティブ」周波数になりました。

ソリューション

www.vmware.com/files/pdf/techpaper/Timekeeper-In-VirtualMachines.pdf

TSC の仮想化を無効にすると、仮想マシン内から TSC を読み取ると物理マシンの TSC 値が返され、仮想マシン内から TSC を書き込んでも効果はありません。 仮想マシンを別のホストに移行したり、サスペンド状態から再開したり、スナップショットに戻すと、TSC が不連続にジャンプします。 TSC 仮想化が無効になっていると、一部のゲスト オペレーティング システムが起動に失敗したり、その他の時間管理の問題が発生したりします。 過去には、TSC を頻繁に読み取るアプリケーションのパフォーマンスを向上させるために、この機能が推奨されることがありました。ですが、現在の製品では仮想 TSC のパフォーマンスが大幅に向上しています。 この機能は、仮想マシンで正確なリアルタイム ソースを必要とする測定を実行する場合にも使用することが推奨されています。

つまり、パラメータを追加する必要があります

モニター_コントロール.virtual_rdtsc = FALSE

まとめ

おそらく、SQL はなぜ頻繁に GetTimePrecise を呼び出すのでしょうか?という疑問があるでしょう。

SQL サーバーのソースはありませんが、ロジックは次のように述べています。 SQL はほぼ協調的な同時実行性を備えたオペレーティング システムであり、各スレッドは時々「道を譲る」必要があります。 それを行うのに最適な場所はどこですか? 当然の期待がある場合 - ロックまたは IO。 わかりましたが、計算サイクルを回転させている場合はどうなるでしょうか? 次に、明白でほぼ唯一の場所は、次の演算子の実行後のインタプリタ (これは完全なインタプリタではありません) 内です。

原則として、SQL サーバーは純粋なコンピューティングには使用されませんが、これは問題ではありません。 しかし、あらゆる種類の一時テーブル (すぐにキャッシュされる) を使用したサイクルにより、コードは非常に高速に実行される一連のステートメントに変わります。

ちなみに、関数を NATIVELY COMPILED でラップすると、時間の要求がなくなり、速度が 10 倍に向上しますが、協調的なマルチタスクの場合はどうでしょうか。 ただし、ネイティブ コンパイルされたコードの場合は、SQL で PREEMPTIVE MULTITASKING を実行する必要がありました。

出所: habr.com

コメントを追加します