是的,我的旧笔记本电脑比您的生产服务器强大几倍。

这些正是我从我们的开发人员那里听到的抱怨。 最有趣的是,事实证明这是真的,引发了长期的调查。 我们将讨论在 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.065 毫秒/io 往返
PROD - 360 秒,1600 tr/秒,0.6 毫秒
我应该注意到它太快了)

然而,在这种情况下,我们用一个简单的例子来处理黎曼 zeta 函数的平凡零点。 在开发人员给我带来的示例中,情况有所不同。 我确信他们是对的,并开始从示例中删除所有与业务逻辑相关的细节。 在某些时候,我意识到我可以完全抛弃他们的代码并编写自己的代码 - 这演示了同样的问题 - 在生产中它的运行速度慢了 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,一切都足够了,在快速和慢速服务器上的测试期间,一个 vCPU 上的 CPU = 100。 我进行了一个测试来计算 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倍(!!!)。 这已经是一个小小的胜利:无论如何,微软和VMware的支持之间不存在陷入僵局而互相攻击的威胁。

然后进展就停止了——假期、重要的事情、病毒式的歇斯底里和工作量的急剧增加。 我经常向我的同事提到这个神奇的问题,但有时他们似乎并不总是相信我——VMware 使代码速度减慢 10-20 倍的说法太可怕了。

我试图找出自己的原因,让我放慢脚步。 有时我似乎找到了解决方案 - 打开和关闭热插拔、更改内存量或处理器数量通常会使机器变得“快速”。 但不是永远。 但事实证明,走出去敲一下方向盘就足够了——也就是说,改变 任何 虚拟机参数

终于,我的美国同事突然找到了根本原因。

是的,我的旧笔记本电脑比您的生产服务器强大几倍。

主机频率不同!

  • 一般来说,这没什么大不了的。 但是:当从“本机”主机转移到具有“不同”频率的主机时,VMware 必须调整 GetTimePrecise 结果。
  • 一般来说,这不是问题,除非有一个应用程序每秒请求数百万次准确的时间,例如 SQL Server。
  • 但这并不可怕,因为 SQL Server 并不总是这样做(参见结论)

但在某些情况下,这种耙子会造成严重打击。 然而,是的,通过点击方向盘(通过更改虚拟机设置中的某些内容),我迫使 VMware“重新计算”配置,并且当前主机的频率成为机器的“本机”频率。

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

当您禁用 TSC 虚拟化时,从虚拟机内部读取 TSC 将返回物理机的 TSC 值,而从虚拟机内部写入 TSC 则不起作用。 将虚拟机迁移到另一台主机、从挂起状态恢复或恢复到快照会导致 TSC 不连续跳转。 禁用 TSC 虚拟化时,某些来宾操作系统无法启动,或出现其他计时问题。 过去,有时建议使用此功能来提高频繁读取 TSC 的应用程序的性能,但虚拟TSC的性能在当前产品中已经得到了大幅提升。 还建议在虚拟机中执行需要精确实时源的测量时使用该功能。

简而言之,需要添加参数

Monitor_control.virtual_rdtsc = FALSE

结论

您可能有一个问题:为什么 SQL 如此频繁地调用 GetTimePrecise?

我没有 SQL Server 源代码,但逻辑是这样的。 SQL 几乎是一个具有协作并发性的操作系统,其中每个线程必须不时地“屈服”。 执行此操作的最佳地点在哪里? 哪里有自然的等待——锁或者IO。 好的,但是如果我们旋转计算循环怎么办? 然后,明显且几乎唯一的地方是在执行下一条语句之后的解释器中(这不是真正的解释器)。

一般来说,SQL Server不用于纯粹的计算钉钉,这不是问题。 但是,使用各种临时表(立即缓存)的循环会将代码转换为一系列非常快速执行的语句。

顺便说一句,如果你把函数包装在 NATIVELY COMPILED 中,那么它就不再要求时间了,而且速度会提高 10 倍,那么协作多任务呢? 但对于本机编译的代码,我们必须在 SQL 中执行 PREEMPTIVE MULTITASKING。

来源: habr.com

添加评论