
MĂ”nda aega tagasi seisis meie ees probleem tuple'ide puhastamisest spacetest. . Puhastust ei olnud mĂ”istlik alustada siis, kui tarantooli mĂ€lu oli juba otsas, vaid pigem ette ja kindla ajaintervalliga. Selle ĂŒlesande jaoks on tarantoolis olemas Lua keeles kirjutatud moodul, mida nimetatakse . PĂ€rast lĂŒhikest kasutamist avastasime, et see ei sobi meile: pidevate suurte andmemahtude puhastamisega Lua hangus GC-s. SeetĂ”ttu hakkasime mĂ”tlema oma capped expirationd mooduli arendamisele, lootes, et natiivses programmeerimiskeeles kirjutatud kood lahendab meie probleemid parimal viisil.
Hea nĂ€itena teenis meile moodul tarantoolis nimega . Kasutatud lĂ€henemine pĂ”hineb sellele, et spais luuakse eraldi vĂ€li, kuhu mĂ€rgitakse tupiku eluaeg, teisisĂ”nu ttl. Moodul taustal skaneerib spaisi, vĂ”rdleb ttl-i praeguse ajaga ja otsustab, kas tupik kustutada vĂ”i mitte. Memcached mooduli kood on lihtne ja elegantne, kuid liiga ĂŒldine. Esiteks ei arvesta see indeksitĂŒĂŒpi, mille alusel toimub iteratsioon ja kustutamine. Teiseks skaneeritakse igal lĂ€bimisel kĂ”iki tupikuid, mille arv vĂ”ib olla ĂŒsna suur. Ja kui mooduli expirationd puhul oli esimene probleem lahendatud (tree-indeks eraldati eraldi klassiks), siis teise probleemi vastu pole tĂ€helepanu pööratud. Need kolm punkti mÀÀrasid valiku oma koodi kirjutamise kasuks.
Kirjeldus
Tarantooli dokumentatsioonis on vÀga hea kuidas kirjutada oma salvestatud protseduure C keeles. Esiteks soovitan sellega tutvuda, et mÔista allpool esinevaid kÀskude ja koodi inserte. Samuti tasub tÀhelepanu pöörata objektidele, mis on saadaval oma capped mooduli kirjutamise ajal, nimelt , , ja .
Alustame kaugelt ja vaatame, kuidas vÀlja nÀeb capped expirationd moodul vÀljastpoolt:
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)Lihtsuse huvides kĂ€ivitage tarantool kaustas, kus asub meie Đ±ĐžĐ±Đ»ĐžĐŸŃĐ”Đșа libcapped-expirationd.so. Raamatukogust eksporditakse kaks funktsiooni: start ja kill. Esiteks on oluline teha need funktsioonid Luast ligipÀÀsetavaks, kasutades box.schema.func.create ja box.schema.user.grant. SeejĂ€rel looge ruum, mille tuple'id sisaldavad kolme vĂ€ljad: esimene - unikaalne identifikaator, teine - e-posti aadress, kolmas - tuple'i eluiga. Esimese vĂ€lja peale loome puu-indeksi ja nimetame selle primaarseks. Edasi saame objekti, mis on ĂŒhendatud meie natiivse raamatukoguga.
PÀrast ettevalmistustöid kÀivitame funktsiooni start:
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})See nĂ€ide töötab skaneerimisel tĂ€pselt samamoodi nagu Lua-s kirjutatud moodul expirationd. Esimene argument funktsioonile start on ainulaadne töö nime. Teine on ruumi identifikaator. Kolmas on ainulaadne indeks, mille alusel kandeid eemaldatakse. Neljas on indeks, mille alusel kandeid lĂ€bime. Viies on kande eluiga tĂ€histav vĂ€li (numereerimine algab 1, mitte 0-st!). Kuues ja seitsmes on skaneerimise seaded. 1024 on maksimaalne kandeid, mida vaadatakse lĂ€bi ĂŒhe tehingu jooksul. 3600 on skaneerimise tĂ€ielik kestus sekundites.
Pange tĂ€hele, et lĂ€bimise ja eemaldamise nĂ€ites kasutatakse sama indĐ”ĐșŃаatorit. Kui see on puuindeks, siis skaneeritakse vĂ€iksematest vĂ”titest suuremate juurde. Kui see on mĂ”ni muu, nĂ€iteks hash-indeks, siis skaneeritakse tavaliselt juhuslikus jĂ€rjekorras. Ăks skaneerimine hĂ”lmab kĂ”iki ruumi kandeid.
Tehkem ruumi mitmesuguste kandeid, mille eluiga on 60 sekundit:
box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}Kontrollime, et sisestamine toimus edukalt:
tarantool> box.space.tester.index.primary:select()
---
- - [0, 'user0@tarantool.io', 1576418976]
- [1, 'user1@tarantool.io', 1576418976]
- [2, 'user2@tarantool.io', 1576418976]
...Teeme valiku uuesti 60+ sekundi pÀrast (alustame esimese ridade sisestamisest) ja nÀeme, et moodul capped expirationd on juba töötanud:
tarantool> box.space.tester.index.primary:select()
---
- []
...LĂ”petame ĂŒlesande:
capped_connection:call('libcapped-expirationd.kill', {'non-indexed'})Vaadakem teist nÀidet, kus kasutatakse eraldi indeksit:
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)Siin on kÔik sama, mis esimeses nÀites, vÀlja arvatud vÀikeste eranditega. Kolmanda vÀlja peale ehitame tree-indeksi ja nimetame selleks exp. See indeks ei pea olema ainulaadne, erinevalt indeksist nimega primary. LÀbimine toimub exp indeksi kaudu ja kustutamine pÔhineb primary'l. Me mÀletame, et varem tehti mÔlemat ainult primary indeksi kasutamisega.
PÀrast ettevalmistustöid kÀivitame funktsiooni start uute argumentidega:
capped_connection:call('libcapped-expirationd.start', {'indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.exp, 3, 1024, 3600})Teeme uuesti lisamise spaisse mitme tupeliga, mille eluiga on 60 sekundit:
box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}30 sekundi pÀrast lisame analoogiliselt veel mÔned tupelid:
box.space.tester:insert{3, 'user3@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{4, 'user4@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{5, 'user5@tarantool.io', math.floor(fiber.time()) + 60}Kontrollime, et sisestamine toimus edukalt:
tarantool> box.space.tester.index.primary:select()
---
- - [0, 'user0@tarantool.io', 1576421257]
- [1, 'user1@tarantool.io', 1576421257]
- [2, 'user2@tarantool.io', 1576421257]
- [3, 'user3@tarantool.io', 1576421287]
- [4, 'user4@tarantool.io', 1576421287]
- [5, 'user5@tarantool.io', 1576421287]
...Teeme valiku uuesti 60+ sekundi pÀrast (alustame esimese ridade sisestamisest) ja nÀeme, et moodul capped expirationd on juba töötanud:
tarantool> box.space.tester.index.primary:select()
---
- - [3, 'user3@tarantool.io', 1576421287]
- [4, 'user4@tarantool.io', 1576421287]
- [5, 'user5@tarantool.io', 1576421287]
...Ruumis on jÀÀnud tuple'id, millel on veel umbes 30 sekundit elada. Veelgi enam, skaneerimine peatuti ĂŒleminekul tuple'ilt, mille ID on 2 ja eluiga 1576421257, tuple'ile, mille ID on 3 ja eluiga 1576421287. Tuple'id, mille eluiga on 1576421287 ja rohkem, ei ole vaadatud, kuna exp indeksi vĂ”tmete jĂ€rjekord on selline. See ongi see sÀÀst, mida soovisime alguses saavutada.
LĂ”petame ĂŒlesande:
capped_connection:call('libcapped-expirationd.kill', {'indexed'})Rakendus
KÔige paremini rÀÀgib projekti kÔikidest omadustest alati selle lÀhtekood ! Avaldamise raames peatume ainult kÔige olulisematel detailidel, nimelt ruumi lÀbimisalgoritmidel.
Argumente, mida edastame meetodile start, hoitakse struktuuris nimega 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;
};Atribuut name on taski nimi. Atribuut space_id on ruumi ID. Atribuut rm_index_id on unikaalse indeksi ID, mille alusel ridade eemaldamine toimub. Atribuut it_index_id on indeksi ID, mille alusel ridade lĂ€bimine toimub. Atribuut it_index_type on indeksi tĂŒĂŒp, mille alusel ridade lĂ€bimine toimub. Atribuut filed_no on ridade aegumisega seotud vĂ€lja number. Atribuut scan_size on maksimaalne ridade arv, mida vaadatakse ĂŒhe tehingu raames. Atribuut scan_time on tĂ€ieliku skaneerimise aeg sekundites.
Argumentide parsimist ei arutata. See on pĂ”hjalik, kuid mitte keeruline töö, kus aitab teid raamatukogu . Raskeid vĂ”ib esineda vaid indeksitega, mis edastatakse Luast keerulise andmestruktuurina mp_map tĂŒĂŒbi kaudu, mitte lihtsate tĂŒĂŒpide mp_bool, mp_double, mp_int, mp_uint ja mp_array abil. Kuid kogu indeksi parsimine ei ole vajalik. Piisab vaid selle unikaalsuse kontrollimisest, tĂŒĂŒbi arvutamisest ja ID vĂ€ljavĂ”tmisest.
Loetleme kĂ”igi funktsioonide prototĂŒĂŒbid, mida kasutatakse parsimiseks:
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);Ja nĂŒĂŒd liigume kĂ”ige olulisema juurde â ruumi lĂ€bimise ja tuplite eemaldamise logika. Iga tuplite plokk, mille suurus ei ĂŒleta scan_size, vaadatakse lĂ€bi ja muudetakse ĂŒhe tehingu raames. Edu korral kinnitatakse see tehing, vea korral tagastatakse see. Viimasena edastatakse funktsiooni expirationd_iterate argumendina nĂ€idik, millelt algab vĂ”i jĂ€tkub skaneerimine. See nĂ€idik suurendatakse sees, kuni tekib viga, ruum lĂ”ppeb vĂ”i tuleb vĂ”imalus protsess eelnevalt peatada. Funktsioon expirationd_expired kontrollib tuplite eluaega, expirationd_delete â eemaldab tupli, expirationd_breakable â kontrollib, kas me peame liikuma edasi.
Funktsiooni expirationd_iterate kood:
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 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;
}Funktsiooni expirationd_expired kood:
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;
}Funktsiooni expirationd_delete kood:
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);
}Funktsiooni expirationd_breakable kood:
static bool
expirationd_breakable(struct expirationd_task *task)
{
return task->it_index_id != task->rm_index_id && task->it_index_type == ITER_GT;
}Rakendus
Allika koodiga saab tutvuda aadressil !
Allikas: habr.com
