Tak, mój stary laptop jest kilka razy mocniejszy niż twój serwer produkcyjny.

Oto twierdzenia, które słyszałem od naszych programistów. Najciekawsze jest to, że okazało się to prawdą, co doprowadziło do długiego śledztwa. Porozmawiamy o serwerach SQL działających na VMware.

Tak, mój stary laptop jest kilka razy mocniejszy niż twój serwer produkcyjny.

Właściwie umieszczenie serwera produkcyjnego beznadziejnie za laptopem jest łatwe. Uruchom (nie w tempdb i nie w bazie danych z włączoną funkcją Delayed Durability) kod:

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

Zajmuje to 5 sekund na moim pulpicie i 28 sekund na serwerze produkcyjnym. Ponieważ SQL musi czekać na fizyczny koniec zapisu do dziennika transakcji, a my tutaj robimy bardzo krótkie transakcje. Z grubsza mówiąc, wjechaliśmy dużą, potężną ciężarówką do ruchu miejskiego i obserwujemy, jak słynnie wyprzedzają ją dostawcy pizzy na skuterach - przepustowość nie jest tu ważna, ważne jest tylko opóźnienie. I żadna pamięć sieciowa, bez względu na to, ile zer jest w jej cenie, nie będzie w stanie przewyższyć lokalnego dysku SSD pod względem opóźnienia.

(w komentarzach okazało się, że skłamałem - w obu miejscach miałem opóźnioną trwałość. Bez opóźnionej trwałości okazuje się, że:
Komputer stacjonarny — 39 sekund, 15 0.065 tr/s, XNUMX ms/io w obie strony
PROD - 360 sekund, 1600 obr/s, 0.6 ms
Powinienem był zauważyć, że jest za szybko)

Jednak w tym przypadku mamy do czynienia z trywialnymi zerami funkcji zeta Riemanna z trywialnym przykładem. W przykładzie, który przynieśli mi programiści, było inaczej. Byłem przekonany, że mają rację i zacząłem usuwać z przykładu wszystkie ich specyfiki związane z logiką biznesową. W pewnym momencie zdałem sobie sprawę, że mógłbym całkowicie wyrzucić ich kod i napisać własny - co pokazuje ten sam problem - w produkcji działa 3-4 razy wolniej:

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

Jeśli wszystko jest z tobą w porządku, sprawdzenie prostoty liczby zajmie 6-7-8 sekund. Stało się tak na wielu serwerach. Ale na niektórych kontrola trwała 25-40 sekund. Co ciekawe, nie było serwerów, na których wykonanie zajęłoby powiedzmy 14 sekund - kod działał albo bardzo szybko, albo bardzo wolno, czyli problem był, powiedzmy, czarno-biały.

Co ja zrobiłem? Dostałem się do metryk VMware. Tam wszystko było w porządku - zasobów było mnóstwo, czas gotowości = 0, wszystkiego było pod dostatkiem, podczas testu zarówno na szybkich, jak i wolnych serwerach CPU = 100 na jednym vCPU. Zrobiłem test, aby obliczyć liczbę Pi - test wykazał te same wyniki na wszystkich serwerach. Zapach czarnej magii stawał się coraz silniejszy.

Po wyjściu na farmę DEV zacząłem bawić się serwerami. Okazało się, że vMotion od hosta do hosta może „wyleczyć” serwer, ale może też zmienić „szybki” serwer w „wolny”. Wygląda na to, że to jest to – niektórzy gospodarze mają problem… ale… nie. Pewna maszyna wirtualna zwolniła na hoście, powiedzmy, A, ale działała szybko na hoście B. A druga maszyna wirtualna, przeciwnie, działała szybko na A i zwolniła na B! Zarówno „szybkie”, jak i „wolne” samochody często obracały się na gospodarzu!

Od tego momentu w powietrzu unosił się wyraźny zapach siarki. W końcu problemu nie można było przypisać żadnej maszynie wirtualnej (na przykład poprawkom systemu Windows) - w końcu zmienił się w „szybki” z vMotion. Ale problemu nie można również przypisać hostowi - w końcu może on mieć zarówno „szybkie”, jak i „wolne” maszyny. Nie było to również związane z obciążeniem - udało mi się uzyskać „wolną” maszynę na hoście, na której poza nią nie było nic.

Z desperacji uruchomiłem Process Explorer Sysinternals i spojrzałem na stos SQL. Na wolnych maszynach linia natychmiast przykuła moją uwagę:

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
… pominięty
sqldk.dll!SystemThread::MakeMiniSOSTthread+0xa54
KERNEL32.DLL!BaseThreadInitThunk+0x14
Ntdll.dll! RtlUserThreadStart + 0x21

To już było coś. Program został napisany:

    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);
                }
            }
        }
    }

Ten program pokazał jeszcze wyraźniejsze spowolnienie - na "szybkich" maszynach pokazuje 16-18 milionów cykli na sekundę, podczas gdy na wolnych - półtora miliona, a nawet 700 tysięcy. Oznacza to, że różnica wynosi 10-20 razy (!!!). To już było małe zwycięstwo: w każdym razie nie było groźby utknięcia między wsparciem Microsoftu i VMware, tak aby zamienili strzały na siebie.

Potem postęp się zatrzymał - wakacje, ważne sprawy, wirusowa histeria i gwałtowny wzrost obciążenia pracą. Często wspominałem kolegom o magicznym problemie, ale momentami wydawało się, że nie zawsze mi wierzyli – stwierdzenie, że VMware spowolniło kod 10-20 razy, było zbyt monstrualne.

Próbowałem odkopać siebie, co to spowalnia. Momentami wydawało mi się, że znalazłem rozwiązanie – włączanie i wyłączanie Hot plugów, zmiana ilości pamięci czy liczby procesorów często zmieniały maszynę w „szybką”. Ale nie na zawsze. Jednak prawdą okazało się to, że wystarczy wyjść i zapukać w koło – czyli zmienić dowolny parametr maszyny wirtualnej

W końcu moi amerykańscy koledzy nagle znaleźli podstawową przyczynę.

Tak, mój stary laptop jest kilka razy mocniejszy niż twój serwer produkcyjny.

Gospodarze różnili się częstotliwością!

  • Z reguły nie jest to przerażające. Ale: podczas przechodzenia z „natywnego” hosta na host o „innej” częstotliwości, VMware musi dostosować wynik GetTimePrecise.
  • Z reguły nie stanowi to problemu, chyba że istnieje aplikacja, która żąda dokładnego czasu miliony razy na sekundę, na przykład serwer SQL.
  • Ale to też nie jest przerażające, ponieważ serwer SQL nie zawsze to robi (patrz Wnioski)

Ale są przypadki, kiedy ta prowizja boli. I tak, pukając w koło (zmieniając coś w ustawieniach VM), zmusiłem VMware do „przeliczenia” konfiguracji, a częstotliwość obecnego hosta stała się „natywną” częstotliwością maszyny.

decyzja

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

Gdy wyłączysz wirtualizację TSC, odczytanie TSC z poziomu maszyny wirtualnej zwróci wartość TSC maszyny fizycznej, a zapisanie TSC z poziomu maszyny wirtualnej nie przyniesie żadnego efektu. Migracja maszyny wirtualnej do innego hosta, wznawianie jej ze stanu wstrzymania lub powrót do migawki powoduje nieciągłe przeskakiwanie TSC. Niektóre systemy operacyjne gościa nie uruchamiają się lub wykazują inne problemy z pomiarem czasu, gdy wirtualizacja TSC jest wyłączona. W przeszłości ta funkcja była czasami zalecana w celu poprawy wydajności aplikacji, które często odczytują TSC, ale wydajność wirtualnego TSC została znacznie ulepszona w obecnych produktach. Funkcja została również zarekomendowana do użycia podczas wykonywania pomiarów wymagających precyzyjnego źródła czasu rzeczywistego w maszynie wirtualnej.

Krótko mówiąc, musisz dodać parametr

monitor_control.virtual_rdtsc = FAŁSZ

wniosek

Pewnie masz pytanie: dlaczego SQL miałby tak często wywoływać GetTimePrecise?

Nie mam źródeł serwera SQL, ale logika tak mówi. SQL jest prawie systemem operacyjnym ze współbieżnością kooperacyjną, w której każdy wątek musi od czasu do czasu „ustąpić”. Gdzie najlepiej to zrobić? Tam, gdzie jest naturalne oczekiwanie - lock lub IO. No dobra, ale co, jeśli kręcimy cykle obliczeniowe? Wtedy oczywiste i prawie jedyne miejsce jest w tłumaczu (to nie do końca jest tłumacz), po wykonaniu kolejnego operatora.

Z reguły serwer SQL nie jest używany do czystych obliczeń i nie stanowi to problemu. Ale cykle z pracą z wszelkiego rodzaju tabelami tymczasowymi (które są natychmiast buforowane) zamieniają kod w sekwencję bardzo szybko wykonywanych instrukcji.

Nawiasem mówiąc, jeśli funkcja jest opakowana w NATIVELY COMPILED, to przestaje żądać czasu, a jej prędkość wzrasta 10-krotnie.Ale co z wielozadaniowością kooperacyjną? Ale w przypadku natywnie skompilowanego kodu musiałem wykonać PREEMPTIVE MULTITASKING w SQL.

Źródło: www.habr.com

Dodaj komentarz