Πριν λίγο καιρό βρεθήκαμε αντιμέτωποι με το πρόβλημα του καθαρισμού πλειάδων σε χώρους
Ένα καλό παράδειγμα για εμάς ήταν η ενότητα tarantool που ονομάζεται
Περιγραφή
Η τεκμηρίωση για το tarantool έχει ένα πολύ καλό
Ας ξεκινήσουμε από μακριά και ας δούμε πώς μοιάζει εξωτερικά μια μονάδα που έχει λήξει με ανώτατο όριο:
fiber = require('fiber')
net_box = require('net.box')
box.cfg{listen = 3300}
box.schema.func.create('libcapped-expirationd.start', {language = 'C'})
box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.start')
box.schema.func.create('libcapped-expirationd.kill', {language = 'C'})
box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.kill')
box.schema.space.create('tester')
box.space.tester:create_index('primary', {unique = true, parts = {1, 'unsigned'}})
capped_connection = net_box:new(3300)
Για απλότητα, εκκινούμε το tarantool στον κατάλογο όπου βρίσκεται η βιβλιοθήκη μας libcapped-expirationd.so. Δύο λειτουργίες εξάγονται από τη βιβλιοθήκη: start και kill. Το πρώτο βήμα είναι να καταστήσετε αυτές τις λειτουργίες διαθέσιμες από τη Lua χρησιμοποιώντας box.schema.func.create και box.schema.user.grant. Στη συνέχεια, δημιουργήστε ένα χώρο του οποίου οι πλειάδες θα περιέχουν μόνο τρία πεδία: το πρώτο είναι ένα μοναδικό αναγνωριστικό, το δεύτερο είναι το email και το τρίτο είναι η διάρκεια ζωής της πλειάδας. Δημιουργούμε ένα δέντρο ευρετήριο πάνω από το πρώτο πεδίο και το ονομάζουμε πρωτεύον. Στη συνέχεια παίρνουμε το αντικείμενο σύνδεσης στην εγγενή βιβλιοθήκη μας.
Μετά τις προπαρασκευαστικές εργασίες, εκτελέστε τη λειτουργία εκκίνησης:
capped_connection:call('libcapped-expirationd.start', {'non-indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.primary, 3, 1024, 3600})
Αυτό το παράδειγμα θα λειτουργεί κατά τη σάρωση ακριβώς το ίδιο με τη μονάδα που έχει λήξει, η οποία είναι γραμμένη σε Lua. Το πρώτο όρισμα στη συνάρτηση έναρξης είναι το μοναδικό όνομα της εργασίας. Το δεύτερο είναι το αναγνωριστικό χώρου. Το τρίτο είναι ένα μοναδικό ευρετήριο με το οποίο θα διαγράφονται οι πλειάδες. Το τέταρτο είναι ο δείκτης με τον οποίο θα διασχιστούν οι πλειάδες. Το πέμπτο είναι ο αριθμός του πεδίου πλειάδας με διάρκεια ζωής (η αρίθμηση ξεκινά από το 1, όχι από το 0!). Η έκτη και η έβδομη είναι οι ρυθμίσεις σάρωσης. 1024 είναι ο μέγιστος αριθμός πλειάδων που μπορούν να προβληθούν σε μία μόνο συναλλαγή. 3600 — πλήρης χρόνος σάρωσης σε δευτερόλεπτα.
Σημειώστε ότι το παράδειγμα χρησιμοποιεί το ίδιο ευρετήριο για ανίχνευση και διαγραφή. Εάν πρόκειται για δείκτη δέντρου, τότε η διέλευση πραγματοποιείται από το μικρότερο κλειδί στο μεγαλύτερο. Εάν υπάρχει κάποιο άλλο, για παράδειγμα, κατακερματισμένος δείκτης, τότε η διέλευση πραγματοποιείται, κατά κανόνα, με τυχαία σειρά. Όλες οι διαστημικές πλειάδες σαρώνονται σε μία σάρωση.
Ας εισάγουμε πολλές πλειάδες στο χώρο με διάρκεια ζωής 60 δευτερολέπτων:
box.space.tester:insert{0, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, '[email protected]', math.floor(fiber.time()) + 60}
Ας ελέγξουμε ότι η εισαγωγή ήταν επιτυχής:
tarantool> box.space.tester.index.primary:select()
---
- - [0, '[email protected]', 1576418976]
- [1, '[email protected]', 1576418976]
- [2, '[email protected]', 1576418976]
...
Ας επαναλάβουμε την επιλογή μετά από 60+ δευτερόλεπτα (μετρώντας από την αρχή της εισαγωγής της πρώτης πλειάδας) και ας δούμε ότι η μονάδα που έχει λήξει έχει ήδη επεξεργαστεί:
tarantool> box.space.tester.index.primary:select()
---
- []
...
Ας σταματήσουμε το έργο:
capped_connection:call('libcapped-expirationd.kill', {'non-indexed'})
Ας δούμε ένα δεύτερο παράδειγμα όπου χρησιμοποιείται ξεχωριστό ευρετήριο για την ανίχνευση:
fiber = require('fiber')
net_box = require('net.box')
box.cfg{listen = 3300}
box.schema.func.create('libcapped-expirationd.start', {language = 'C'})
box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.start')
box.schema.func.create('libcapped-expirationd.kill', {language = 'C'})
box.schema.user.grant('guest', 'execute', 'function', 'libcapped-expirationd.kill')
box.schema.space.create('tester')
box.space.tester:create_index('primary', {unique = true, parts = {1, 'unsigned'}})
box.space.tester:create_index('exp', {unique = false, parts = {3, 'unsigned'}})
capped_connection = net_box:new(3300)
Όλα εδώ είναι ίδια όπως στο πρώτο παράδειγμα, με λίγες εξαιρέσεις. Δημιουργούμε ένα δέντρο ευρετήριο πάνω από το τρίτο πεδίο και το ονομάζουμε exp. Αυτό το ευρετήριο δεν χρειάζεται να είναι μοναδικό, σε αντίθεση με το ευρετήριο που ονομάζεται πρωτεύον. Η διέλευση θα πραγματοποιηθεί με το ευρετήριο exp και η διαγραφή από το κύριο. Θυμόμαστε ότι προηγουμένως και τα δύο γίνονταν μόνο χρησιμοποιώντας τον πρωτεύοντα δείκτη.
Μετά την προπαρασκευαστική εργασία, εκτελούμε τη συνάρτηση έναρξης με νέα ορίσματα:
capped_connection:call('libcapped-expirationd.start', {'indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.exp, 3, 1024, 3600})
Ας εισάγουμε ξανά πολλές πλειάδες στο χώρο με διάρκεια ζωής 60 δευτερολέπτων:
box.space.tester:insert{0, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, '[email protected]', math.floor(fiber.time()) + 60}
Μετά από 30 δευτερόλεπτα, κατ' αναλογία, θα προσθέσουμε μερικές ακόμη πλειάδες:
box.space.tester:insert{3, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{4, '[email protected]', math.floor(fiber.time()) + 60}
box.space.tester:insert{5, '[email protected]', math.floor(fiber.time()) + 60}
Ας ελέγξουμε ότι η εισαγωγή ήταν επιτυχής:
tarantool> box.space.tester.index.primary:select()
---
- - [0, '[email protected]', 1576421257]
- [1, '[email protected]', 1576421257]
- [2, '[email protected]', 1576421257]
- [3, '[email protected]', 1576421287]
- [4, '[email protected]', 1576421287]
- [5, '[email protected]', 1576421287]
...
Ας επαναλάβουμε την επιλογή μετά από 60+ δευτερόλεπτα (μετρώντας από την αρχή της εισαγωγής της πρώτης πλειάδας) και ας δούμε ότι η μονάδα που έχει λήξει έχει ήδη επεξεργαστεί:
tarantool> box.space.tester.index.primary:select()
---
- - [3, '[email protected]', 1576421287]
- [4, '[email protected]', 1576421287]
- [5, '[email protected]', 1576421287]
...
Υπάρχουν ακόμα μερικές πλειάδες στο χώρο που θα έχουν περίπου 30 δευτερόλεπτα ακόμα για να ζήσουν. Επιπλέον, η σάρωση σταμάτησε κατά τη μετάβαση από πλειάδα με αναγνωριστικό 2 και διάρκεια ζωής 1576421257 σε πλειάδα με αναγνωριστικό 3 και διάρκεια ζωής 1576421287. Οι πλειάδες με διάρκεια ζωής 1576421287 ή περισσότερο δεν σαρώθηκαν λόγω της παραγγελίας του τα κλειδιά ευρετηρίου exp. Αυτή είναι η εξοικονόμηση που θέλαμε να πετύχουμε από την αρχή.
Ας σταματήσουμε το έργο:
capped_connection:call('libcapped-expirationd.kill', {'indexed'})
Реализация
Ο καλύτερος τρόπος για να πείτε για όλα τα χαρακτηριστικά ενός έργου είναι η αρχική του πηγή.
Τα ορίσματα που μεταβιβάζουμε στη μέθοδο έναρξης αποθηκεύονται σε μια δομή που ονομάζεται expirationd_task:
struct expirationd_task
{
char name[256];
uint32_t space_id;
uint32_t rm_index_id;
uint32_t it_index_id;
uint32_t it_index_type;
uint32_t field_no;
uint32_t scan_size;
uint32_t scan_time;
};
Το χαρακτηριστικό name είναι το όνομα της εργασίας. Το χαρακτηριστικό space_id είναι το αναγνωριστικό χώρου. Το χαρακτηριστικό rm_index_id είναι το αναγνωριστικό του μοναδικού ευρετηρίου με το οποίο θα διαγράφονται οι πλειάδες. Το χαρακτηριστικό it_index_id είναι το αναγνωριστικό του ευρετηρίου με το οποίο θα διασχίζονται οι πλειάδες. Το χαρακτηριστικό it_index_type είναι ο τύπος ευρετηρίου με τον οποίο θα διασχίζονται οι πλειάδες. Το χαρακτηριστικό filed_no είναι ο αριθμός του πεδίου πλειάδας με διάρκεια ζωής. Το χαρακτηριστικό scan_size είναι ο μέγιστος αριθμός πλειάδων που σαρώνονται σε μία συναλλαγή. Το χαρακτηριστικό scan_time είναι ο πλήρης χρόνος σάρωσης σε δευτερόλεπτα.
Δεν θα εξετάσουμε τα επιχειρήματα ανάλυσης. Αυτή είναι μια επίπονη αλλά απλή δουλειά, με την οποία θα σας βοηθήσει η βιβλιοθήκη
Παραθέτουμε τα πρωτότυπα όλων των συναρτήσεων που χρησιμοποιούνται για την ανάλυση:
bool expirationd_parse_name(struct expirationd_task *task, const char **pos);
bool expirationd_parse_space_id(struct expirationd_task *task, const char **pos);
bool expirationd_parse_rm_index_id(struct expirationd_task *task, const char **pos);
bool expirationd_parse_rm_index_unique(struct expirationd_task *task, const char **pos);
bool expirationd_parse_rm_index(struct expirationd_task *task, const char **pos);
bool expirationd_parse_it_index_id(struct expirationd_task *task, const char **pos);
bool expirationd_parse_it_index_type(struct expirationd_task *task, const char **pos);
bool expirationd_parse_it_index(struct expirationd_task *task, const char **pos);
bool expirationd_parse_field_no(struct expirationd_task *task, const char **pos);
bool expirationd_parse_scan_size(struct expirationd_task *task, const char **pos);
bool expirationd_parse_scan_time(struct expirationd_task *task, const char **pos);
Τώρα ας προχωρήσουμε στο πιο σημαντικό πράγμα - τη λογική της παράκαμψης του χώρου και της διαγραφής πλειάδων. Κάθε μπλοκ πλειάδων όχι μεγαλύτερο από το scan_size σαρώνεται και τροποποιείται σε μία μόνο συναλλαγή. Εάν είναι επιτυχής, αυτή η συναλλαγή δεσμεύεται· εάν παρουσιαστεί σφάλμα, επαναφέρεται. Το τελευταίο όρισμα στη συνάρτηση expirationd_iterate είναι ένας δείκτης στον επαναλήπτη από τον οποίο ξεκινά ή συνεχίζεται η σάρωση. Αυτός ο επαναλήπτης αυξάνεται εσωτερικά έως ότου παρουσιαστεί σφάλμα, εξαντληθεί ο χώρος ή δεν είναι δυνατή η διακοπή της διαδικασίας εκ των προτέρων. Η συνάρτηση expirationd_expired ελέγχει τη διάρκεια ζωής μιας πλειάδας, η expirationd_delete διαγράφει μια πλειάδα, η expirationd_breakable ελέγχει αν πρέπει να προχωρήσουμε.
Κωδικός συνάρτησης Expirationd_iterate:
static bool
expirationd_iterate(struct expirationd_task *task, box_iterator_t **iterp)
{
box_iterator_t *iter = *iterp;
box_txn_begin();
for (uint32_t i = 0; i < task->scan_size; ++i) {
box_tuple_t *tuple = NULL;
if (box_iterator_next(iter, &tuple) < 0) {
box_iterator_free(iter);
*iterp = NULL;
box_txn_rollback();
return false;
}
if (!tuple) {
box_iterator_free(iter);
*iterp = NULL;
box_txn_commit();
return true;
}
if (expirationd_expired(task, tuple))
expirationd_delete(task, tuple);
else if (expirationd_breakable(task))
break;
}
box_txn_commit();
return true;
}
Κωδικός συνάρτησης expirationd_expired:
static bool
expirationd_expired(struct expirationd_task *task, box_tuple_t *tuple)
{
const char *buf = box_tuple_field(tuple, task->field_no - 1);
if (!buf || mp_typeof(*buf) != MP_UINT)
return false;
uint64_t val = mp_decode_uint(&buf);
if (val > fiber_time64() / 1000000)
return false;
return true;
}
Expirationd_delete κωδικός συνάρτησης:
static void
expirationd_delete(struct expirationd_task *task, box_tuple_t *tuple)
{
uint32_t len;
const char *str = box_tuple_extract_key(tuple, task->space_id, task->rm_index_id, &len);
box_delete(task->space_id, task->rm_index_id, str, str + len, NULL);
}
Κωδικός συνάρτησης expiration_breakable:
static bool
expirationd_breakable(struct expirationd_task *task)
{
return task->it_index_id != task->rm_index_id && task->it_index_type == ITER_GT;
}
App
Μπορείτε να δείτε τον πηγαίο κώδικα στο
Πηγή: www.habr.com