Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Η υψηλή απόδοση είναι μία από τις βασικές απαιτήσεις κατά την εργασία με μεγάλα δεδομένα. Στο τμήμα φόρτωσης δεδομένων της Sberbank, αντλούμε σχεδόν όλες τις συναλλαγές στο Data Cloud που βασίζεται στο Hadoop και ως εκ τούτου αντιμετωπίζουμε πραγματικά μεγάλες ροές πληροφοριών. Φυσικά, πάντα αναζητούμε τρόπους βελτίωσης της απόδοσης και τώρα θέλουμε να σας πούμε πώς καταφέραμε να επιδιορθώσουμε το RegionServer HBase και τον πελάτη HDFS, χάρη στον οποίο μπορέσαμε να αυξήσουμε σημαντικά την ταχύτητα των λειτουργιών ανάγνωσης.
Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

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

Γιατί ο σκληρός δίσκος και οι γρήγορες αναγνώσεις τυχαίας πρόσβασης δεν είναι συμβατές
Όπως γνωρίζετε, το HBase, και πολλές άλλες βάσεις δεδομένων, αποθηκεύουν δεδομένα σε μπλοκ μεγέθους πολλών δεκάδων kilobyte. Από προεπιλογή είναι περίπου 64 KB. Τώρα ας φανταστούμε ότι πρέπει να πάρουμε μόνο 100 byte και ζητάμε από την HBase να μας δώσει αυτά τα δεδομένα χρησιμοποιώντας ένα συγκεκριμένο κλειδί. Δεδομένου ότι το μέγεθος του μπλοκ στο HFiles είναι 64 KB, το αίτημα θα είναι 640 φορές μεγαλύτερο (μόνο ένα λεπτό!) από το απαραίτητο.

Στη συνέχεια, αφού το αίτημα θα περάσει μέσω του HDFS και του μηχανισμού προσωρινής αποθήκευσης μεταδεδομένων του ShortCircuitCache (που επιτρέπει την άμεση πρόσβαση σε αρχεία), αυτό οδηγεί στην ανάγνωση ήδη 1 MB από το δίσκο. Ωστόσο, αυτό μπορεί να ρυθμιστεί με την παράμετρο dfs.client.read.shortcircuit.buffer.size και σε πολλές περιπτώσεις είναι λογικό να μειωθεί αυτή η τιμή, για παράδειγμα στα 126 KB.

Ας υποθέσουμε ότι το κάνουμε αυτό, αλλά επιπλέον, όταν αρχίζουμε να διαβάζουμε δεδομένα μέσω του java api, όπως λειτουργίες όπως το FileChannel.read και ζητάμε από το λειτουργικό σύστημα να διαβάσει τον καθορισμένο όγκο δεδομένων, διαβάζει "για κάθε περίπτωση" 2 φορές περισσότερο , δηλ. 256 KB στην περίπτωσή μας. Αυτό συμβαίνει επειδή η java δεν έχει έναν εύκολο τρόπο να ορίσει τη σημαία FADV_RANDOM για να αποτρέψει αυτήν τη συμπεριφορά.

Ως αποτέλεσμα, για να λάβουμε τα 100 byte μας, διαβάζονται 2600 φορές περισσότερα κάτω από την κουκούλα. Φαίνεται ότι η λύση είναι προφανής, ας μειώσουμε το μέγεθος του μπλοκ σε ένα kilobyte, να ορίσουμε την αναφερόμενη σημαία και να αποκτήσουμε μεγάλη επιτάχυνση διαφώτισης. Αλλά το πρόβλημα είναι ότι μειώνοντας το μέγεθος του μπλοκ κατά 2 φορές, μειώνουμε επίσης τον αριθμό των byte που διαβάζονται ανά μονάδα χρόνου κατά 2 φορές.

Κάποιο κέρδος από τη ρύθμιση της σημαίας FADV_RANDOM μπορεί να επιτευχθεί, αλλά μόνο με υψηλή πολλαπλή νήμα και με μέγεθος μπλοκ 128 KB, αλλά αυτό είναι το πολύ μερικές δεκάδες τοις εκατό:

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Πραγματοποιήθηκαν δοκιμές σε 100 αρχεία, με μέγεθος 1 GB το καθένα και σε 10 σκληρούς δίσκους.

Ας υπολογίσουμε τι μπορούμε, καταρχήν, να υπολογίζουμε σε αυτήν την ταχύτητα:
Ας πούμε ότι διαβάζουμε από 10 δίσκους με ταχύτητα 280 MB/sec, δηλ. 3 εκατομμύρια φορές 100 byte. Αλλά όπως θυμόμαστε, τα δεδομένα που χρειαζόμαστε είναι 2600 φορές λιγότερα από αυτά που διαβάζουμε. Έτσι, διαιρούμε τα 3 εκατομμύρια με το 2600 και παίρνουμε 1100 εγγραφές ανά δευτερόλεπτο.

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

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

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Εδώ βλέπουμε ότι για τα πρώτα λεπτά η ταχύτητα είναι πραγματικά περίπου χίλιες εγγραφές ανά δευτερόλεπτο. Ωστόσο, περαιτέρω, λόγω του γεγονότος ότι διαβάζονται πολύ περισσότερα από αυτά που ζητήθηκαν, τα δεδομένα καταλήγουν στο buff/cache του λειτουργικού συστήματος (linux) και η ταχύτητα αυξάνεται σε πιο αξιοπρεπείς 60 χιλιάδες ανά δευτερόλεπτο

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

Στην περίπτωσή μας, θα πραγματοποιήσουμε δοκιμές σε έναν πάγκο 4 διακομιστών, ο καθένας από τους οποίους χρεώνεται ως εξής:

CPU: Xeon E5-2680 v4 @ 2.40 GHz 64 νήματα.
Μνήμη: 730 GB.
Έκδοση java: 1.8.0_111

Και εδώ το βασικό σημείο είναι ο όγκος των δεδομένων στους πίνακες που πρέπει να διαβαστούν. Το γεγονός είναι ότι εάν διαβάζετε δεδομένα από έναν πίνακα που είναι εξ ολοκλήρου τοποθετημένος στην κρυφή μνήμη HBase, τότε δεν θα φτάσει καν στην ανάγνωση από το buff/cache του λειτουργικού συστήματος. Επειδή το HBase εκχωρεί από προεπιλογή το 40% της μνήμης σε μια δομή που ονομάζεται BlockCache. Ουσιαστικά πρόκειται για ένα ConcurrentHashMap, όπου το κλειδί είναι το όνομα αρχείου + μετατόπιση του μπλοκ και η τιμή είναι τα πραγματικά δεδομένα σε αυτήν τη μετατόπιση.

Έτσι, όταν διαβάζουμε μόνο από αυτή τη δομή, εμείς βλέπουμε εξαιρετική ταχύτητα, όπως ένα εκατομμύριο αιτήματα ανά δευτερόλεπτο. Αλλά ας φανταστούμε ότι δεν μπορούμε να διαθέσουμε εκατοντάδες gigabyte μνήμης μόνο για ανάγκες βάσης δεδομένων, επειδή υπάρχουν πολλά άλλα χρήσιμα πράγματα που τρέχουν σε αυτούς τους διακομιστές.

Για παράδειγμα, στην περίπτωσή μας, ο όγκος του BlockCache σε ένα RS είναι περίπου 12 GB. Προσγειώσαμε δύο RS σε έναν κόμβο, δηλ. Διατίθενται 96 GB για BlockCache σε όλους τους κόμβους. Και υπάρχουν πολλές φορές περισσότερα δεδομένα, για παράδειγμα, ας είναι 4 πίνακες, 130 περιοχές ο καθένας, στους οποίους τα αρχεία έχουν μέγεθος 800 MB, συμπιεσμένα με FAST_DIFF, δηλ. συνολικά 410 GB (αυτά είναι καθαρά δεδομένα, δηλαδή χωρίς να λαμβάνεται υπόψη ο παράγοντας αναπαραγωγής).

Έτσι, το BlockCache είναι μόνο περίπου το 23% του συνολικού όγκου δεδομένων και αυτό είναι πολύ πιο κοντά στις πραγματικές συνθήκες αυτού που ονομάζεται BigData. Και εδώ αρχίζει η διασκέδαση - γιατί προφανώς, όσο λιγότερα χτυπήματα cache, τόσο χειρότερη είναι η απόδοση. Άλλωστε, αν χάσετε, θα πρέπει να κάνετε πολλή δουλειά - δηλ. μεταβείτε στην κλήση των λειτουργιών του συστήματος. Ωστόσο, αυτό δεν μπορεί να αποφευχθεί, οπότε ας δούμε μια εντελώς διαφορετική πτυχή - τι συμβαίνει με τα δεδομένα μέσα στη μνήμη cache;

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

1. Τοποθετήστε το μπλοκ 1 στην κρυφή μνήμη
2. Αφαιρέστε το μπλοκ 1 από την προσωρινή μνήμη
3. Τοποθετήστε το μπλοκ 2 στην κρυφή μνήμη
4. Αφαιρέστε το μπλοκ 2 από την προσωρινή μνήμη
5. Τοποθετήστε το μπλοκ 3 στην κρυφή μνήμη

Ολοκληρώθηκαν 5 ενέργειες! Ωστόσο, αυτή η κατάσταση δεν μπορεί να χαρακτηριστεί φυσιολογική· στην πραγματικότητα, αναγκάζουμε την HBase να κάνει ένα σωρό εντελώς άχρηστες εργασίες. Διαβάζει συνεχώς δεδομένα από την προσωρινή μνήμη του λειτουργικού συστήματος, τα τοποθετεί στο BlockCache, για να τα πετάξει σχεδόν αμέσως επειδή έχει φτάσει ένα νέο τμήμα δεδομένων. Το animation στην αρχή της ανάρτησης δείχνει την ουσία του προβλήματος - Ο Σκουπιδοσυλλέκτης σβήνει, η ατμόσφαιρα θερμαίνεται, η μικρή Γκρέτα στη μακρινή και καυτή Σουηδία αναστατώνεται. Και εμείς οι άνθρωποι της πληροφορικής δεν μας αρέσει όταν τα παιδιά είναι λυπημένα, οπότε αρχίζουμε να σκεφτόμαστε τι μπορούμε να κάνουμε για αυτό.

Τι γίνεται αν δεν βάλετε όλα τα μπλοκ στην κρυφή μνήμη, αλλά μόνο ένα ορισμένο ποσοστό από αυτά, για να μην ξεχειλίσει η κρυφή μνήμη; Ας ξεκινήσουμε προσθέτοντας απλώς μερικές γραμμές κώδικα στην αρχή της λειτουργίας για την τοποθέτηση δεδομένων στο BlockCache:

  public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory) {
    if (cacheDataBlockPercent != 100 && buf.getBlockType().isData()) {
      if (cacheKey.getOffset() % 100 >= cacheDataBlockPercent) {
        return;
      }
    }
...

Το θέμα εδώ είναι το εξής: η μετατόπιση είναι η θέση του μπλοκ στο αρχείο και τα τελευταία ψηφία του κατανέμονται τυχαία και ομοιόμορφα από το 00 έως το 99. Επομένως, θα παραλείψουμε μόνο αυτά που εμπίπτουν στην περιοχή που χρειαζόμαστε.

Για παράδειγμα, ορίστε cacheDataBlockPercent = 20 και δείτε τι συμβαίνει:

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Το αποτέλεσμα είναι προφανές. Στα παρακάτω γραφήματα, γίνεται σαφές γιατί συνέβη μια τέτοια επιτάχυνση - εξοικονομούμε πολλούς πόρους GC χωρίς να κάνουμε το Σισύφειο έργο της τοποθέτησης δεδομένων στην κρυφή μνήμη μόνο για να τα πετάξουμε αμέσως στον αγωγό των σκύλων του Άρη:

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Ταυτόχρονα, η χρήση της CPU αυξάνεται, αλλά είναι πολύ μικρότερη από την παραγωγικότητα:

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Αξίζει επίσης να σημειωθεί ότι τα μπλοκ που είναι αποθηκευμένα στο BlockCache είναι διαφορετικά. Τα περισσότερα, περίπου το 95%, είναι τα ίδια τα δεδομένα. Και τα υπόλοιπα είναι μεταδεδομένα, όπως φίλτρα Bloom ή LEAF_INDEX και т.д.. Αυτά τα δεδομένα δεν είναι αρκετά, αλλά είναι πολύ χρήσιμα, γιατί πριν αποκτήσει απευθείας πρόσβαση στα δεδομένα, το HBase στρέφεται στο meta για να καταλάβει εάν είναι απαραίτητο να ψάξετε εδώ περαιτέρω και, εάν ναι, πού ακριβώς βρίσκεται το μπλοκ ενδιαφέροντος.

Επομένως, στον κώδικα βλέπουμε μια συνθήκη ελέγχου buf.getBlockType().isData() και χάρη σε αυτό το meta, θα το αφήσουμε στην κρυφή μνήμη σε κάθε περίπτωση.

Τώρα ας αυξήσουμε το φορτίο και ας σφίξουμε ελαφρώς το χαρακτηριστικό με μία κίνηση. Στην πρώτη δοκιμή κάναμε το ποσοστό αποκοπής = 20 και το BlockCache υποχρησιμοποιήθηκε ελαφρώς. Τώρα ας το ορίσουμε στο 23% και ας προσθέσουμε 100 νήματα κάθε 5 λεπτά για να δούμε σε ποιο σημείο εμφανίζεται ο κορεσμός:

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Εδώ βλέπουμε ότι η αρχική έκδοση φτάνει σχεδόν αμέσως στο ταβάνι με περίπου 100 χιλιάδες αιτήματα ανά δευτερόλεπτο. Ενώ το patch δίνει επιτάχυνση έως και 300 χιλιάδες. Ταυτόχρονα, είναι σαφές ότι η περαιτέρω επιτάχυνση δεν είναι πλέον τόσο «δωρεάν»· η χρήση της CPU αυξάνεται επίσης.

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

Τρεις επιλογές έχουν προστεθεί για τον έλεγχο αυτού:

hbase.lru.cache.heavy.eviction.count.limit — ορίζει πόσες φορές θα πρέπει να εκτελείται η διαδικασία εξάλειψης δεδομένων από την κρυφή μνήμη πριν αρχίσουμε να χρησιμοποιούμε τη βελτιστοποίηση (δηλαδή παράβλεψη μπλοκ). Από προεπιλογή ισούται με MAX_INT = 2147483647 και στην πραγματικότητα σημαίνει ότι το χαρακτηριστικό δεν θα αρχίσει ποτέ να λειτουργεί με αυτήν την τιμή. Επειδή η διαδικασία έξωσης ξεκινά κάθε 5 - 10 δευτερόλεπτα (εξαρτάται από το φορτίο) και 2147483647 * 10 / 60 / 60 / 24 / 365 = 680 χρόνια. Ωστόσο, μπορούμε να ορίσουμε αυτήν την παράμετρο στο 0 και να κάνουμε τη λειτουργία να λειτουργεί αμέσως μετά την εκκίνηση.

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

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

hbase.lru.cache.heavy.eviction.mb.size.limit — ορίζει πόσα megabyte θα θέλαμε να τοποθετήσουμε στην κρυφή μνήμη (και, φυσικά, την εξαγωγή) σε 10 δευτερόλεπτα. Το χαρακτηριστικό θα προσπαθήσει να φτάσει σε αυτήν την τιμή και να τη διατηρήσει. Το θέμα είναι το εξής: αν χώσουμε gigabytes στην κρυφή μνήμη, τότε θα πρέπει να εκδιώξουμε gigabytes, και αυτό, όπως είδαμε παραπάνω, είναι πολύ ακριβό. Ωστόσο, δεν πρέπει να προσπαθήσετε να το ρυθμίσετε πολύ μικρό, καθώς αυτό θα προκαλέσει πρόωρη έξοδο από τη λειτουργία παράβλεψης μπλοκ. Για ισχυρούς διακομιστές (περίπου 20-40 φυσικούς πυρήνες), είναι βέλτιστο να ορίσετε περίπου 300-400 MB. Για τη μεσαία κατηγορία (~10 πυρήνες) 200-300 MB. Για αδύναμα συστήματα (2-5 πυρήνες) τα 50-100 MB μπορεί να είναι φυσιολογικά (δεν έχουν δοκιμαστεί σε αυτά).

Ας δούμε πώς λειτουργεί αυτό: ας πούμε ότι ορίσαμε το hbase.lru.cache.heavy.eviction.mb.size.limit = 500, υπάρχει κάποιο είδος φόρτωσης (ανάγνωσης) και μετά κάθε ~10 δευτερόλεπτα υπολογίζουμε πόσα byte ήταν έξωση χρησιμοποιώντας τον τύπο:

Γενικά = Άθροισμα ελευθερωμένων byte (MB) * 100 / Όριο (MB) - 100;

Εάν στην πραγματικότητα 2000 MB εκδιώχθηκαν, τότε τα γενικά έξοδα ισούνται με:

2000 * 100 / 500 - 100 = 300%

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

Ωστόσο, εάν το φορτίο πέσει, ας υποθέσουμε ότι μόνο 200 MB αποβάλλονται και το Overhead γίνεται αρνητικό (η λεγόμενη υπέρβαση):

200 * 100 / 500 - 100 = -60%

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

Παρακάτω είναι ένα παράδειγμα του πώς φαίνεται αυτό σε πραγματικά δεδομένα. Δεν χρειάζεται να προσπαθήσετε να φτάσετε στο 0%, είναι αδύνατο. Είναι πολύ καλό όταν είναι περίπου 30 - 100%, αυτό βοηθά στην αποφυγή πρόωρης εξόδου από τη λειτουργία βελτιστοποίησης κατά τη διάρκεια βραχυπρόθεσμων υπερτάσεων.

hbase.lru.cache.heavy.eviction.overhead.coefficient — ορίζει πόσο γρήγορα θα θέλαμε να πάρουμε το αποτέλεσμα. Εάν γνωρίζουμε με βεβαιότητα ότι οι αναγνώσεις μας είναι ως επί το πλείστον μεγάλες και δεν θέλουμε να περιμένουμε, μπορούμε να αυξήσουμε αυτή την αναλογία και να έχουμε υψηλές επιδόσεις πιο γρήγορα.

Για παράδειγμα, ορίζουμε αυτόν τον συντελεστή = 0.01. Αυτό σημαίνει ότι το Overhead (βλ. παραπάνω) θα πολλαπλασιαστεί με αυτόν τον αριθμό με το αποτέλεσμα που προκύπτει και το ποσοστό των μπλοκ που έχουν αποθηκευτεί στην κρυφή μνήμη θα μειωθεί. Ας υποθέσουμε ότι Overhead = 300% και συντελεστής = 0.01, τότε το ποσοστό των μπλοκ προσωρινής αποθήκευσης θα μειωθεί κατά 3%.

Μια παρόμοια λογική "Backpressure" εφαρμόζεται επίσης για αρνητικές τιμές Overhead (υπέρβαση). Δεδομένου ότι οι βραχυπρόθεσμες διακυμάνσεις στον όγκο των αναγνώσεων και των εξώσεων είναι πάντα δυνατές, αυτός ο μηχανισμός σάς επιτρέπει να αποφύγετε την πρόωρη έξοδο από τη λειτουργία βελτιστοποίησης. Η αντίστροφη πίεση έχει μια ανεστραμμένη λογική: όσο ισχυρότερη είναι η υπέρβαση, τόσο περισσότερα μπλοκ αποθηκεύονται στην κρυφή μνήμη.

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Κωδικός υλοποίησης

        LruBlockCache cache = this.cache.get();
        if (cache == null) {
          break;
        }
        freedSumMb += cache.evict()/1024/1024;
        /*
        * Sometimes we are reading more data than can fit into BlockCache
        * and it is the cause a high rate of evictions.
        * This in turn leads to heavy Garbage Collector works.
        * So a lot of blocks put into BlockCache but never read,
        * but spending a lot of CPU resources.
        * Here we will analyze how many bytes were freed and decide
        * decide whether the time has come to reduce amount of caching blocks.
        * It help avoid put too many blocks into BlockCache
        * when evict() works very active and save CPU for other jobs.
        * More delails: https://issues.apache.org/jira/browse/HBASE-23887
        */

        // First of all we have to control how much time
        // has passed since previuos evict() was launched
        // This is should be almost the same time (+/- 10s)
        // because we get comparable volumes of freed bytes each time.
        // 10s because this is default period to run evict() (see above this.wait)
        long stopTime = System.currentTimeMillis();
        if ((stopTime - startTime) > 1000 * 10 - 1) {
          // Here we have to calc what situation we have got.
          // We have the limit "hbase.lru.cache.heavy.eviction.bytes.size.limit"
          // and can calculte overhead on it.
          // We will use this information to decide,
          // how to change percent of caching blocks.
          freedDataOverheadPercent =
            (int) (freedSumMb * 100 / cache.heavyEvictionMbSizeLimit) - 100;
          if (freedSumMb > cache.heavyEvictionMbSizeLimit) {
            // Now we are in the situation when we are above the limit
            // But maybe we are going to ignore it because it will end quite soon
            heavyEvictionCount++;
            if (heavyEvictionCount > cache.heavyEvictionCountLimit) {
              // It is going for a long time and we have to reduce of caching
              // blocks now. So we calculate here how many blocks we want to skip.
              // It depends on:
             // 1. Overhead - if overhead is big we could more aggressive
              // reducing amount of caching blocks.
              // 2. How fast we want to get the result. If we know that our
              // heavy reading for a long time, we don't want to wait and can
              // increase the coefficient and get good performance quite soon.
              // But if we don't sure we can do it slowly and it could prevent
              // premature exit from this mode. So, when the coefficient is
              // higher we can get better performance when heavy reading is stable.
              // But when reading is changing we can adjust to it and set
              // the coefficient to lower value.
              int change =
                (int) (freedDataOverheadPercent * cache.heavyEvictionOverheadCoefficient);
              // But practice shows that 15% of reducing is quite enough.
              // We are not greedy (it could lead to premature exit).
              change = Math.min(15, change);
              change = Math.max(0, change); // I think it will never happen but check for sure
              // So this is the key point, here we are reducing % of caching blocks
              cache.cacheDataBlockPercent -= change;
              // If we go down too deep we have to stop here, 1% any way should be.
              cache.cacheDataBlockPercent = Math.max(1, cache.cacheDataBlockPercent);
            }
          } else {
            // Well, we have got overshooting.
            // Mayby it is just short-term fluctuation and we can stay in this mode.
            // It help avoid permature exit during short-term fluctuation.
            // If overshooting less than 90%, we will try to increase the percent of
            // caching blocks and hope it is enough.
            if (freedSumMb >= cache.heavyEvictionMbSizeLimit * 0.1) {
              // Simple logic: more overshooting - more caching blocks (backpressure)
              int change = (int) (-freedDataOverheadPercent * 0.1 + 1);
              cache.cacheDataBlockPercent += change;
              // But it can't be more then 100%, so check it.
              cache.cacheDataBlockPercent = Math.min(100, cache.cacheDataBlockPercent);
            } else {
              // Looks like heavy reading is over.
              // Just exit form this mode.
              heavyEvictionCount = 0;
              cache.cacheDataBlockPercent = 100;
            }
          }
          LOG.info("BlockCache evicted (MB): {}, overhead (%): {}, " +
            "heavy eviction counter: {}, " +
            "current caching DataBlock (%): {}",
            freedSumMb, freedDataOverheadPercent,
            heavyEvictionCount, cache.cacheDataBlockPercent);

          freedSumMb = 0;
          startTime = stopTime;
       }

Ας τα δούμε τώρα όλα αυτά χρησιμοποιώντας ένα πραγματικό παράδειγμα. Έχουμε το ακόλουθο σενάριο δοκιμής:

  1. Ας αρχίσουμε να κάνουμε Σάρωση (25 νήματα, παρτίδα = 100)
  2. Μετά από 5 λεπτά, προσθέστε multi-gets (25 νήματα, παρτίδα = 100)
  3. Μετά από 5 λεπτά, απενεργοποιήστε τα multi-gets (μόνο η σάρωση απομένει ξανά)

Κάνουμε δύο εκτελέσεις, πρώτα hbase.lru.cache.heavy.eviction.count.limit = 10000 (που στην πραγματικότητα απενεργοποιεί τη δυνατότητα) και μετά ορίζουμε όριο = 0 (το ενεργοποιεί).

Στα παρακάτω αρχεία καταγραφής βλέπουμε πώς ενεργοποιείται η δυνατότητα και επαναφέρει το Overshooting στο 14-71%. Από καιρό σε καιρό το φορτίο μειώνεται, το οποίο ενεργοποιεί το Backpressure και το HBase αποθηκεύει ξανά στην κρυφή μνήμη περισσότερα μπλοκ.

Καταγραφή RegionServer
εξωθήθηκε (MB): 0, αναλογία 0.0, γενικά έξοδα (%): -100, βαρύ μετρητή εξώθησης: 0, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 100
εξωθήθηκε (MB): 0, αναλογία 0.0, γενικά έξοδα (%): -100, βαρύ μετρητή εξώθησης: 0, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 100
έξωση (MB): 2170, αναλογία 1.09, γενικά έξοδα (%): 985, βαρύ μετρητή εξώθησης: 1, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 91 < έναρξη
έξωση (MB): 3763, αναλογία 1.08, γενικά έξοδα (%): 1781, βαρύ μετρητή εξώθησης: 2, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 76
έξωση (MB): 3306, αναλογία 1.07, γενικά έξοδα (%): 1553, βαρύ μετρητή εξώθησης: 3, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 61
έξωση (MB): 2508, αναλογία 1.06, γενικά έξοδα (%): 1154, βαρύ μετρητή εξώθησης: 4, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 50
έξωση (MB): 1824, αναλογία 1.04, γενικά έξοδα (%): 812, βαρύ μετρητή εξώθησης: 5, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 42
έξωση (MB): 1482, αναλογία 1.03, γενικά έξοδα (%): 641, βαρύ μετρητή εξώθησης: 6, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 36
έξωση (MB): 1140, αναλογία 1.01, γενικά έξοδα (%): 470, βαρύ μετρητή εξώθησης: 7, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 32
έξωση (MB): 913, αναλογία 1.0, γενικά έξοδα (%): 356, βαρύ μετρητή εξώθησης: 8, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 29
έξωση (MB): 912, αναλογία 0.89, γενικά έξοδα (%): 356, βαρύ μετρητή εξώθησης: 9, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 26
έξωση (MB): 684, αναλογία 0.76, γενικά έξοδα (%): 242, βαρύ μετρητή εξώθησης: 10, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 24
έξωση (MB): 684, αναλογία 0.61, γενικά έξοδα (%): 242, βαρύ μετρητή εξώθησης: 11, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 456, αναλογία 0.51, γενικά έξοδα (%): 128, βαρύ μετρητή εξώθησης: 12, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 21
έξωση (MB): 456, αναλογία 0.42, γενικά έξοδα (%): 128, βαρύ μετρητή εξώθησης: 13, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 20
έξωση (MB): 456, αναλογία 0.33, γενικά έξοδα (%): 128, βαρύ μετρητή εξώθησης: 14, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 15, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 342, αναλογία 0.32, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 16, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 342, αναλογία 0.31, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 17, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.3, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 18, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.29, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 19, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.27, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 20, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.25, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 21, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.24, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 22, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.22, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 23, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.21, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 24, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.2, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 25, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 228, αναλογία 0.17, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 26, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
εξωθήθηκε (MB): 456, αναλογία 0.17, γενικά έξοδα (%): 128, βαρύ μετρητή εξώθησης: 27, τρέχουσα προσωρινή αποθήκευση Block δεδομένων (%): 18 < προστέθηκε λαμβάνει (αλλά ο πίνακας το ίδιο)
έξωση (MB): 456, αναλογία 0.15, γενικά έξοδα (%): 128, βαρύ μετρητή εξώθησης: 28, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 342, αναλογία 0.13, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 29, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 342, αναλογία 0.11, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 30, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 342, αναλογία 0.09, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 31, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 228, αναλογία 0.08, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 32, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 228, αναλογία 0.07, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 33, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 228, αναλογία 0.06, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 34, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 228, αναλογία 0.05, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 35, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 228, αναλογία 0.05, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 36, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 228, αναλογία 0.04, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 37, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 109, αναλογία 0.04, γενικά έξοδα (%): -46, βαρύ μετρητή εξώθησης: 37, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22 < αντίθλιψη
έξωση (MB): 798, αναλογία 0.24, γενικά έξοδα (%): 299, βαρύ μετρητή εξώθησης: 38, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 20
έξωση (MB): 798, αναλογία 0.29, γενικά έξοδα (%): 299, βαρύ μετρητή εξώθησης: 39, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 570, αναλογία 0.27, γενικά έξοδα (%): 185, βαρύ μετρητή εξώθησης: 40, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 17
έξωση (MB): 456, αναλογία 0.22, γενικά έξοδα (%): 128, βαρύ μετρητή εξώθησης: 41, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 16
έξωση (MB): 342, αναλογία 0.16, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 42, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 16
έξωση (MB): 342, αναλογία 0.11, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 43, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 16
έξωση (MB): 228, αναλογία 0.09, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 44, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 16
έξωση (MB): 228, αναλογία 0.07, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 45, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 16
έξωση (MB): 228, αναλογία 0.05, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 46, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 16
έξωση (MB): 222, αναλογία 0.04, γενικά έξοδα (%): 11, βαρύ μετρητή εξώθησης: 47, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 16
έξωση (MB): 104, αναλογία 0.03, γενικά έξοδα (%): -48, βαρύ μετρητή εξώθησης: 47, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 21 < λαμβάνει η διακοπή
έξωση (MB): 684, αναλογία 0.2, γενικά έξοδα (%): 242, βαρύ μετρητή εξώθησης: 48, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 19
έξωση (MB): 570, αναλογία 0.23, γενικά έξοδα (%): 185, βαρύ μετρητή εξώθησης: 49, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 342, αναλογία 0.22, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 50, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 228, αναλογία 0.21, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 51, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 228, αναλογία 0.2, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 52, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 228, αναλογία 0.18, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 53, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 228, αναλογία 0.16, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 54, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 228, αναλογία 0.14, γενικά έξοδα (%): 14, βαρύ μετρητή εξώθησης: 55, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 18
έξωση (MB): 112, αναλογία 0.14, γενικά έξοδα (%): -44, βαρύ μετρητή εξώθησης: 55, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 23 < αντίθλιψη
έξωση (MB): 456, αναλογία 0.26, γενικά έξοδα (%): 128, βαρύ μετρητή εξώθησης: 56, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.31, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 57, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 58, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 59, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 60, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 61, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 62, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 63, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.32, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 64, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 65, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 66, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.32, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 67, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 68, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.32, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 69, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.32, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 70, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 71, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 72, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 73, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 74, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 75, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
έξωση (MB): 342, αναλογία 0.33, γενικά έξοδα (%): 71, βαρύ μετρητή εξώθησης: 76, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 22
εξωθήθηκε (MB): 21, αναλογία 0.33, γενικά έξοδα (%): -90, βαρύ μετρητή εξώθησης: 76, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 32
εξωθήθηκε (MB): 0, αναλογία 0.0, γενικά έξοδα (%): -100, βαρύ μετρητή εξώθησης: 0, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 100
εξωθήθηκε (MB): 0, αναλογία 0.0, γενικά έξοδα (%): -100, βαρύ μετρητή εξώθησης: 0, τρέχουσα προσωρινή αποθήκευση DataBlock (%): 100

Οι σαρώσεις χρειάστηκαν για να εμφανιστεί η ίδια διαδικασία με τη μορφή γραφήματος της σχέσης μεταξύ δύο τμημάτων κρυφής μνήμης - μονής (όπου μπλοκ που δεν έχουν ζητηθεί ποτέ πριν) και πολλαπλών (τα δεδομένα "αιτούνται" τουλάχιστον μία φορά αποθηκεύονται εδώ):

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Και τέλος, πώς φαίνεται η λειτουργία των παραμέτρων με τη μορφή γραφήματος. Για σύγκριση, η κρυφή μνήμη απενεργοποιήθηκε εντελώς στην αρχή, στη συνέχεια το HBase ξεκίνησε με προσωρινή αποθήκευση και καθυστέρηση της έναρξης των εργασιών βελτιστοποίησης κατά 5 λεπτά (30 κύκλοι εξώθησης).

Μπορείτε να βρείτε τον πλήρη κωδικό στο Pull Request HBASE 23887 στο github.

Ωστόσο, 300 χιλιάδες αναγνώσεις ανά δευτερόλεπτο δεν είναι το μόνο που μπορεί να επιτευχθεί σε αυτό το υλικό υπό αυτές τις συνθήκες. Το γεγονός είναι ότι όταν χρειάζεται πρόσβαση σε δεδομένα μέσω HDFS, χρησιμοποιείται ο μηχανισμός ShortCircuitCache (εφεξής SSC), ο οποίος σας επιτρέπει να έχετε άμεση πρόσβαση στα δεδομένα, αποφεύγοντας τις αλληλεπιδράσεις δικτύου.

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

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Έχοντας συνειδητοποιήσει αυτό, συνειδητοποιήσαμε ότι το πρόβλημα μπορεί να παρακαμφθεί δημιουργώντας μια σειρά ανεξάρτητων SSC:

private final ShortCircuitCache[] shortCircuitCache;
...
shortCircuitCache = new ShortCircuitCache[this.clientShortCircuitNum];
for (int i = 0; i < this.clientShortCircuitNum; i++)
  this.shortCircuitCache[i] = new ShortCircuitCache(…);

Και στη συνέχεια εργαστείτε μαζί τους, εξαιρουμένων των διασταυρώσεων επίσης στο τελευταίο ψηφίο μετατόπισης:

public ShortCircuitCache getShortCircuitCache(long idx) {
    return shortCircuitCache[(int) (idx % clientShortCircuitNum)];
}

Τώρα μπορείτε να ξεκινήσετε τις δοκιμές. Για να γίνει αυτό, θα διαβάσουμε αρχεία από το HDFS με μια απλή εφαρμογή πολλαπλών νημάτων. Ορίστε τις παραμέτρους:

conf.set("dfs.client.read.shortcircuit", "true");
conf.set("dfs.client.read.shortcircuit.buffer.size", "65536"); // по дефолту = 1 МБ и это сильно замедляет чтение, поэтому лучше привести в соответствие к реальным нуждам
conf.set("dfs.client.short.circuit.num", num); // от 1 до 10

Και απλά διαβάστε τα αρχεία:

FSDataInputStream in = fileSystem.open(path);
for (int i = 0; i < count; i++) {
    position += 65536;
    if (position > 900000000)
        position = 0L;
    int res = in.read(position, byteBuffer, 0, 65536);
}

Αυτός ο κώδικας εκτελείται σε ξεχωριστά νήματα και θα αυξήσουμε τον αριθμό των αρχείων που διαβάζονται ταυτόχρονα (από 10 σε 200 - οριζόντιος άξονας) και τον αριθμό των κρυφών μνήμης (από 1 σε 10 - γραφικά). Ο κατακόρυφος άξονας δείχνει την επιτάχυνση που προκύπτει από την αύξηση του SSC σε σχέση με την περίπτωση που υπάρχει μόνο μία κρυφή μνήμη.

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Πώς να διαβάσετε το γράφημα: Ο χρόνος εκτέλεσης για 100 χιλιάδες αναγνώσεις σε μπλοκ 64 KB με μία κρυφή μνήμη απαιτεί 78 δευτερόλεπτα. Ενώ με 5 κρυφές μνήμες χρειάζονται 16 δευτερόλεπτα. Εκείνοι. υπάρχει επιτάχυνση ~5 φορές. Όπως φαίνεται από το γράφημα, το αποτέλεσμα δεν είναι πολύ αισθητό για έναν μικρό αριθμό παράλληλων αναγνώσεων· αρχίζει να παίζει αξιοσημείωτο ρόλο όταν υπάρχουν περισσότερες από 50 αναγνώσεις νημάτων. Είναι επίσης αξιοσημείωτο ότι αυξάνεται ο αριθμός των SSC από 6 και παραπάνω δίνει μια σημαντικά μικρότερη αύξηση απόδοσης.

Σημείωση 1: δεδομένου ότι τα αποτελέσματα των δοκιμών είναι αρκετά πτητικά (βλ. παρακάτω), πραγματοποιήθηκαν 3 εκτελέσεις και οι τιμές που προέκυψαν υπολογίστηκαν κατά μέσο όρο.

Σημείωση 2: Το κέρδος απόδοσης από τη διαμόρφωση της τυχαίας πρόσβασης είναι το ίδιο, αν και η ίδια η πρόσβαση είναι ελαφρώς πιο αργή.

Ωστόσο, είναι απαραίτητο να διευκρινιστεί ότι, σε αντίθεση με την περίπτωση του HBase, αυτή η επιτάχυνση δεν είναι πάντα δωρεάν. Εδώ "ξεκλειδώνουμε" την ικανότητα της CPU να κάνει περισσότερη δουλειά, αντί να κρέμεται από κλειδαριές.

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

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

Για παράδειγμα, ας ρίξουμε μια πιο προσεκτική ματιά στη ρύθμιση SSC = 3. Η αύξηση της απόδοσης στο εύρος είναι περίπου 3.3 φορές. Παρακάτω είναι τα αποτελέσματα και από τις τρεις ξεχωριστές διαδρομές.

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

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

Έτσι, αυτό θα έχει θετικό αποτέλεσμα για κάθε εργαλείο που χρησιμοποιεί μαζική πρόσβαση σε HDFS (για παράδειγμα Spark, κ.λπ.), υπό την προϋπόθεση ότι ο κωδικός εφαρμογής είναι ελαφρύς (δηλαδή το βύσμα βρίσκεται στην πλευρά του πελάτη HDFS) και υπάρχει δωρεάν ισχύς CPU . Για να ελέγξουμε, ας δοκιμάσουμε τι αποτέλεσμα θα έχει η συνδυασμένη χρήση της βελτιστοποίησης BlockCache και του συντονισμού SSC για ανάγνωση από το HBase.

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

Μπορεί να φανεί ότι υπό τέτοιες συνθήκες το αποτέλεσμα δεν είναι τόσο μεγάλο όσο σε εκλεπτυσμένες δοκιμές (ανάγνωση χωρίς καμία επεξεργασία), αλλά είναι πολύ πιθανό να συμπιεστούν επιπλέον 80K εδώ. Μαζί, και οι δύο βελτιστοποιήσεις παρέχουν έως και 4x επιτάχυνση.

Έγινε επίσης ένα PR για αυτή τη βελτιστοποίηση [HDFS-15202], το οποίο έχει συγχωνευθεί και αυτή η λειτουργία θα είναι διαθέσιμη σε μελλοντικές εκδόσεις.

Και τέλος, ήταν ενδιαφέρον να συγκρίνουμε την απόδοση ανάγνωσης μιας παρόμοιας βάσης δεδομένων ευρείας στήλης, της Cassandra και της HBase.

Για να γίνει αυτό, ξεκινήσαμε παρουσίες του τυπικού βοηθητικού προγράμματος δοκιμής φόρτωσης YCSB από δύο κεντρικούς υπολογιστές (800 νήματα συνολικά). Από την πλευρά του διακομιστή - 4 περιπτώσεις RegionServer και Cassandra σε 4 κεντρικούς υπολογιστές (όχι αυτούς όπου εκτελούνται οι πελάτες, για να αποφευχθεί η επιρροή τους). Οι αναγνώσεις προήλθαν από πίνακες μεγέθους:

HBase – 300 GB σε HDFS (100 GB καθαρά δεδομένα)

Cassandra - 250 GB (συντελεστής αναπαραγωγής = 3)

Εκείνοι. ο όγκος ήταν περίπου ο ίδιος (στο HBase λίγο περισσότερο).

Παράμετροι HBase:

dfs.client.short.circuit.num = 5 (Βελτιστοποίηση πελάτη HDFS)

hbase.lru.cache.heavy.eviction.count.limit = 30 - αυτό σημαίνει ότι το έμπλαστρο θα αρχίσει να λειτουργεί μετά από 30 εξώσεις (~5 λεπτά)

hbase.lru.cache.heavy.eviction.mb.size.limit = 300 — όγκος στόχος αποθήκευσης και εξώθησης

Τα αρχεία καταγραφής YCSB αναλύθηκαν και μεταγλωττίστηκαν σε γραφήματα Excel:

Πώς να αυξήσετε την ταχύτητα ανάγνωσης από το HBase έως και 3 φορές και από το HDFS έως και 5 φορές

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

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

Πηγή: www.habr.com

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