Да, старият ми лаптоп е няколко пъти по-мощен от производствения ви сървър

Това са точно оплакванията, които чух от нашите разработчици. Най-интересното е, че това се оказа вярно, което даде повод за дълго разследване. Ще говорим за SQL сървъри, които работят на VMware.

Да, старият ми лаптоп е няколко пъти по-мощен от производствения ви сървър

Всъщност е лесно да се гарантира, че производственият сървър е безнадеждно зад лаптопа. Изпълнете (не на 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/sec, 0.065ms /io отиване и връщане
PROD - 360 секунди, 1600 tr/sec, 0.6ms
Трябваше да забележа, че беше твърде бързо)

В този случай обаче имаме работа с тривиални нули на дзета функцията на Риман с тривиален пример. В примера, който разработчиците ми донесоха, беше различно. Бях убеден, че са прави, и започнах да премахвам от примера всички техни специфики, свързани с бизнес логиката. В един момент разбрах, че мога напълно да изхвърля техния код и да напиша свой собствен - което демонстрира същия проблем - в продукцията работи 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 на един vCPU. Направих тест за изчисляване на числото Пи - тестът показа едни и същи резултати на всеки сървър. Миризмата на черна магия ставаше все по-силна и по-силна.

След като стигнах до DEV фермата, започнах да играя със сървърите. Оказа се, че vMotion от хост към хост може да „излекува“ сървър, но също така може да превърне „бърз“ сървър в „бавен“. Изглежда, че това е - някои хостове имат проблем... но... не. Някаква виртуална машина беше бавна на хост, да речем A, но работеше бързо на хост B. А друга виртуална машина, напротив, работеше бързо на A и се забави на B! Както „бързите“, така и „бавните“ машини често се въртяха на хоста!

От този момент нататък във въздуха се усещаше отчетлива миризма на сяра. В крайна сметка проблемът не може да се припише на виртуалната машина (кръпки на Windows, например) - в края на краищата той се превърна в „бърз“ с vMotion. Но проблемът също не може да се припише на хоста - в края на краищата той може да има както „бързи“, така и „бавни“ машини. Освен това това не беше свързано с натоварването - успях да получа „бавна“ машина на хоста, където нямаше нищо освен него.

От отчаяние стартирах Process Explorer от Sysinternals и погледнах 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 пъти (!!!). Това вече беше малка победа: във всеки случай нямаше заплаха да се заклещим между поддръжката на Microsoft и VMware, така че да обърнат стрелки един към друг.

След това прогресът спря - ваканции, важни дела, вирусна истерия и рязко увеличаване на натоварването. Често споменавах магическия проблем на моите колеги, но понякога изглеждаше, че дори не винаги ми вярваха - твърдението, че VMware забавя кода с 10-20 пъти, беше твърде чудовищно.

Опитах се да открия какво ме забавя. Понякога ми се струваше, че съм намерил решение - включването и изключването на Hot plugs, промяната на количеството памет или броя на процесорите често превръщаха машината в „бърза“. Но не завинаги. Но това, което се оказа вярно е, че е достатъчно да излезеш и да чукнеш колелото – тоест смяна който и да е параметър на виртуална машина

Накрая моите американски колеги изведнъж откриха първопричината.

Да, старият ми лаптоп е няколко пъти по-мощен от производствения ви сървър

Домакините се различаваха по честота!

  • По правило това не е голяма работа. Но: при преместване от „роден“ хост към хост с „различна“ честота, VMware трябва да коригира резултата GetTimePrecise.
  • По правило това не е проблем, освен ако няма приложение, което изисква точния час милиони пъти в секунда, като SQL сървър.
  • Но това не е страшно, тъй като SQL сървърът не винаги прави това (вижте Заключение)

Но има случаи, когато този рейк удря силно. И все пак, да, чрез докосване на колелото (като промених нещо в настройките на VM) принудих 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 сървъра, но логиката казва това. SQL е почти операционна система с кооперативна паралелност, където всяка нишка трябва да „отстъпва“ от време на време. Къде е най-доброто място да направите това? Където има естествено чакане - заключване или IO. Добре, но какво ще стане, ако въртим изчислителни цикли? Тогава очевидното и почти единствено място е в интерпретатора (това всъщност не е интерпретатор), след изпълнение на следващия оператор.

Като цяло SQL сървърът не се използва за чисто изчислително заковаване и това не е проблем. Но циклите, които работят с всякакви временни таблици (които незабавно се кешират), превръщат кода в поредица от много бързо изпълнени оператори.

Между другото, ако обвиете функцията в NATIVELY COMPILED, тя спира да изисква време и скоростта й се увеличава 10 пъти. Но за нативно компилиран код трябваше да направим ПРЕДВАРИТЕЛНА МНОГОЗАДАЧНОСТ в SQL.

Източник: www.habr.com

Добавяне на нов коментар