Ды мой стары laptop у некалькі разоў больш магутны, чым ваш production server

Менавіта такія прэтэнзіі я пачуў ад нашых дэвелапераў. Самае цікавае, што гэта аказалася праўдай, даўшы пачатак працягламу расследаванню. Гаворка пойдзе пра SQL servers, якія круцяцца ў нас на VMware.

Ды мой стары laptop у некалькі разоў больш магутны, чым ваш production server

Уласна, дамагчыся таго, каб production server безнадзейна адстаў ад лаптопа лёгка. Выканайце (не на tempdb і не на базе з уключанай Delayed Durability) код:

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 секунд, а на production server - 28 секунд. Таму што SQL павінен чакаць фізічнага канчатка запісу ў transaction log, а мы тут які робіцца вельмі кароткія транзакцыі. Грубіянска кажучы, мы загналі вялікі магутны грузавік у гарадскі трафік, і назіраем, як яго ліха абганяюць дастаўшчыкі піцы на скутэрах - тут не важны throughput, важная толькі latency. А ніводны network storage, колькі б нулёў ні было ў яго кошце, не зможа выйграць па latency у лакальнага SSD.

(у каментах высветлілася што я схлусіў - у мяне ў абодвух месцах зачасаўся delayed durability. Без delayed durability атрымліваецца:
Desktop – 39 секунд, 15K tr/sec, 0.065ms /io roundtrip
PROD – 360 секунд, 1600 tr/sec, 0.6ms
Я павінен быў звярнуць увагу, што ўжо занадта хутка)

Аднак у дадзеным выпадку мы маем справу з трывіяльнымі нулямі зэта функцыі Рымана з трывіяльным прыкладам. У тым прыкладзе, які мне прынеслі дэвелаперы, было іншае. Я пераканаўся, што яны маюць рацыю, і стаў вычышчаць з прыкладу ўсю іх спецыфіку, звязаную з бізнэс логікай. У нейкі момант я зразумеў, што магу цалкам выкінуць іх код, і напісаць свой - які дэманструе тую ж праблему - на production ён выконваецца ў 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. Там было ўсё добра - рэсурсаў было ў лішку, Ready time = 0, усяго хапае, падчас тэсту і на хуткіх, і на павольных серверах CPU = 100 на адным vCPU. Я ўзяў тэст па разліку ліку Pi - тэст паказваў аднолькавыя вынікі на любых серверах. Усё мацней пахла чорнай магіяй.

Выбраўшыся на DEV ферму, я стаў гуляць серверамі. Высветлілася, што vMotion з хаста на хост можа "вылечыць" сервер, але можа і наадварот, "хуткі" сервер ператварыць у "павольны". Здаецца вось яно — нейкія хасты маюць праблему… але… не. Нейкая віртуалка тармазіла на хасце, дапусцім, A але працавала хутка на хасце B. А іншая віртуалка наадварот, працавала хутка на A і тармазіла на B! На хасце часта круціліся і хуткія і павольныя машынкі!

З гэтага моманту ў паветры выразна запахла шэрай. Бо праблема не магла быць прыпісана ні віртуалцы (windows patches, напрыклад) - бо яна ператваралася ў "хуткую" пры 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
… skipped
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 support так, каб яны перакладалі стрэлкі сябар на сябра.

Далей прагрэс спыніўся - водпуск, важныя справы, вірусная істэрыя і рэзкае ўзрастанне нагрузкі. Я часта згадваў магічную праблему калегам, але часам здавалася, што яны нават не заўсёды мне вераць – занадта ўжо жахлівай была заява ад тым, што VMware запавольвае код у 10-20 раз.

Я спрабаваў сам раскапаць, што ж тармозіць. Часам мне здавалася, што я знайшоў рашэнне уключэнне і выключэнне Hot plugs, змена аб'ёму памяці або лікі працэсараў часта ператварала машынку ў хуткую . Але не назаўжды. А вось што аказалася праўдай — дык гэта тое, што дастаткова выйсці і пастукаць па коле — гэта значыць змяніць любы параметр віртуалкі

Нарэшце мае амерыканскія калегі раптам знайшлі root cause.

Ды мой стары laptop у некалькі разоў больш магутны, чым ваш production server

Госты адрозніваліся частатой!

  • Як правіла, гэта не страшна. Але: пры пераездзе з 'роднага' хаста на хост з 'іншы' частатой VMware павінна карэктаваць вынік GetTimePrecise.
  • Як правіла гэта не страшна, калі толькі не аказваецца аплікацыі, якая запытвае дакладны час мільёны разоў у секунду, як SQL server.
  • Але і гэта не страшна, бо SQL server робіць гэта далёка не заўсёды (гл. Зняволенне)

Але ёсць выпадкі, калі гэтыя граблі балюча б'юць. І такі так, пастукаўшы па коле (памяняўшы нешта ў наладах VM) я прымушаў VMware 'пералічыць' канфігурацыю, і частата бягучага хаста станавілася 'роднай' частатой машынкі.

рашэнне

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

Калі Вы маеце магчымасць virtualization of TSC, reading TSC from within virtual machine returns the physical machine's TSC value, and writing the TSC from with virtual machine no effect. Міграцыя віртуальнай машыны на іншы host, resuming it з падрыхтаваных дзяржаў, або адпраўляючы на ​​выправіць causa TSC на скід discontinuously. Некаторыя іншыя сістэмныя сістэмы неадназначна, або выключаны іншыя timekeeping problémy, keď TSC віртуалізацыя не мае. У цырымоніі, гэты артыкул мае некаторыя тыя, што recommended k improve performance of applications read the TSC frequently, але вызначэнне віртуальнай TSC мае быць недастатковай, неадпаведна ў цяперашнім прадуктах. Памяшканне таксама будзе спалучана з дапамогай, калі ажыццяўленне меры, што патрабуе патрэбных крыніцаў рэальнага часу ў віртуальнай машыне.

Карацей кажучы, трэба дадаць параметр

monitor_control.virtual_rdtsc = FALSE

Заключэнне

У вас напэўна ўзнікла пытанне: а нафіга SQL выклікаць GetTimePrecise так часта?

У мяне няма зыходнікаў SQL server, але логіка кажа вось што. SQL гэта амаль аперацыёнка з cooperative concurrency, дзе кожны thread павінен час ад часу "саступаць". А дзе гэта лепей зрабіць? Там, дзе ёсць натуральнае чаканне - lock або IO. Добра, а што, калі мы круцім вылічальныя цыклы? Тады відавочнае і амаль адзінае месца – у інтэрпертатары (гэта не зусім інтэрпрэтатар), пасля выканання чарговага аператара.

Як правіла, SQL server не выкарыстоўваецца для забівання цвікоў чыстых вылічэнняў і гэта не зяўляецца праблемай. Але цыклы з працай са ўсякімі часавымі таблічкамі (якія тут жа кэшуюцца) ператвараюць код у паслядоўнасць вельмі хутка выкананых аператараў.

Дарэчы, калі функцыю абгарнуць у NATIVELY COMPILED, то яна перастае запытваць час, і яе хуткасць павялічваецца разоў у 10. А як жа cooperative multitasking? А вось для natively compiled code і прыйшлося ў SQL зрабіць PREEMPTIVE MULTITASKING.

Крыніца: habr.com

Дадаць каментар