是的,我的舊筆記本電腦比您的生產服務器強大數倍。

這些正是我從我們的開發人員那裡聽到的抱怨。 最有趣的是,事實證明這是真的,引發了長期的調查。 我們將討論在 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。

來源: www.habr.com

添加評論