これらはまさに私たちの開発者から聞いた苦情です。最も興味深いのは、これが真実であることが判明し、長い調査が行われたことです。 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 に勝るネットワーク ストレージはありません。
(コメントで私が嘘をついていたことが判明しました。両方の場所で耐久性を遅延させていました。耐久性を遅延させないと、次のようになります。
デスクトップ - 39 秒、15K tr/秒、0.065ms /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、すべてが十分でした。高速サーバーと低速サーバーの両方でテスト中、100 つの vCPU で CPU = XNUMX でした。円周率を計算するテストを実行しましたが、どのサーバーでも同じ結果が出ました。黒魔術の匂いが強くなっていった。
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 サーバーのように、正確な時間を毎秒何百万回も要求するアプリケーションがない限り、これは通常、大きな問題にはなりません。
- しかし、SQL Server が常にこれを実行するわけではないので、これは恐ろしいことではありません (結論を参照)。
しかし、このレーキが大きな打撃を与える場合もあります。そして、はい、ホイールをタップして(VM 設定で何かを変更して)、VMware に構成を「再計算」させ、現在のホストの周波数がマシンの「ネイティブ」周波数になりました。
ソリューション
TSC の仮想化を無効にすると、仮想マシン内から TSC を読み取ると物理マシンの TSC 値が返され、仮想マシン内から TSC を書き込むと効果はありません。仮想マシンを別のホストに移行したり、一時停止状態から再開したり、スナップショットに戻したりすると、TSC が不連続にジャンプします。 TSC 仮想化が無効になっていると、一部のゲスト オペレーティング システムが起動に失敗したり、その他のタイムキーピングの問題が発生したりします。 過去には、この機能はTSCを頻繁に読み取るアプリケーションのパフォーマンスを向上させるために推奨されることもありました。しかし、現在の製品では仮想 TSC のパフォーマンスが大幅に向上しています。この機能は、仮想マシンで正確なリアルタイム ソースを必要とする測定を実行する場合にも使用することが推奨されています。
つまり、パラメータを追加する必要がある
monitor_control.virtual_rdtsc = 偽
まとめ
おそらく、SQL が GetTimePrecise をなぜ頻繁に呼び出す必要があるのかという疑問があるでしょう。
SQL サーバーのソース コードはありませんが、論理的にはこうなります。 SQL は、協調的な同時実行性を備えたオペレーティング システムに似ており、各スレッドは時々「譲歩」する必要があります。これを行うのに最適な場所はどこですか?自然な待機(ロックまたは IO)が発生する場所。わかりました。しかし、計算サイクルを回転させるとどうなるでしょうか?そうすると、明白でほぼ唯一の場所は、次の演算子を実行した後のインタープリタ内です (厳密にはインタープリタではありません)。
通常、SQL サーバーは純粋なコンピューティング処理には使用されませんが、これは問題ではありません。しかし、サイクルはあらゆる種類の一時テーブル(すぐにキャッシュされる)と連携して動作するため、コードは非常に高速に実行される演算子のシーケンスに変換されます。
ちなみに、関数をNATIVELY COMPILEDでラップすると、時間の要求がなくなり、速度が10倍になります。しかし、協調型マルチタスクはどうでしょうか?しかし、ネイティブにコンパイルされたコードの場合、SQL で PREEMPTIVE MULTITASKING を実行する必要がありました。
出所: habr.com
