Μερικές φορές περισσότερο είναι λιγότερο. Όταν η μείωση του φορτίου οδηγεί σε αύξηση της καθυστέρησης

Όπως στο τις περισσότερες αναρτήσεις, υπάρχει πρόβλημα με μια κατανεμημένη υπηρεσία, ας ονομάσουμε αυτήν την υπηρεσία Alvin. Αυτή τη φορά δεν ανακάλυψα το πρόβλημα μόνος μου, με ενημέρωσαν τα παιδιά από την πλευρά του πελάτη.

Μια μέρα ξύπνησα με ένα δυσαρεστημένο email λόγω μεγάλων καθυστερήσεων με τον Alvin, το οποίο σχεδιάζαμε να κυκλοφορήσουμε στο εγγύς μέλλον. Συγκεκριμένα, ο πελάτης αντιμετώπισε καθυστέρηση 99ου εκατοστημόριου στην περιοχή των 50 ms, πολύ πάνω από τον προϋπολογισμό μας για λανθάνουσα κατάσταση. Αυτό ήταν εκπληκτικό καθώς δοκίμασα την υπηρεσία εκτενώς, ειδικά σε λανθάνουσα κατάσταση, που είναι ένα κοινό παράπονο.

Πριν βάλω τον Alvin σε δοκιμές, έκανα πολλά πειράματα με 40 ερωτήματα ανά δευτερόλεπτο (QPS), όλα δείχνουν λανθάνουσα κατάσταση μικρότερη από 10 ms. Ήμουν έτοιμος να δηλώσω ότι δεν συμφωνώ με τα αποτελέσματά τους. Αλλά ρίχνοντας μια άλλη ματιά στην επιστολή, παρατήρησα κάτι νέο: δεν είχα δοκιμάσει ακριβώς τις συνθήκες που ανέφεραν, το QPS τους ήταν πολύ χαμηλότερο από το δικό μου. Δοκίμασα σε 40k QPS, αλλά αυτά μόνο σε 1k. Έκανα ένα άλλο πείραμα, αυτή τη φορά με χαμηλότερο QPS, μόνο και μόνο για να τους κατευνάσω.

Εφόσον γράφω για αυτό το blog, πιθανότατα έχετε ήδη καταλάβει ότι οι αριθμοί τους ήταν σωστοί. Δοκίμασα τον εικονικό πελάτη μου ξανά και ξανά, με το ίδιο αποτέλεσμα: ένας χαμηλός αριθμός αιτημάτων όχι μόνο αυξάνει τον λανθάνοντα χρόνο, αλλά αυξάνει τον αριθμό των αιτημάτων με καθυστέρηση μεγαλύτερη από 10 ms. Με άλλα λόγια, εάν στα 40k QPS περίπου 50 αιτήματα ανά δευτερόλεπτο υπερέβαιναν τα 50 ms, τότε σε 1k QPS υπήρχαν 100 αιτήματα άνω των 50 ms κάθε δευτερόλεπτο. Παράδοξο!

Μερικές φορές περισσότερο είναι λιγότερο. Όταν η μείωση του φορτίου οδηγεί σε αύξηση της καθυστέρησης

Περιορίζοντας την αναζήτηση

Όταν αντιμετωπίζετε ένα πρόβλημα καθυστέρησης σε ένα κατανεμημένο σύστημα με πολλά στοιχεία, το πρώτο βήμα είναι να δημιουργήσετε μια σύντομη λίστα υπόπτων. Ας σκάψουμε λίγο πιο βαθιά στην αρχιτεκτονική του Alvin:

Μερικές φορές περισσότερο είναι λιγότερο. Όταν η μείωση του φορτίου οδηγεί σε αύξηση της καθυστέρησης

Ένα καλό σημείο εκκίνησης είναι μια λίστα με ολοκληρωμένες μεταβάσεις εισόδου/εξόδου (κλήσεις δικτύου/αναζητήσεις δίσκου, κ.λπ.). Ας προσπαθήσουμε να καταλάβουμε πού είναι η καθυστέρηση. Εκτός από την προφανή I/O με τον πελάτη, ο Alvin κάνει ένα επιπλέον βήμα: έχει πρόσβαση στο χώρο αποθήκευσης δεδομένων. Ωστόσο, αυτός ο χώρος αποθήκευσης λειτουργεί στο ίδιο σύμπλεγμα με το Alvin, επομένως ο λανθάνοντας χρόνος εκεί θα πρέπει να είναι μικρότερος από τον πελάτη. Λοιπόν, η λίστα των υπόπτων:

  1. Κλήση δικτύου από πελάτη στον Alvin.
  2. Κλήση δικτύου από τον Alvin στο χώρο αποθήκευσης δεδομένων.
  3. Αναζήτηση στο δίσκο στο χώρο αποθήκευσης δεδομένων.
  4. Κλήση δικτύου από την αποθήκη δεδομένων στον Alvin.
  5. Κλήση δικτύου από τον Alvin σε έναν πελάτη.

Ας προσπαθήσουμε να διαγράψουμε κάποια σημεία.

Η αποθήκευση δεδομένων δεν έχει καμία σχέση με αυτό

Το πρώτο πράγμα που έκανα ήταν να μετατρέψω τον Alvin σε διακομιστή ping-ping που δεν επεξεργάζεται αιτήματα. Όταν λαμβάνει ένα αίτημα, επιστρέφει μια κενή απάντηση. Εάν η καθυστέρηση μειωθεί, τότε ένα σφάλμα στην υλοποίηση του Alvin ή της αποθήκης δεδομένων δεν είναι κάτι πρωτόγνωρο. Στο πρώτο πείραμα έχουμε το ακόλουθο γράφημα:

Μερικές φορές περισσότερο είναι λιγότερο. Όταν η μείωση του φορτίου οδηγεί σε αύξηση της καθυστέρησης

Όπως μπορείτε να δείτε, δεν υπάρχει καμία βελτίωση κατά τη χρήση του διακομιστή ping-ping. Αυτό σημαίνει ότι η αποθήκη δεδομένων δεν αυξάνει την καθυστέρηση και η λίστα των υπόπτων μειώνεται στο μισό:

  1. Κλήση δικτύου από πελάτη στον Alvin.
  2. Κλήση δικτύου από τον Alvin σε έναν πελάτη.

Εξαιρετική! Η λίστα συρρικνώνεται γρήγορα. Νόμιζα ότι είχα σχεδόν καταλάβει τον λόγο.

gRPC

Τώρα είναι η ώρα να σας παρουσιάσουμε έναν νέο παίκτη: gRPC. Αυτή είναι μια βιβλιοθήκη ανοιχτού κώδικα από την Google για επικοινωνία κατά τη διαδικασία RPC. αν και gRPC καλά βελτιστοποιημένο και ευρέως χρησιμοποιημένο, αυτή ήταν η πρώτη μου φορά που το χρησιμοποιούσα σε ένα σύστημα αυτού του μεγέθους και περίμενα ότι η υλοποίησή μου δεν θα ήταν βέλτιστη - το λιγότερο.

διαθεσιμότητα gRPC στη στοίβα δημιούργησε μια νέα ερώτηση: ίσως είναι η εφαρμογή μου ή εγώ gRPC προκαλεί πρόβλημα καθυστέρησης; Προσθήκη νέου υπόπτου στη λίστα:

  1. Ο πελάτης καλεί τη βιβλιοθήκη gRPC
  2. Βιβλιοθήκη gRPC πραγματοποιεί μια κλήση δικτύου στη βιβλιοθήκη του πελάτη gRPC στο διακομιστή
  3. Βιβλιοθήκη gRPC επαφές Alvin (καμία λειτουργία σε περίπτωση διακομιστή πινγκ-πονγκ)

Για να σας δώσω μια ιδέα για το πώς φαίνεται ο κώδικας, η εφαρμογή πελάτη μου/Alvin δεν διαφέρει πολύ από την εφαρμογή πελάτη-διακομιστή ασύγχρονα παραδείγματα.

Σημείωση: Η παραπάνω λίστα είναι λίγο απλοποιημένη γιατί gRPC καθιστά δυνατή τη χρήση του δικού σας μοντέλου νήματος (πρότυπο;), στο οποίο η στοίβα εκτέλεσης είναι συνυφασμένη gRPC και υλοποίηση χρήστη. Για λόγους απλότητας, θα παραμείνουμε σε αυτό το μοντέλο.

Το προφίλ θα διορθώσει τα πάντα

Έχοντας διαγράψει τις αποθήκες δεδομένων, νόμιζα ότι είχα σχεδόν τελειώσει: «Τώρα είναι εύκολο! Ας εφαρμόσουμε το προφίλ και ας μάθουμε πού συμβαίνει η καθυστέρηση." Εγώ μεγάλος λάτρης του προφίλ ακριβείας, επειδή οι CPU είναι πολύ γρήγορες και τις περισσότερες φορές δεν αποτελούν το σημείο συμφόρησης. Οι περισσότερες καθυστερήσεις συμβαίνουν όταν ο επεξεργαστής πρέπει να σταματήσει την επεξεργασία για να κάνει κάτι άλλο. Το Ακριβές προφίλ CPU κάνει ακριβώς αυτό: καταγράφει με ακρίβεια τα πάντα διακόπτες περιβάλλοντος και διευκρινίζει πού σημειώνονται καθυστερήσεις.

Πήρα τέσσερα προφίλ: με υψηλό QPS (χαμηλή καθυστέρηση) και με διακομιστή πινγκ πονγκ με χαμηλό QPS (υψηλή καθυστέρηση), τόσο από την πλευρά του πελάτη όσο και από την πλευρά του διακομιστή. Και για παν ενδεχόμενο, πήρα και δείγμα προφίλ επεξεργαστή. Όταν συγκρίνω προφίλ, συνήθως αναζητώ μια ανώμαλη στοίβα κλήσεων. Για παράδειγμα, στην κακή πλευρά με τον υψηλό λανθάνοντα χρόνο υπάρχουν πολλοί περισσότεροι διακόπτες περιβάλλοντος (10 φορές ή περισσότεροι). Αλλά στην περίπτωσή μου, ο αριθμός των διακοπτών περιβάλλοντος ήταν σχεδόν ο ίδιος. Προς φρίκη μου, δεν υπήρχε τίποτα σημαντικό εκεί.

Πρόσθετος εντοπισμός σφαλμάτων

Ήμουν απελπισμένος. Δεν ήξερα ποια άλλα εργαλεία θα μπορούσα να χρησιμοποιήσω και το επόμενο σχέδιό μου ήταν ουσιαστικά να επαναλάβω τα πειράματα με διαφορετικές παραλλαγές αντί να διαγνώσω ξεκάθαρα το πρόβλημα.

Κι αν

Από την αρχή με απασχολούσε το συγκεκριμένο latency των 50ms. Αυτή είναι μια πολύ μεγάλη στιγμή. Αποφάσισα ότι θα κόψω κομμάτια από τον κώδικα μέχρι να καταλάβω ποιο ακριβώς τμήμα προκαλεί αυτό το σφάλμα. Μετά ήρθε ένα πείραμα που πέτυχε.

Ως συνήθως, εκ των υστέρων φαίνεται ότι όλα ήταν προφανή. Τοποθέτησα τον πελάτη στο ίδιο μηχάνημα με τον Alvin - και έστειλα ένα αίτημα localhost. Και η αύξηση της καθυστέρησης έχει φύγει!

Μερικές φορές περισσότερο είναι λιγότερο. Όταν η μείωση του φορτίου οδηγεί σε αύξηση της καθυστέρησης

Κάτι δεν πήγαινε καλά με το δίκτυο.

Εκμάθηση δεξιοτήτων μηχανικού δικτύου

Πρέπει να ομολογήσω: οι γνώσεις μου για τις τεχνολογίες δικτύου είναι τρομερές, ειδικά αν λάβουμε υπόψη το γεγονός ότι εργάζομαι μαζί τους καθημερινά. Αλλά το δίκτυο ήταν ο κύριος ύποπτος και έπρεπε να μάθω πώς να το διορθώνω.

Ευτυχώς, το Διαδίκτυο αγαπά όσους θέλουν να μάθουν. Ο συνδυασμός ping και tracert φαινόταν σαν μια αρκετά καλή αρχή για τον εντοπισμό σφαλμάτων των προβλημάτων μεταφοράς δικτύου.

Πρώτον, ξεκίνησα PsPing στη θύρα TCP του Alvin. Χρησιμοποίησα τις προεπιλεγμένες ρυθμίσεις - τίποτα το ιδιαίτερο. Από περισσότερα από χίλια ping, κανένα δεν ξεπέρασε τα 10 ms, με εξαίρεση το πρώτο για προθέρμανση. Αυτό είναι αντίθετο με την παρατηρούμενη αύξηση του λανθάνοντος χρόνου κατά 50 ms στο 99ο εκατοστημόριο: εκεί, για κάθε 100 αιτήματα, θα έπρεπε να έχουμε δει περίπου ένα αίτημα με καθυστέρηση 50 ms.

Μετά προσπάθησα tracert: Μπορεί να υπάρχει πρόβλημα σε έναν από τους κόμβους κατά μήκος της διαδρομής μεταξύ του Alvin και του πελάτη. Αλλά και ο ιχνηλάτης επέστρεψε με άδεια χέρια.

Επομένως, δεν ήταν ο κώδικάς μου, η εφαρμογή του gRPC ή το δίκτυο που προκαλούσε την καθυστέρηση. Είχα αρχίσει να ανησυχώ ότι δεν θα το καταλάβαινα ποτέ αυτό.

Τώρα σε ποιο λειτουργικό σύστημα είμαστε

gRPC χρησιμοποιείται ευρέως στο Linux, αλλά εξωτικό στα Windows. Αποφάσισα να δοκιμάσω ένα πείραμα, το οποίο λειτούργησε: Δημιούργησα μια εικονική μηχανή Linux, μεταγλωττίζω το Alvin για Linux και το ανέπτυξα.

Μερικές φορές περισσότερο είναι λιγότερο. Όταν η μείωση του φορτίου οδηγεί σε αύξηση της καθυστέρησης

Και να τι συνέβη: ο διακομιστής πινγκ-πονγκ Linux δεν είχε τις ίδιες καθυστερήσεις με έναν παρόμοιο κεντρικό υπολογιστή των Windows, αν και η πηγή δεδομένων δεν ήταν διαφορετική. Αποδεικνύεται ότι το πρόβλημα βρίσκεται στην εφαρμογή gRPC για Windows.

Ο αλγόριθμος του Nagle

Όλο αυτό το διάστημα νόμιζα ότι μου έλειπε μια σημαία gRPC. Τώρα καταλαβαίνω τι είναι πραγματικά gRPC Η σημαία των Windows λείπει. Βρήκα μια εσωτερική βιβλιοθήκη RPC που ήμουν σίγουρος ότι θα λειτουργούσε καλά για όλες τις σημαίες που είχαν οριστεί Winsock. Στη συνέχεια, πρόσθεσα όλες αυτές τις σημαίες στο gRPC και ανέπτυξα τον Alvin στα Windows, σε έναν ενημερωμένο διακομιστή πινγκ-πονγκ των Windows!

Μερικές φορές περισσότερο είναι λιγότερο. Όταν η μείωση του φορτίου οδηγεί σε αύξηση της καθυστέρησης

Σχεδόν Έγινε: Άρχισα να αφαιρώ τις πρόσθετες σημαίες μία κάθε φορά μέχρι να επιστρέψει η παλινδρόμηση, ώστε να μπορέσω να εντοπίσω την αιτία. Ήταν διαβόητο TCP_NODELAY, διακόπτης αλγορίθμου Nagle.

Ο αλγόριθμος του Nagle προσπαθεί να μειώσει τον αριθμό των πακέτων που αποστέλλονται μέσω ενός δικτύου καθυστερώντας τη μετάδοση των μηνυμάτων έως ότου το μέγεθος του πακέτου υπερβεί έναν ορισμένο αριθμό byte. Αν και αυτό μπορεί να είναι καλό για τον μέσο χρήστη, είναι καταστροφικό για διακομιστές σε πραγματικό χρόνο, καθώς το λειτουργικό σύστημα θα καθυστερήσει ορισμένα μηνύματα, προκαλώντας καθυστερήσεις στο χαμηλό QPS. U gRPC Αυτή η σημαία ορίστηκε στην εφαρμογή Linux για υποδοχές TCP, αλλά όχι στα Windows. Είμαι αυτό διορθώθηκε.

Συμπέρασμα

Η υψηλότερη καθυστέρηση σε χαμηλό QPS προκλήθηκε από τη βελτιστοποίηση του λειτουργικού συστήματος. Εκ των υστέρων, το προφίλ δεν εντόπισε λανθάνουσα κατάσταση επειδή έγινε σε λειτουργία πυρήνα και όχι σε λειτουργία χρήστη. Δεν ξέρω αν ο αλγόριθμος του Nagle μπορεί να παρατηρηθεί μέσω συλλήψεων ETW, αλλά θα ήταν ενδιαφέρον.

Όσον αφορά το πείραμα του localhost, πιθανότατα δεν άγγιξε τον πραγματικό κώδικα δικτύωσης και ο αλγόριθμος του Nagle δεν εκτελούσε, επομένως τα ζητήματα λανθάνοντος χρόνου εξαφανίστηκαν όταν ο πελάτης έφτασε στον Alvin μέσω του localhost.

Την επόμενη φορά που θα δείτε αύξηση του λανθάνοντος χρόνου καθώς μειώνεται ο αριθμός των αιτημάτων ανά δευτερόλεπτο, ο αλγόριθμος του Nagle θα πρέπει να είναι στη λίστα των υπόπτων σας!

Πηγή: www.habr.com

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