Αναπαραγωγή υψηλού επιπέδου στο Tarantool DBMS

Γεια σας, δημιουργώ εφαρμογές για DBMS Tarantool είναι μια πλατφόρμα που αναπτύχθηκε από τον Όμιλο Mail.ru που συνδυάζει ένα DBMS υψηλής απόδοσης και έναν διακομιστή εφαρμογών στη γλώσσα Lua. Η υψηλή ταχύτητα των λύσεων που βασίζονται στο Tarantool επιτυγχάνεται, ειδικότερα, χάρη στην υποστήριξη της λειτουργίας μνήμης του DBMS και της δυνατότητας εκτέλεσης επιχειρηματικής λογικής εφαρμογής σε έναν ενιαίο χώρο διευθύνσεων με δεδομένα. Ταυτόχρονα, διασφαλίζεται η διατήρηση των δεδομένων χρησιμοποιώντας συναλλαγές ACID (διατηρείται ένα αρχείο καταγραφής WAL στο δίσκο). Το Tarantool έχει ενσωματωμένη υποστήριξη για αναπαραγωγή και διαμοιρασμό. Ξεκινώντας από την έκδοση 2.1, υποστηρίζονται ερωτήματα στη γλώσσα SQL. Το Tarantool είναι ανοιχτού κώδικα και διαθέτει άδεια χρήσης με την άδεια Simplified BSD. Υπάρχει επίσης μια εμπορική έκδοση Enterprise.

Αναπαραγωγή υψηλού επιπέδου στο Tarantool DBMS
Νιώσε την δύναμη! (…αλλιώς απολαύστε την παράσταση)

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

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

Αναπαραγωγή υψηλού επιπέδου στο Tarantool DBMS
Ρύζι. 1. Αναπαραγωγή σε ένα σύμπλεγμα

Ένα παράδειγμα εναλλακτικού σεναρίου θα ήταν η μεταφορά δεδομένων που δημιουργούνται σε μια βάση δεδομένων σε μια άλλη βάση δεδομένων για επεξεργασία/παρακολούθηση. Στην τελευταία περίπτωση, μια πιο βολική λύση μπορεί να είναι η χρήση υψηλό επίπεδο αναπαραγωγή - αναπαραγωγή δεδομένων σε επίπεδο επιχειρηματικής λογικής εφαρμογής. Εκείνοι. Δεν χρησιμοποιούμε έτοιμη λύση ενσωματωμένη στο DBMS, αλλά υλοποιούμε μόνοι μας την αναπαραγωγή εντός της εφαρμογής που αναπτύσσουμε. Αυτή η προσέγγιση έχει τόσο πλεονεκτήματα όσο και μειονεκτήματα. Ας απαριθμήσουμε τα πλεονεκτήματα.

1. Εξοικονόμηση κίνησης:

  • Δεν μπορείτε να μεταφέρετε όλα τα δεδομένα, αλλά μόνο ένα μέρος τους (για παράδειγμα, μπορείτε να μεταφέρετε μόνο ορισμένους πίνακες, μερικές από τις στήλες ή τις εγγραφές τους που πληρούν ένα συγκεκριμένο κριτήριο).
  • Σε αντίθεση με την αναπαραγωγή χαμηλού επιπέδου, η οποία εκτελείται συνεχώς σε ασύγχρονη (που εφαρμόζεται στην τρέχουσα έκδοση του Tarantool - 1.10) ή σε σύγχρονη (που θα εφαρμοστεί σε επόμενες εκδόσεις του Tarantool), η αναπαραγωγή υψηλού επιπέδου μπορεί να εκτελεστεί σε περιόδους σύνδεσης (δηλ. η εφαρμογή συγχρονίζει πρώτα τα δεδομένα - μια συνεδρία ανταλλαγής δεδομένων, μετά γίνεται μια παύση στην αναπαραγωγή, μετά την οποία πραγματοποιείται η επόμενη συνεδρία ανταλλαγής κ.λπ.).
  • εάν μια εγγραφή έχει αλλάξει πολλές φορές, μπορείτε να μεταφέρετε μόνο την πιο πρόσφατη έκδοσή της (σε αντίθεση με την αναπαραγωγή χαμηλού επιπέδου, στην οποία όλες οι αλλαγές που έγιναν στο master θα αναπαράγονται διαδοχικά στα αντίγραφα).

2. Δεν υπάρχουν δυσκολίες με την υλοποίηση της ανταλλαγής HTTP, η οποία σας επιτρέπει να συγχρονίζετε απομακρυσμένες βάσεις δεδομένων.

Αναπαραγωγή υψηλού επιπέδου στο Tarantool DBMS
Ρύζι. 2. Αντιγραφή μέσω HTTP

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

Αναπαραγωγή υψηλού επιπέδου στο Tarantool DBMS
Ρύζι. 3. Αντιγραφή σε ετερογενή συστήματα

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

Εάν στην περίπτωσή σας τα παραπάνω πλεονεκτήματα είναι ζωτικής σημασίας (ή είναι απαραίτητη προϋπόθεση), τότε είναι λογικό να χρησιμοποιείτε αναπαραγωγή υψηλού επιπέδου. Ας δούμε διάφορους τρόπους για την υλοποίηση της αναπαραγωγής δεδομένων υψηλού επιπέδου στο Tarantool DBMS.

Ελαχιστοποίηση της κυκλοφορίας

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

Πώς να ελαχιστοποιήσετε τον όγκο των δεδομένων που μεταφέρονται κατά την αναπαραγωγή υψηλού επιπέδου; Μια απλή λύση θα μπορούσε να είναι η επιλογή δεδομένων κατά ημερομηνία και ώρα. Για να το κάνετε αυτό, μπορείτε να χρησιμοποιήσετε το πεδίο ημερομηνίας-ώρας που υπάρχει ήδη στον πίνακα (εάν υπάρχει). Για παράδειγμα, ένα έγγραφο "παραγγελίας" μπορεί να έχει ένα πεδίο "απαιτούμενος χρόνος εκτέλεσης παραγγελίας" - delivery_time. Το πρόβλημα με αυτήν τη λύση είναι ότι οι τιμές σε αυτό το πεδίο δεν χρειάζεται να είναι με τη σειρά που αντιστοιχεί στη δημιουργία παραγγελιών. Επομένως, δεν μπορούμε να θυμηθούμε τη μέγιστη τιμή πεδίου delivery_time, που μεταδόθηκε κατά την προηγούμενη περίοδο ανταλλαγής και κατά την επόμενη συνεδρία ανταλλαγής επιλέξτε όλες τις εγγραφές με υψηλότερη τιμή πεδίου delivery_time. Ενδέχεται να έχουν προστεθεί εγγραφές με χαμηλότερη τιμή πεδίου μεταξύ των συνεδριών ανταλλαγής delivery_time. Επίσης, η παραγγελία θα μπορούσε να έχει υποστεί αλλαγές, οι οποίες ωστόσο δεν επηρέασαν το γήπεδο delivery_time. Και στις δύο περιπτώσεις, οι αλλαγές δεν θα μεταφερθούν από την πηγή στον προορισμό. Για να λύσουμε αυτά τα προβλήματα, θα χρειαστεί να μεταφέρουμε δεδομένα "επικαλυπτόμενα". Εκείνοι. σε κάθε συνεδρία ανταλλαγής θα μεταφέρουμε όλα τα δεδομένα με την τιμή του πεδίου delivery_time, υπερβαίνοντας κάποιο σημείο στο παρελθόν (για παράδειγμα, N ώρες από την τρέχουσα στιγμή). Ωστόσο, είναι προφανές ότι για μεγάλα συστήματα αυτή η προσέγγιση είναι εξαιρετικά περιττή και μπορεί να μειώσει την εξοικονόμηση κίνησης για την οποία προσπαθούμε. Επιπλέον, ο πίνακας που μεταφέρεται ενδέχεται να μην έχει πεδίο συσχετισμένο με ημερομηνία-ώρα.

Μια άλλη λύση, πιο σύνθετη όσον αφορά την υλοποίηση, είναι η επιβεβαίωση της λήψης δεδομένων. Σε αυτή την περίπτωση, κατά τη διάρκεια κάθε συνεδρίας ανταλλαγής, μεταδίδονται όλα τα δεδομένα, η λήψη των οποίων δεν έχει επιβεβαιωθεί από τον παραλήπτη. Για να το εφαρμόσετε αυτό, θα χρειαστεί να προσθέσετε μια στήλη Boolean στον πίνακα προέλευσης (για παράδειγμα, is_transferred). Εάν ο δέκτης επιβεβαιώσει τη λήψη της εγγραφής, το αντίστοιχο πεδίο παίρνει την τιμή true, μετά την οποία η είσοδος δεν εμπλέκεται πλέον σε ανταλλαγές. Αυτή η επιλογή υλοποίησης έχει τα ακόλουθα μειονεκτήματα. Πρώτον, για κάθε εγγραφή που μεταφέρεται, πρέπει να δημιουργείται και να αποστέλλεται μια επιβεβαίωση. Σε γενικές γραμμές, αυτό θα μπορούσε να είναι συγκρίσιμο με τον διπλασιασμό του όγκου των δεδομένων που μεταφέρονται και να οδηγήσει σε διπλασιασμό του αριθμού των μετακινήσεων μετ' επιστροφής. Δεύτερον, δεν υπάρχει δυνατότητα αποστολής της ίδιας εγγραφής σε πολλούς δέκτες (ο πρώτος παραλήπτης που θα λάβει θα επιβεβαιώσει την παραλαβή για τον εαυτό του και για όλους τους άλλους).

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

Μπορείτε να βελτιώσετε την αποτελεσματικότητα της μεταφοράς δεδομένων βελτιώνοντας ελαφρώς την προηγούμενη προσέγγιση. Για να γίνει αυτό, θα χρησιμοποιήσουμε τον τύπο ακέραιου αριθμού (μακρός ακέραιος) ως τιμές πεδίου στήλης για την παρακολούθηση αλλαγών. Ας ονομάσουμε τη στήλη row_ver. Η τιμή πεδίου αυτής της στήλης πρέπει να ρυθμίζεται/ενημερώνεται κάθε φορά που δημιουργείται/τροποποιείται μια εγγραφή. Αλλά σε αυτήν την περίπτωση, στο πεδίο δεν θα εκχωρηθεί η τρέχουσα ημερομηνία-ώρα, αλλά η τιμή κάποιου μετρητή, αυξημένη κατά ένα. Ως αποτέλεσμα, η στήλη row_ver θα περιέχει μοναδικές τιμές και μπορεί να χρησιμοποιηθεί όχι μόνο για την εμφάνιση δεδομένων «δέλτα» (δεδομένα που προστέθηκαν/αλλάχθηκαν από το τέλος της προηγούμενης συνεδρίας ανταλλαγής), αλλά και για την απλή και αποτελεσματική κατανομή τους σε σελίδες.

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

Διαβίβαση δεδομένων με χρήση μετρητή εκδόσεων σειρών

Υλοποίηση του διακομιστή/κύριο τμήματος

Στον MS SQL Server, υπάρχει ένας ειδικός τύπος στήλης για την υλοποίηση αυτής της προσέγγισης - rowversion. Κάθε βάση δεδομένων έχει έναν μετρητή που αυξάνεται κατά ένα κάθε φορά που μια εγγραφή προστίθεται/αλλάσσεται σε έναν πίνακα που έχει μια στήλη όπως rowversion. Η τιμή αυτού του μετρητή εκχωρείται αυτόματα στο πεδίο αυτής της στήλης στην εγγραφή που προστέθηκε/αλλάχθηκε. Το Tarantool DBMS δεν έχει παρόμοιο ενσωματωμένο μηχανισμό. Ωστόσο, στο Tarantool δεν είναι δύσκολο να το εφαρμόσετε χειροκίνητα. Ας δούμε πώς γίνεται αυτό.

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

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

box.cfg{}

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

Ας δημιουργήσουμε μια ακολουθία row_version:

box.schema.sequence.create('row_version',
    { if_not_exists = true })

Επιλογή if_not_exists επιτρέπει στο σενάριο δημιουργίας να εκτελεστεί πολλές φορές: εάν το αντικείμενο υπάρχει, το Tarantool δεν θα προσπαθήσει να το δημιουργήσει ξανά. Αυτή η επιλογή θα χρησιμοποιηθεί σε όλες τις επόμενες εντολές DDL.

Ας δημιουργήσουμε ένα χώρο ως παράδειγμα.

box.schema.space.create('goods', {
    format = {
        {
            name = 'id',
            type = 'unsigned'

        },
        {
            name = 'name',
            type = 'string'

        },
        {
            name = 'code',
            type = 'unsigned'

        },
        {
            name = 'row_ver',
            type = 'unsigned'

        }
    },
    if_not_exists = true
})

Εδώ ορίζουμε το όνομα του χώρου (goods), τα ονόματα των πεδίων και οι τύποι τους.

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

box.schema.sequence.create('goods_id',
    { if_not_exists = true })
box.space.goods:create_index('primary', {
    parts = { 'id' },
    sequence = 'goods_id',
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

Το Tarantool υποστηρίζει διάφορους τύπους ευρετηρίων. Τα ευρετήρια που χρησιμοποιούνται πιο συχνά είναι οι τύποι TREE και HASH, οι οποίοι βασίζονται σε δομές που αντιστοιχούν στο όνομα. Το ΔΕΝΤΡΟ είναι ο πιο ευέλικτος τύπος ευρετηρίου. Σας επιτρέπει να ανακτάτε δεδομένα με οργανωμένο τρόπο. Αλλά για την επιλογή ισότητας, το HASH είναι πιο κατάλληλο. Αντίστοιχα, συνιστάται η χρήση HASH για το πρωτεύον κλειδί (που είναι αυτό που κάναμε).

Για να χρησιμοποιήσετε τη στήλη row_ver για να μεταφέρετε αλλαγμένα δεδομένα, πρέπει να δεσμεύσετε τιμές ακολουθίας στα πεδία αυτής της στήλης row_ver. Αλλά σε αντίθεση με το πρωτεύον κλειδί, την τιμή του πεδίου στήλης row_ver θα πρέπει να αυξάνεται κατά ένα όχι μόνο κατά την προσθήκη νέων εγγραφών, αλλά και κατά την αλλαγή των υπαρχουσών. Μπορείτε να χρησιμοποιήσετε έναυσμα για αυτό. Το Tarantool έχει δύο τύπους ενεργοποίησης χώρου: before_replace и on_replace. Οι σκανδαλισμοί ενεργοποιούνται κάθε φορά που αλλάζουν τα δεδομένα στο χώρο (για κάθε πλειάδα που επηρεάζεται από τις αλλαγές, εκκινείται μια λειτουργία ενεργοποίησης). Διαφορετικός on_replace, before_replace-τα triggers σας επιτρέπουν να τροποποιήσετε τα δεδομένα της πλειάδας για την οποία εκτελείται η ενεργοποίηση. Αντίστοιχα, μας ταιριάζει ο τελευταίος τύπος σκανδάλης.

box.space.goods:before_replace(function(old, new)
    return box.tuple.new({new[1], new[2], new[3],
        box.sequence.row_version:next()})
end)

Η ακόλουθη ενεργοποίηση αντικαθιστά την τιμή πεδίου row_ver αποθηκευμένη πλειάδα στην επόμενη τιμή της ακολουθίας row_version.

Για να μπορέσουμε να εξάγουμε δεδομένα από το διάστημα goods κατά στήλη row_ver, ας δημιουργήσουμε ένα ευρετήριο:

box.space.goods:create_index('row_ver', {
    parts = { 'row_ver' },
    unique = true,
    type = 'TREE',
    if_not_exists = true
})

Τύπος ευρετηρίου - δέντρο (TREE), επειδή θα χρειαστεί να εξαγάγουμε τα δεδομένα με αύξουσα σειρά των τιμών στη στήλη row_ver.

Ας προσθέσουμε μερικά δεδομένα στο χώρο:

box.space.goods:insert{nil, 'pen', 123}
box.space.goods:insert{nil, 'pencil', 321}
box.space.goods:insert{nil, 'brush', 100}
box.space.goods:insert{nil, 'watercolour', 456}
box.space.goods:insert{nil, 'album', 101}
box.space.goods:insert{nil, 'notebook', 800}
box.space.goods:insert{nil, 'rubber', 531}
box.space.goods:insert{nil, 'ruler', 135}

Επειδή Το πρώτο πεδίο είναι ένας μετρητής αυτόματης αύξησης, αντ' αυτού περνάμε το μηδέν. Το Tarantool θα αντικαταστήσει αυτόματα την επόμενη τιμή. Ομοίως, ως η τιμή των πεδίων στήλης row_ver μπορείτε να περάσετε το μηδέν - ή να μην καθορίσετε καθόλου την τιμή, επειδή αυτή η στήλη καταλαμβάνει την τελευταία θέση στο χώρο.

Ας ελέγξουμε το αποτέλεσμα εισαγωγής:

tarantool> box.space.goods:select()
---
- - [1, 'pen', 123, 1]
  - [2, 'pencil', 321, 2]
  - [3, 'brush', 100, 3]
  - [4, 'watercolour', 456, 4]
  - [5, 'album', 101, 5]
  - [6, 'notebook', 800, 6]
  - [7, 'rubber', 531, 7]
  - [8, 'ruler', 135, 8]
...

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

local page_size = 5
local function get_goods(row_ver)
    local index = box.space.goods.index.row_ver
    local goods = {}
    local counter = 0
    for _, tuple in index:pairs(row_ver, {
        iterator = 'GT' }) do
        local obj = tuple:tomap({ names_only = true })
        table.insert(goods, obj)
        counter = counter + 1
        if counter >= page_size then
            break
        end
    end
    return goods
end

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

Η δειγματοληψία δεδομένων στο Tarantool γίνεται μέσω ευρετηρίων. Λειτουργία get_goods χρησιμοποιεί έναν επαναλήπτη ανά ευρετήριο row_ver για λήψη αλλαγμένων δεδομένων. Ο τύπος Iterator είναι GT (Μεγαλύτερο από, μεγαλύτερο από). Αυτό σημαίνει ότι ο επαναλήπτης θα διασχίσει διαδοχικά τις τιμές ευρετηρίου ξεκινώντας από το κλειδί που πέρασε (τιμή πεδίου row_ver).

Ο επαναλήπτης επιστρέφει πλειάδες. Για να μπορέσετε στη συνέχεια να μεταφέρετε δεδομένα μέσω HTTP, είναι απαραίτητο να μετατρέψετε τις πλειάδες σε μια δομή κατάλληλη για επακόλουθη σειριοποίηση. Το παράδειγμα χρησιμοποιεί την τυπική συνάρτηση για αυτό tomap. Αντί να χρησιμοποιείτε tomap μπορείτε να γράψετε τη δική σας συνάρτηση. Για παράδειγμα, μπορεί να θέλουμε να μετονομάσουμε ένα πεδίο name, μην περάσεις το γήπεδο code και προσθέστε ένα πεδίο comment:

local function unflatten_goods(tuple)
    local obj = {}
    obj.id = tuple.id
    obj.goods_name = tuple.name
    obj.comment = 'some comment'
    obj.row_ver = tuple.row_ver
    return obj
end

Το μέγεθος σελίδας των δεδομένων εξόδου (ο αριθμός των εγγραφών σε ένα τμήμα) καθορίζεται από τη μεταβλητή page_size. Στο παράδειγμα η τιμή page_size είναι 5. Σε ένα πραγματικό πρόγραμμα, το μέγεθος της σελίδας έχει συνήθως μεγαλύτερη σημασία. Εξαρτάται από το μέσο μέγεθος της διαστημικής πλειάδας. Το βέλτιστο μέγεθος σελίδας μπορεί να προσδιοριστεί εμπειρικά μετρώντας το χρόνο μεταφοράς δεδομένων. Όσο μεγαλύτερο είναι το μέγεθος της σελίδας, τόσο μικρότερος είναι ο αριθμός των μετακινήσεων μετ' επιστροφής μεταξύ της πλευράς αποστολής και λήψης. Με αυτόν τον τρόπο μπορείτε να μειώσετε το συνολικό χρόνο λήψης αλλαγών. Ωστόσο, εάν το μέγεθος της σελίδας είναι πολύ μεγάλο, θα δαπανήσουμε πολύ χρόνο στον διακομιστή για τη σειριοποίηση του δείγματος. Ως αποτέλεσμα, ενδέχεται να υπάρξουν καθυστερήσεις στην επεξεργασία άλλων αιτημάτων που έρχονται στον διακομιστή. Παράμετρος page_size μπορεί να φορτωθεί από το αρχείο διαμόρφωσης. Για κάθε μεταδιδόμενο χώρο, μπορείτε να ορίσετε τη δική του τιμή. Ωστόσο, για τους περισσότερους χώρους η προεπιλεγμένη τιμή (για παράδειγμα, 100) μπορεί να είναι κατάλληλη.

Ας εκτελέσουμε τη συνάρτηση get_goods:

tarantool> get_goods(0)

---
- - row_ver: 1
    code: 123
    name: pen
    id: 1
  - row_ver: 2
    code: 321
    name: pencil
    id: 2
  - row_ver: 3
    code: 100
    name: brush
    id: 3
  - row_ver: 4
    code: 456
    name: watercolour
    id: 4
  - row_ver: 5
    code: 101
    name: album
    id: 5
...

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

tarantool> get_goods(5)

---
- - row_ver: 6
    code: 800
    name: notebook
    id: 6
  - row_ver: 7
    code: 531
    name: rubber
    id: 7
  - row_ver: 8
    code: 135
    name: ruler
    id: 8
...

Αλλη μια φορά:

tarantool> get_goods(8)
---
- []
...

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

Ας κάνουμε αλλαγές στο χώρο:

box.space.goods:update(4, {{'=', 6, 'copybook'}})
box.space.goods:insert{nil, 'clip', 234}
box.space.goods:insert{nil, 'folder', 432}

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

Ας επαναλάβουμε την τελευταία κλήση συνάρτησης:

tarantool> get_goods(8)
---



- - row_ver: 9
    code: 800
    name: copybook
    id: 6
  - row_ver: 10
    code: 234
    name: clip
    id: 9
  - row_ver: 11
    code: 432
    name: folder
    id: 10
...

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

Θα αφήσουμε την έκδοση αποτελεσμάτων μέσω HTTP με τη μορφή JSON εκτός του πεδίου εφαρμογής αυτού του άρθρου. Μπορείτε να διαβάσετε για αυτό εδώ: https://habr.com/ru/company/mailru/blog/272141/

Υλοποίηση του τμήματος πελάτη/σκλάβου

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

box.schema.space.create('goods', {
    format = {
        {
            name = 'id',
            type = 'unsigned'

        },
        {
            name = 'name',
            type = 'string'

        },
        {
            name = 'code',
            type = 'unsigned'

        }
    },
    if_not_exists = true
})

box.space.goods:create_index('primary', {
    parts = { 'id' },
    sequence = 'goods_id',
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

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

Επιπλέον, χρειαζόμαστε χώρο για αποθήκευση τιμών row_ver:

box.schema.space.create('row_ver', {
    format = {
        {
            name = 'space_name',
            type = 'string'

        },
        {
            name = 'value',
            type = 'string'

        }
    },
    if_not_exists = true
})

box.space.row_ver:create_index('primary', {
    parts = { 'space_name' },
    unique = true,
    type = 'HASH',
    if_not_exists = true
})

Για κάθε φορτωμένο χώρο (πεδίο space_name) θα αποθηκεύσουμε την τελευταία τιμή που φορτώθηκε εδώ row_ver (πεδίο value). Η στήλη λειτουργεί ως πρωτεύον κλειδί space_name.

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

local http_client = require('http.client').new()

Χρειαζόμαστε επίσης μια βιβλιοθήκη για json deserialization:

local json = require('json')

Αυτό είναι αρκετό για να δημιουργήσετε μια συνάρτηση φόρτωσης δεδομένων:

local function load_data(url, row_ver)
    local url = ('%s?rowVer=%s'):format(url,
        tostring(row_ver))
    local body = nil
    local data = http_client:request('GET', url, body, {
        keepalive_idle =  1,
        keepalive_interval = 1
    })
    return json.decode(data.body)
end

Η συνάρτηση εκτελεί ένα αίτημα HTTP στη διεύθυνση url και το στέλνει row_ver ως παράμετρος και επιστρέφει το αποσημειωμένο αποτέλεσμα του αιτήματος.

Η λειτουργία για την αποθήκευση των ληφθέντων δεδομένων μοιάζει με αυτό:

local function save_goods(goods)
    local n = #goods
    box.atomic(function()
        for i = 1, n do
            local obj = goods[i]
            box.space.goods:put(
                obj.id, obj.name, obj.code)
        end
    end)
end

Κύκλος αποθήκευσης δεδομένων στο διάστημα goods τοποθετείται σε μια συναλλαγή (η συνάρτηση χρησιμοποιείται για αυτό box.atomic) για να μειώσετε τον αριθμό των λειτουργιών του δίσκου.

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

local function sync_goods()
    local tuple = box.space.row_ver:get('goods')
    local row_ver = tuple and tuple.value or 0

    —— set your url here:
    local url = 'http://127.0.0.1:81/test/goods/list'

    while true do
        local goods = load_goods(url, row_ver)

        local count = #goods
        if count == 0 then
            return
        end

        save_goods(goods)

        row_ver = goods[count].rowVer
        box.space.row_ver:put({'goods', row_ver})
    end
end

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

Για προστασία από τυχαίο βρόχο (σε περίπτωση σφάλματος στο πρόγραμμα), ο βρόχος while μπορεί να αντικατασταθεί από for:

for _ = 1, max_req do ...

Ως αποτέλεσμα της εκτέλεσης της συνάρτησης sync_goods χώρος goods ο δέκτης θα περιέχει τις πιο πρόσφατες εκδόσεις όλων των διαστημικών εγγραφών goods στην πηγή.

Προφανώς, η διαγραφή δεδομένων δεν μπορεί να μεταδοθεί με αυτόν τον τρόπο. Εάν υπάρχει τέτοια ανάγκη, μπορείτε να χρησιμοποιήσετε ένα σημάδι διαγραφής. Προσθήκη στο διάστημα goods boolean πεδίο is_deleted και αντί να διαγράψουμε φυσικά μια εγγραφή, χρησιμοποιούμε λογική διαγραφή - ορίζουμε την τιμή του πεδίου is_deleted σε νόημα true. Μερικές φορές αντί για ένα πεδίο boolean is_deleted είναι πιο βολικό να χρησιμοποιήσετε το πεδίο deleted, το οποίο αποθηκεύει την ημερομηνία-ώρα της λογικής διαγραφής της εγγραφής. Μετά την εκτέλεση μιας λογικής διαγραφής, η εγγραφή που έχει επισημανθεί για διαγραφή θα μεταφερθεί από την πηγή στον προορισμό (σύμφωνα με τη λογική που συζητήθηκε παραπάνω).

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

Εξετάσαμε έναν αποτελεσματικό τρόπο αναπαραγωγής δεδομένων υψηλού επιπέδου σε εφαρμογές που χρησιμοποιούν το Tarantool DBMS.

Ευρήματα

  1. Το Tarantool DBMS είναι ένα ελκυστικό, πολλά υποσχόμενο προϊόν για τη δημιουργία εφαρμογών υψηλού φορτίου.
  2. Η αναπαραγωγή δεδομένων υψηλού επιπέδου έχει πολλά πλεονεκτήματα έναντι της αναπαραγωγής χαμηλού επιπέδου.
  3. Η μέθοδος αναπαραγωγής υψηλού επιπέδου που συζητείται στο άρθρο σάς επιτρέπει να ελαχιστοποιήσετε τον όγκο των μεταφερόμενων δεδομένων μεταφέροντας μόνο εκείνες τις εγγραφές που έχουν αλλάξει από την τελευταία περίοδο ανταλλαγής.

Πηγή: www.habr.com

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