Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης

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

Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης

Επιλογή σελιδοποίησης #1

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

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

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

Ας δούμε το πρώτο ερώτημα χρησιμοποιώντας μια δοκιμαστική βάση δεδομένων MS SQL ως παράδειγμα AdventureWorks για διακομιστή του 2016. Για το σκοπό αυτό θα χρησιμοποιήσουμε τον πίνακα Sales.SalesOrderHeader:

SELECT * FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Το παραπάνω ερώτημα θα επιστρέψει τις πρώτες 50 παραγγελίες από τη λίστα, ταξινομημένες κατά φθίνουσα ημερομηνία προσθήκης, με άλλα λόγια, τις 50 πιο πρόσφατες παραγγελίες.

Εκτελείται γρήγορα στη δοκιμαστική βάση, αλλά ας δούμε το σχέδιο εκτέλεσης και τα στατιστικά I/O:

Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης

Table 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Μπορείτε να λάβετε στατιστικά στοιχεία εισόδου/εξόδου για κάθε ερώτημα εκτελώντας την εντολή SET STATISTICS IO ON στο χρόνο εκτέλεσης του ερωτήματος.

Όπως μπορείτε να δείτε από το σχέδιο εκτέλεσης, η πιο εντατική επιλογή πόρων είναι να ταξινομήσετε όλες τις σειρές του πίνακα προέλευσης κατά ημερομηνία προσθήκης. Και το πρόβλημα είναι ότι όσο περισσότερες σειρές εμφανίζονται στον πίνακα, τόσο πιο «σκληρή» θα είναι η ταξινόμηση. Στην πράξη, τέτοιες καταστάσεις θα πρέπει να αποφεύγονται, επομένως ας προσθέσουμε έναν δείκτη στην ημερομηνία προσθήκης και ας δούμε αν έχει αλλάξει η κατανάλωση πόρων:

Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης

Table 'SalesOrderHeader'. Scan count 1, logical reads 165, physical reads 0, read-ahead reads 5, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

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

SELECT * FROM Sales.SalesOrderHeader
WHERE SubTotal > 100
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης

Table 'SalesOrderHeader'. Scan count 1, logical reads 1081, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

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

CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);

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

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

Τώρα ας προχωρήσουμε στο δεύτερο ερώτημα που αναφέρθηκε στην αρχή - αυτό που μετράει τον αριθμό των εγγραφών που ικανοποιούν το κριτήριο αναζήτησης. Ας πάρουμε το ίδιο παράδειγμα - αναζήτηση για παραγγελίες που ξεπερνούν τα 100 $:

SELECT COUNT(1) FROM Sales.SalesOrderHeader
WHERE SubTotal > 100

Δεδομένου του σύνθετου δείκτη που αναφέρεται παραπάνω, λαμβάνουμε:

Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης

Table 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

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

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

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

Επιλογή σελιδοποίησης #2

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

Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης
Στην πραγματικότητα, το μόνο πράγμα που έχει αλλάξει είναι ότι δεν υπάρχει τρόπος πλοήγησης σε συγκεκριμένους αριθμούς σελίδων και τώρα αυτός ο πίνακας δεν χρειάζεται να γνωρίζει πόσοι μπορεί να είναι για να εμφανιστεί. Αλλά τίθεται το ερώτημα - πώς γνωρίζει ο πίνακας εάν υπάρχουν δεδομένα για την επόμενη σελίδα (για να εμφανιστεί σωστά ο σύνδεσμος "Επόμενο");

Η απάντηση είναι πολύ απλή: μπορείτε να διαβάσετε από τη βάση δεδομένων μία εγγραφή παραπάνω από αυτή που χρειάζεται για την εμφάνιση και η παρουσία αυτής της «πρόσθετης» εγγραφής θα δείξει αν υπάρχει επόμενο τμήμα. Με αυτόν τον τρόπο, χρειάζεται να εκτελέσετε μόνο ένα αίτημα για να λάβετε μία σελίδα δεδομένων, γεγονός που βελτιώνει σημαντικά την απόδοση και διευκολύνει την υποστήριξη τέτοιων λειτουργιών. Στην πρακτική μου, υπήρξε μια περίπτωση που η άρνηση μέτρησης του συνολικού αριθμού εγγραφών επιτάχυνε την παράδοση των αποτελεσμάτων κατά 4-5 φορές.

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

Αποχρώσεις εφαρμογής σελιδοποίησης

Όλα τα παραδείγματα ερωτημάτων που δίνονται παραπάνω χρησιμοποιούν την προσέγγιση "offset + count", όταν το ίδιο το ερώτημα καθορίζει με ποια σειρά οι σειρές αποτελέσματος και πόσες σειρές πρέπει να επιστραφούν. Αρχικά, ας δούμε πώς να οργανώσετε καλύτερα τη μετάδοση παραμέτρων σε αυτήν την περίπτωση. Στην πράξη, έχω συναντήσει διάφορες μεθόδους:

  • Ο σειριακός αριθμός της ζητούμενης σελίδας (pageIndex), μέγεθος σελίδας (pageSize).
  • Ο σειριακός αριθμός της πρώτης εγγραφής που θα επιστραφεί (startIndex), ο μέγιστος αριθμός εγγραφών στο αποτέλεσμα (count).
  • Ο αριθμός σειράς της πρώτης εγγραφής που θα επιστραφεί (startIndex), ο αριθμός σειράς της τελευταίας εγγραφής που θα επιστραφεί (endIndex).

Με την πρώτη ματιά μπορεί να φαίνεται ότι αυτό είναι τόσο στοιχειώδες που δεν υπάρχει διαφορά. Αλλά αυτό δεν είναι έτσι - η πιο βολική και καθολική επιλογή είναι η δεύτερη (startIndex, count). Υπάρχουν διάφοροι λόγοι για αυτό:

  • Για την προσέγγιση διόρθωσης καταχώρισης +1 που δίνεται παραπάνω, η πρώτη επιλογή με το pageIndex και το pageSize είναι εξαιρετικά άβολη. Για παράδειγμα, θέλουμε να εμφανίζουμε 50 δημοσιεύσεις ανά σελίδα. Σύμφωνα με τον παραπάνω αλγόριθμο, πρέπει να διαβάσετε μία εγγραφή παραπάνω από αυτή που χρειάζεται. Εάν αυτό το "+1" δεν έχει εφαρμοστεί στον διακομιστή, αποδεικνύεται ότι για την πρώτη σελίδα πρέπει να ζητήσουμε εγγραφές από το 1 έως το 51, για τη δεύτερη - από το 51 έως το 101 κ.λπ. Εάν καθορίσετε μέγεθος σελίδας 51 και αυξήσετε το pageIndex, τότε η δεύτερη σελίδα θα επιστρέψει από 52 σε 102, κ.λπ. Αντίστοιχα, στην πρώτη επιλογή, ο μόνος τρόπος για να εφαρμόσετε σωστά ένα κουμπί για να μεταβείτε στην επόμενη σελίδα είναι να ζητήσετε από τον διακομιστή να διορθώσει την "επιπλέον" γραμμή, η οποία θα είναι μια πολύ σιωπηρή απόχρωση.
  • Η τρίτη επιλογή δεν έχει καθόλου νόημα, καθώς για να εκτελέσετε ερωτήματα στις περισσότερες βάσεις δεδομένων θα πρέπει να περάσετε το πλήθος και όχι το ευρετήριο της τελευταίας εγγραφής. Η αφαίρεση του startIndex από το endIndex μπορεί να είναι μια απλή αριθμητική πράξη, αλλά εδώ είναι περιττή.

Τώρα θα πρέπει να περιγράψουμε τα μειονεκτήματα της υλοποίησης της σελιδοποίησης μέσω "offset + quantity":

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

Υπάρχουν εναλλακτικές, αλλά είναι και ατελείς. Η πρώτη από αυτές τις προσεγγίσεις ονομάζεται «σελιδοποίηση συνόλου κλειδιών» ή «μέθοδος αναζήτησης» και έχει ως εξής: μετά τη λήψη ενός τμήματος, μπορείτε να θυμηθείτε τις τιμές των πεδίων στην τελευταία εγγραφή της σελίδας και στη συνέχεια να τις χρησιμοποιήσετε για να αποκτήσετε την επόμενη μερίδα. Για παράδειγμα, εκτελέσαμε το ακόλουθο ερώτημα:

SELECT * FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Και στην τελευταία εγγραφή πήραμε την τιμή της ημερομηνίας παραγγελίας «2014-06-29». Στη συνέχεια, για να λάβετε την επόμενη σελίδα, μπορείτε να δοκιμάσετε να κάνετε αυτό:

SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate < '2014-06-29'
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Το πρόβλημα είναι ότι η OrderDate είναι ένα μη μοναδικό πεδίο και η συνθήκη που προσδιορίζεται παραπάνω είναι πιθανό να χάσει πολλές απαιτούμενες σειρές. Για να προσθέσετε σαφήνεια σε αυτό το ερώτημα, πρέπει να προσθέσετε ένα μοναδικό πεδίο στη συνθήκη (υποθέστε ότι το 75074 είναι η τελευταία τιμή του πρωτεύοντος κλειδιού από το πρώτο τμήμα):

SELECT * FROM Sales.SalesOrderHeader
WHERE (OrderDate = '2014-06-29' AND SalesOrderID < 75074)
   OR (OrderDate < '2014-06-29')
ORDER BY OrderDate DESC, SalesOrderID DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Αυτή η επιλογή θα λειτουργήσει σωστά, αλλά γενικά θα είναι δύσκολο να βελτιστοποιηθεί καθώς η συνθήκη περιέχει έναν τελεστή OR. Εάν η τιμή του πρωτεύοντος κλειδιού αυξάνεται καθώς αυξάνεται η OrderDate, τότε η συνθήκη μπορεί να απλοποιηθεί αφήνοντας μόνο ένα φίλτρο από το SalesOrderID. Αλλά εάν δεν υπάρχει αυστηρή συσχέτιση μεταξύ των τιμών του πρωτεύοντος κλειδιού και του πεδίου βάσει του οποίου ταξινομείται το αποτέλεσμα, αυτό το OR δεν μπορεί να αποφευχθεί στα περισσότερα DBMS. Μια εξαίρεση που γνωρίζω είναι η PostgreSQL, η οποία υποστηρίζει πλήρως τη σύγκριση πολλαπλών και η παραπάνω συνθήκη μπορεί να γραφτεί ως "WHERE (OrderDate, SalesOrderID) < ('2014-06-29', 75074)". Δεδομένου ενός σύνθετου κλειδιού με αυτά τα δύο πεδία, ένα ερώτημα όπως αυτό θα πρέπει να είναι αρκετά εύκολο.

Μια δεύτερη εναλλακτική προσέγγιση μπορεί να βρεθεί, για παράδειγμα, στο API κύλισης ElasticSearch ή Cosmos DB — όταν ένα αίτημα, εκτός από δεδομένα, επιστρέφει ένα ειδικό αναγνωριστικό με το οποίο μπορείτε να λάβετε το επόμενο τμήμα δεδομένων. Εάν αυτό το αναγνωριστικό έχει απεριόριστη διάρκεια ζωής (όπως στο Comsos DB), τότε αυτός είναι ένας πολύ καλός τρόπος για την υλοποίηση σελιδοποίησης με διαδοχική μετάβαση μεταξύ σελίδων (επιλογή #2 που αναφέρθηκε παραπάνω). Πιθανά μειονεκτήματα: δεν υποστηρίζεται σε όλα τα DBMS. το αναγνωριστικό επόμενου τμήματος που προκύπτει μπορεί να έχει περιορισμένη διάρκεια ζωής, η οποία γενικά δεν είναι κατάλληλη για την υλοποίηση της αλληλεπίδρασης με τον χρήστη (όπως το API κύλισης ElasticSearch).

Σύνθετο φιλτράρισμα

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

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

Για παράδειγμα, εάν επιλέξουμε την κατηγορία Ποδήλατα και το χρώμα Μαύρο σε αυτό το παράδειγμα, ο πίνακας θα εμφανίζει μόνο μαύρα ποδήλατα, αλλά:

  • Για κάθε κριτήριο στην ομάδα Κατηγορίες, ο αριθμός των προϊόντων αυτής της κατηγορίας θα εμφανίζεται με μαύρο χρώμα.
  • Για κάθε κριτήριο της ομάδας "Χρώματα", θα εμφανίζεται ο αριθμός των ποδηλάτων αυτού του χρώματος.

Ακολουθεί ένα παράδειγμα του αποτελέσματος εξόδου για τέτοιες συνθήκες:

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

Τώρα ας φανταστούμε πώς αυτό μπορεί να εφαρμοστεί σε σχεσιακή βάση. Κάθε ομάδα κριτηρίων, όπως Κατηγορία και Χρώμα, θα απαιτεί ξεχωριστό ερώτημα:

SELECT pc.ProductCategoryID, pc.Name, COUNT(1) FROM Production.Product p
  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
  INNER JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID
WHERE p.Color = 'Black'
GROUP BY pc.ProductCategoryID, pc.Name
ORDER BY COUNT(1) DESC

Ζητήματα απόδοσης και απόδοσης αποτελεσμάτων αναζήτησης

SELECT Color, COUNT(1) FROM Production.Product p
  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
WHERE ps.ProductCategoryID = 1 --Bikes
GROUP BY Color
ORDER BY COUNT(1) DESC

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

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

  • Συνδυάστε όλες τις μετρήσεις ποσοτήτων σε ένα ερώτημα. Τεχνικά αυτό είναι δυνατό χρησιμοποιώντας τη λέξη-κλειδί UNION, αλλά δεν θα βοηθήσει ιδιαίτερα την απόδοση - η βάση δεδομένων θα πρέπει να εκτελέσει κάθε ένα από τα τμήματα από την αρχή.
  • Ποσότητες κρυφής μνήμης. Αυτό μου προτείνεται σχεδόν κάθε φορά που περιγράφω ένα πρόβλημα. Η προειδοποίηση είναι ότι αυτό είναι γενικά αδύνατο. Ας πούμε ότι έχουμε 10 «όψεις», καθεμία από τις οποίες έχει 5 τιμές. Αυτή είναι μια πολύ «μέτρια» κατάσταση σε σύγκριση με αυτό που μπορεί να δει κανείς στα ίδια ηλεκτρονικά καταστήματα. Η επιλογή ενός στοιχείου πτυχής επηρεάζει τις ποσότητες σε άλλα 9, με άλλα λόγια, για κάθε συνδυασμό κριτηρίων οι ποσότητες μπορεί να είναι διαφορετικές. Στο παράδειγμά μας, υπάρχουν συνολικά 50 κριτήρια που μπορεί να επιλέξει ο χρήστης, επομένως θα υπάρχουν 250 πιθανοί συνδυασμοί. Δεν υπάρχει αρκετή μνήμη ή χρόνος για να συμπληρώσετε μια τέτοια σειρά δεδομένων. Εδώ μπορείτε να αντιταχθείτε και να πείτε ότι δεν είναι όλοι οι συνδυασμοί πραγματικοί και ο χρήστης σπάνια επιλέγει περισσότερα από 5-10 κριτήρια. Ναι, είναι δυνατό να γίνει lazy loading και cache μια ποσότητα μόνο από αυτά που έχουν επιλεγεί ποτέ, αλλά όσο περισσότερες επιλογές υπάρχουν, τόσο λιγότερο αποτελεσματική θα είναι μια τέτοια κρυφή μνήμη και τόσο πιο αισθητά θα είναι τα προβλήματα χρόνου απόκρισης (ειδικά αν το σύνολο δεδομένων αλλάζει τακτικά) .

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

  • Καλέστε έναν πλήρη επανυπολογισμό των «όψεων» όσο πιο σπάνια γίνεται. Για παράδειγμα, μην υπολογίζετε ξανά τα πάντα κάθε φορά που αλλάζουν τα κριτήρια αναζήτησης, αλλά αντίθετα βρείτε τον συνολικό αριθμό αποτελεσμάτων που ταιριάζουν με τις τρέχουσες συνθήκες και ζητήστε από τον χρήστη να τα εμφανίσει - "Βρέθηκαν 1425 εγγραφές, εμφανίζονται;" Ο χρήστης μπορεί είτε να συνεχίσει να αλλάζει τους όρους αναζήτησης είτε να κάνει κλικ στο κουμπί "εμφάνιση". Μόνο στη δεύτερη περίπτωση θα εκτελεστούν όλα τα αιτήματα για τη λήψη αποτελεσμάτων και τον εκ νέου υπολογισμό των ποσοτήτων σε όλες τις «πλευρές». Σε αυτή την περίπτωση, όπως μπορείτε εύκολα να δείτε, θα πρέπει να αντιμετωπίσετε ένα αίτημα για να λάβετε τον συνολικό αριθμό των αποτελεσμάτων και τη βελτιστοποίησή του. Αυτή η μέθοδος μπορεί να βρεθεί σε πολλά μικρά ηλεκτρονικά καταστήματα. Προφανώς, αυτό δεν είναι πανάκεια για αυτό το πρόβλημα, αλλά σε απλές περιπτώσεις μπορεί να είναι ένας καλός συμβιβασμός.
  • Χρησιμοποιήστε μηχανές αναζήτησης για να βρείτε αποτελέσματα και να μετρήσετε πτυχές, όπως Solr, ElasticSearch, Sphinx και άλλες. Όλα έχουν σχεδιαστεί για να δημιουργούν «όψεις» και το κάνουν αρκετά αποτελεσματικά λόγω του ανεστραμμένου δείκτη. Πώς λειτουργούν οι μηχανές αναζήτησης, γιατί σε τέτοιες περιπτώσεις είναι πιο αποτελεσματικές από βάσεις δεδομένων γενικού σκοπού, ποιες πρακτικές και παγίδες υπάρχουν - αυτό είναι ένα θέμα για ξεχωριστό άρθρο. Εδώ θα ήθελα να επιστήσω την προσοχή σας στο γεγονός ότι η μηχανή αναζήτησης δεν μπορεί να αντικαταστήσει την κύρια αποθήκευση δεδομένων· χρησιμοποιείται ως προσθήκη: τυχόν αλλαγές στην κύρια βάση δεδομένων που σχετίζονται με την αναζήτηση συγχρονίζονται στο ευρετήριο αναζήτησης. Η μηχανή αναζήτησης συνήθως αλληλεπιδρά μόνο με τη μηχανή αναζήτησης και δεν έχει πρόσβαση στην κύρια βάση δεδομένων. Ένα από τα πιο σημαντικά σημεία εδώ είναι πώς να οργανώσετε αυτόν τον συγχρονισμό αξιόπιστα. Όλα εξαρτώνται από τις απαιτήσεις «χρόνου αντίδρασης». Εάν ο χρόνος μεταξύ μιας αλλαγής στην κύρια βάση δεδομένων και της «εκδήλωσής» της στην αναζήτηση δεν είναι κρίσιμος, μπορείτε να δημιουργήσετε μια υπηρεσία που αναζητά τις πρόσφατα αλλαγμένες εγγραφές κάθε λίγα λεπτά και τις ευρετηριάζει. Εάν θέλετε τον συντομότερο δυνατό χρόνο απόκρισης, μπορείτε να εφαρμόσετε κάτι σαν εξερχόμενα συναλλαγών για αποστολή ενημερώσεων στην υπηρεσία αναζήτησης.

Ευρήματα

  1. Η εφαρμογή σελιδοποίησης από την πλευρά του διακομιστή είναι μια σημαντική περιπλοκή και έχει νόημα μόνο για ταχέως αναπτυσσόμενα ή απλά μεγάλα σύνολα δεδομένων. Δεν υπάρχει απολύτως ακριβής συνταγή για τον τρόπο αξιολόγησης του «μεγάλου» ή του «ταχείας ανάπτυξης», αλλά θα ακολουθούσα την εξής προσέγγιση:
    • Εάν η λήψη μιας πλήρους συλλογής δεδομένων, λαμβάνοντας υπόψη τον χρόνο διακομιστή και τη μετάδοση του δικτύου, ανταποκρίνεται κανονικά στις απαιτήσεις απόδοσης, δεν έχει νόημα η εφαρμογή σελιδοποίησης από την πλευρά του διακομιστή.
    • Μπορεί να υπάρξει μια κατάσταση όπου δεν αναμένονται προβλήματα απόδοσης στο εγγύς μέλλον, καθώς υπάρχουν λίγα δεδομένα, αλλά η συλλογή δεδομένων αυξάνεται συνεχώς. Εάν κάποιο σύνολο δεδομένων στο μέλλον ενδέχεται να μην ικανοποιεί πλέον το προηγούμενο σημείο, είναι προτιμότερο να ξεκινήσετε αμέσως τη σελιδοποίηση.
  2. Εάν δεν υπάρχει αυστηρή απαίτηση εκ μέρους της επιχείρησης για εμφάνιση του συνολικού αριθμού αποτελεσμάτων ή εμφάνιση αριθμών σελίδων και το σύστημά σας δεν διαθέτει μηχανή αναζήτησης, είναι προτιμότερο να μην εφαρμόσετε αυτά τα σημεία και να εξετάσετε την επιλογή #2.
  3. Εάν υπάρχει σαφής απαίτηση για πολύπλευρη αναζήτηση, έχετε δύο επιλογές χωρίς να θυσιάσετε την απόδοση:
    • Μην υπολογίζετε ξανά όλες τις ποσότητες κάθε φορά που αλλάζουν τα κριτήρια αναζήτησης.
    • Χρησιμοποιήστε μηχανές αναζήτησης όπως Solr, ElasticSearch, Sphinx και άλλες. Πρέπει όμως να γίνει κατανοητό ότι δεν μπορεί να αντικαταστήσει την κύρια βάση δεδομένων και θα πρέπει να χρησιμοποιηθεί ως προσθήκη στην κύρια αποθήκευση για την επίλυση προβλημάτων αναζήτησης.
  4. Επίσης, στην περίπτωση της πολύπλευρης αναζήτησης, είναι λογικό να χωρίσουμε την ανάκτηση της σελίδας αποτελεσμάτων αναζήτησης και την καταμέτρηση σε δύο παράλληλες αιτήσεις. Η καταμέτρηση των ποσοτήτων μπορεί να διαρκέσει περισσότερο από τη λήψη αποτελεσμάτων, ενώ τα αποτελέσματα είναι πιο σημαντικά για τον χρήστη.
  5. Εάν χρησιμοποιείτε μια βάση δεδομένων SQL για αναζήτηση, οποιαδήποτε αλλαγή κώδικα που σχετίζεται με αυτό το τμήμα θα πρέπει να ελεγχθεί καλά για απόδοση στον κατάλληλο όγκο δεδομένων (που υπερβαίνει τον όγκο στη ζωντανή βάση δεδομένων). Συνιστάται επίσης να χρησιμοποιείτε παρακολούθηση του χρόνου εκτέλεσης ερωτημάτων σε όλες τις παρουσίες της βάσης δεδομένων, και ειδικά στη «ζωντανή». Ακόμα κι αν όλα ήταν καλά με τα σχέδια ερωτημάτων στο στάδιο ανάπτυξης, καθώς ο όγκος των δεδομένων αυξάνεται, η κατάσταση μπορεί να αλλάξει αισθητά.

Πηγή: www.habr.com

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