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

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

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

Собствено, лесно е да се постигне, че производственият сървър е безнадеждно изостанал от лаптопа. Изпълнете (не на tempdb и не на база с включена Delayed Durability) кода:

set nocount on
create table _t (v varchar(100))
declare @n int=300000
while @n>0 begin 
  insert into _t select 'Какъв бавен!'
  delete from _t
  set @n=@n-1
  end
GO
drop table _t

На моя десктоп се изпълнява за 5 секунди, а на производствения сървър — 28 секунди. Защото SQL трябва да изчака физическото приключване на записа в transaction log, а тук правим много кратки транзакции. Грубо казано, ние вкарахме голям мощен камион в градския трафик и наблюдаваме как пицарите на скутери го изпреварват — тук не е важен throughput, важна е само latency. А нищо мрежово хранилище, колкото и да е скъпо, не може да надмине локалния SSD по latency.

(в коментарите се оказа, че съм излъгал — имам delayed durability и на двете места. Без delayed durability е така:
Desktop — 39 секунди, 15K tr/sec, 0.065ms /io roundtrip
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 -- проверка на нечетни числа до квадратния корен
  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. Всичко изглеждаше наред — ресурсите бяха в изобилие, Ready time = 0, всичко беше достатъчно, по време на теста на бързите и на бавните сървъри CPU=100 на един vCPU. Избрах тест, свързан с изчисляването на числото Pi — тестът показваше еднакви резултати на всякакви сървъри. Въздухът започна да ухае на черна магия.

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

От отчаяние стартирах 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 0) { Console.WriteLine("{0}", counter); } } } }

    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 е била значително подобрена в текущите продукти. Функцията също така е била препоръчвана за използване, когато се извършват измервания, които изискват прецизен източник на реално време в виртуалната машина.

С други думи, трябва да добавите параметър

monitor_control.virtual_rdtsc = FALSE

Заключение

Наистина сте се запитали: защо SQL извиква GetTimePrecise толкова често?

Нямам изходен код на SQL сървъра, но логиката казва следното. SQL е почти операционна система с кооперативна конкурентност, където всеки поток трябва от време на време да 'устъпва'. А къде е най-добре да се направи това? Там, където има естествено изчакване — блокировка или I/O. Добре, а какво, ако въртим изчислителни цикли? Тогава единственото очевидно място е в интерпретатора (това не е точно интерпретатор), след изпълнението на следващата команда.

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

Между другото, ако функцията се обгърне в NATIVELY COMPILED, тя престава да запитва времето и скоростта ѝ се увеличава 10 пъти. А какво ще кажете за кооперативната мултитаскинг? Ами за нативно компилирания код всъщност е трябвало в SQL да се направи PREEMPTIVE MULTITASKING.

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

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster