Ναι, ο παλιός μου φορητός υπολογιστής είναι αρκετές φορές πιο ισχυρός από τον διακομιστή παραγωγής σας.

Αυτοί είναι οι ισχυρισμοί που άκουσα από τους προγραμματιστές μας. Το πιο ενδιαφέρον είναι ότι αυτό αποδείχθηκε αληθινό, δίνοντας αφορμή για μια μακρά έρευνα. Θα μιλήσουμε για διακομιστές 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.6 ms
Έπρεπε να είχα προσέξει ότι είναι πολύ γρήγορο)

Ωστόσο, σε αυτή την περίπτωση έχουμε να κάνουμε με τετριμμένα μηδενικά της συνάρτησης ζήτα Riemann με ένα ασήμαντο παράδειγμα. Στο παράδειγμα που μου έφεραν οι προγραμματιστές, ήταν διαφορετικά. Ήμουν πεπεισμένος ότι είχαν δίκιο και άρχισα να καθαρίζω όλες τις ιδιαιτερότητές τους που σχετίζονται με την επιχειρηματική λογική από το παράδειγμα. Κάποια στιγμή, συνειδητοποίησα ότι μπορούσα να πετάξω εντελώς τον κώδικα τους και να γράψω τον δικό μου - που δείχνει το ίδιο πρόβλημα - στην παραγωγή τρέχει 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. Έκανα μια δοκιμή για να υπολογίσω τον αριθμό των Pi - η δοκιμή έδειξε τα ίδια αποτελέσματα σε οποιονδήποτε διακομιστή. Η μυρωδιά της μαύρης μαγείας γινόταν όλο και πιο δυνατή.

Έχοντας βγει στο αγρόκτημα DEV, άρχισα να παίζω με διακομιστές. Αποδείχθηκε ότι το vMotion από κεντρικό υπολογιστή σε κεντρικό υπολογιστή μπορεί να «θεραπεύσει» έναν διακομιστή, αλλά μπορεί επίσης να μετατρέψει έναν «γρήγορο» διακομιστή σε «αργό». Φαίνεται ότι αυτό είναι - ορισμένοι οικοδεσπότες έχουν πρόβλημα ... αλλά ... όχι. Κάποια εικονική μηχανή επιβράδυνε στον κεντρικό υπολογιστή, ας πούμε, τον Α, αλλά λειτούργησε γρήγορα στον κεντρικό υπολογιστή Β. Και η άλλη εικονική μηχανή, αντίθετα, δούλευε γρήγορα στο Α και επιβράδυνε στο Β! Τόσο «γρήγορα» και «αργά» αυτοκίνητα συχνά γυρνούσαν πάνω στον οικοδεσπότη!

Από εκείνη τη στιγμή, υπήρχε μια ευδιάκριτη μυρωδιά θείου στον αέρα. Εξάλλου, το πρόβλημα δεν μπορούσε να αποδοθεί σε καμία εικονική μηχανή (π.χ. ενημερώσεις κώδικα των 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, αλλάζοντας την ποσότητα της μνήμης ή τον αριθμό των επεξεργαστών συχνά μετέτρεπαν το μηχάνημα σε «γρήγορο». Όχι όμως για πάντα. Αλλά αυτό που αποδείχθηκε αλήθεια είναι ότι αρκεί να βγεις έξω και να χτυπήσεις τον τροχό - δηλαδή να αλλάξεις κάθε παράμετρος εικονικής μηχανής

Τελικά, οι Αμερικανοί συνάδελφοί μου βρήκαν ξαφνικά μια βασική αιτία.

Ναι, ο παλιός μου φορητός υπολογιστής είναι αρκετές φορές πιο ισχυρός από τον διακομιστή παραγωγής σας.

Οι οικοδεσπότες διέφεραν σε συχνότητα!

  • Κατά κανόνα, αυτό δεν είναι τρομακτικό. Αλλά: κατά τη μετάβαση από έναν "εγγενή" κεντρικό υπολογιστή σε έναν κεντρικό υπολογιστή με "διαφορετική" συχνότητα, το 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 φορές. Τι γίνεται όμως με το cooperative multitasking; Αλλά για εγγενώς μεταγλωττισμένο κώδικα, έπρεπε να κάνω PREEMPTIVE MULTITASKING στην SQL.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο