
Disa herë më parë, kemi pasur një problem me pastrimin e tuple-ve në hapësira. . Pastrimi duhet të niste jo në momentin kur tarantool-i kishte përfunduar memorjen, por paraprakisht dhe me një periudhë të caktuar. Për këtë detyrë, tarantool ka një modul, i shkruar në Lua, të quajtur . Pas një përdorimi të shkurtër të këtij moduli kuptuam se nuk na përshtatej: gjatë pastrimeve të vazhdueshme të sasisë së madhe të të dhënave, Lua ngec në GC. Kështu që ne menduam të zhvillonim një modul të vetin capped expirationd, me shpresën se kodi, i shkruar në një gjuhë programuese natyrale, do të zgjidhte më mirë problemet tona.
Një shembull i mirë për ne ishte moduli tarantool, i quajtur . Qasja e përdorur në të bazohet në atë që në hapësirë krijohet një fushë e veçantë, në të cilën përcaktohet koha e jetës së tuple-it, ose më saktë, ttl. Moduli skanon në sfond hapësirën, krahasohet ttl me kohën aktuale dhe merr vendimin për të fshirë tuple-in apo jo. Kodi i modulit memcached është i thjeshtë dhe elegant, por shumë i përgjithshëm. Së pari, ai nuk merr parasysh llojin e indeksit, sipas të cilit bëhet kalimi dhe fshirja. Së dyti, në çdo kalim skanohen të gjitha tuple-t, numri i të cilave mund të jetë mjaft i madh. Dhe nëse në modulit expirationd problemi i parë u zgjidh (indeksi tree u ndan në një klasë të veçantë), problemi i dytë ende nuk i është kushtuar vëmendje. Këto tre pika e përcaktuan zgjedhjen për të shkruar kodin tonë.
Përshkrimi
Në dokumentacionin e tarantool ka një të shkëlqyer në lidhje me si të shkruani procedurat tuaja të ruajtura në gjuhën C. Së pari, ju sugjeroj të familiarizoheni me të për të kuptuar ato inserte me komanda dhe kod që do të hasni më poshtë. Gjithashtu, duhet të kushtoni vëmendje për referencën pjesëve që janë të disponueshme kur shkruani modul të vetë-kapsuluar, veçanërisht në , , dhe .
Le ta fillojmë nga larg dhe të shohim se si duket moduli capped expirationd nga jashtë:
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)PĂ«r lehtĂ«si, e ekzekutojmĂ« tarantool nĂ« katalogun ku ndodhet biblioteka jonĂ« libcapped-expirationd.so. Nga biblioteka eksportohen dy funksione: start dhe kill. NĂ« fillim, Ă«shtĂ« e nevojshme t'i bĂ«ni kĂ«to funksione tĂ« aksesueshme nga Lua duke pĂ«rdorur box.schema.func.create dhe box.schema.user.grant. Pastaj krijoni njĂ« hapĂ«sirĂ«, tuple-t e sĂ« cilĂ«s do tĂ« pĂ«rmbajnĂ« vetĂ«m tri fusha: e para â njĂ« identifikues unik, e dyta â njĂ« email, e treta â koha e jetĂ«s sĂ« tuple-it. Mbi fushĂ«n e parĂ« krijojmĂ« njĂ« indeks tree dhe e quajmĂ« primary. MĂ« pas, marrim objektin e lidhjes me bibliotekĂ«n tonĂ« natyrale.
Pasi përgatitjet janë kryer, ekzekutojmë funksionin 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})Ky shembull do tĂ« funksionojĂ« duke skanuar pikĂ«risht ashtu si moduli expirationd, i shkruar nĂ« Lua. Argumenti i parĂ« nĂ« funksionin start kalon njĂ« emĂ«r unik tĂ« detyrĂ«s. Argumenti i dytĂ« â identifikuesin e hapĂ«sirĂ«s. Argumenti i tretĂ« â indeksi unik, sipas tĂ« cilit do tĂ« bĂ«het fshirja e tuple-ve. Argumenti i katĂ«rt â indeksi, sipas tĂ« cilit do tĂ« bĂ«het kalimi i tuple-ve. Argumenti i pestĂ« â numri i fushĂ«s sĂ« tuple-it me kohĂ«n e jetĂ«s (numĂ«rimi fillon nga 1, dhe jo nga 0!). Argumentet e gjashta dhe tĂ« shtata â cilĂ«simet e skanimit. 1024 â Ă«shtĂ« numri maksimal i tuple-ve qĂ« shqyrtohen nĂ« njĂ« transaksion tĂ« vetĂ«m. 3600 â koha e plotĂ« e skanimit nĂ« sekonda.
Vini re se për kalimin dhe fshirjen në shembull është përdorur të njëjtin indeks. Nëse është një indeks tree, kalimi bëhet nga çelësi më të vogël në atë më të madh. Nëse ndonjë indeks tjetër, për shembull, një indeks hash, kalimi bëhet zakonisht në një rend të rastësishëm. Gjatë një skanimi shqyrtohen të gjitha tuple-t e hapësirës.
Le të bëjmë një insert në hapësirë të disa tuple-ve me kohën e jetës 60 sekonda:
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}Të kontrollojmë se inserti ishte i suksesshëm:
tarantool> box.space.tester.index.primary:select()
---
- - [0, 'user0@tarantool.io', 1576418976]
- [1, 'user1@tarantool.io', 1576418976]
- [2, 'user2@tarantool.io', 1576418976]
...Përsërisim select pas 60+ sekondash (duke e llogaritur nga fillimi i insertit të tuple-it të parë) dhe do të shohim se moduli capped expirationd ka funksionuar:
tarantool> box.space.tester.index.primary:select()
---
- []
...Të ndalojmë detyrën:
capped_connection:call('libcapped-expirationd.kill', {'non-indexed'})Le të shqyrtojmë një shembull të dytë, kur për kalim përdoret një indeks i veçantë:
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)Këtu është gjithçka e njëjtë me shembullin e parë, me përjashtim të vogël. Përdorim një indeks tree mbi fushën e tretë dhe e quajmë exp. Ky indeks nuk është i detyrueshëm të jetë unik, ndryshe nga indeksi që quhet primar. Shkarkimi do të bëhet përmes indeksit exp, ndërsa fshirja përmes primarit. Ne kujtojmë se më parë, të dyja këto bëheshin vetëm duke përdorur indeksin primar.
Pas punĂ«ve pĂ«rgatitore, ne запŃŃĐșĐ°ĐŒ funksionin start me argumentet e reja:
capped_connection:call('libcapped-expirationd.start', {'indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.exp, 3, 1024, 3600})Sërish do të bëjmë një insert në hapësirë me disa tuple me një jetëgjatësi prej 60 sekondash:
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}Pasi kalojnë 30 sekonda, do të shtojmë disa tuple të tjera:
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}Të kontrollojmë se inserti ishte i suksesshëm:
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]
...Përsërisim select pas 60+ sekondash (duke e llogaritur nga fillimi i insertit të tuple-it të parë) dhe do të shohim se moduli capped expirationd ka funksionuar:
tarantool> box.space.tester.index.primary:select()
---
- - [3, 'user3@tarantool.io', 1576421287]
- [4, 'user4@tarantool.io', 1576421287]
- [5, 'user5@tarantool.io', 1576421287]
...Në hapësirë kanë mbetur tuple që do të jetojnë edhe për rreth 30 sekonda. Për më tepër, skanimi ndali kur kaloi nga tuple me identifikues 2 dhe jetëgjatësi 1576421257 në tuple me identifikues 3 dhe jetëgjatësi 1576421287. Tuple me jetëgjatësi 1576421287 dhe më shumë nuk u panë për shkak të renditjes së çelësave të indeksit exp. Kjo është ajo kursim që ne dëshironim të arrinim në fillim.
Të ndalojmë detyrën:
capped_connection:call('libcapped-expirationd.kill', {'indexed'})Realizimi
Më mirë se gjithçka, të gjitha veçoritë e projektit do t'ju tregojë gjithmonë kodi i tij burimor ! Në këtë publikim ne do të ndalemi vetëm në momentet më të rëndësishme, të cilat lidhen me algoritmet e skanimit të hapësirës.
Argumentet që ne i kalojmë në metodën start, ruajnë në një strukturë të quajtur 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;
};Atributi name â Ă«shtĂ« emri i detyrĂ«s. Atributi space_id â Ă«shtĂ« identifikuesi i hapĂ«sirĂ«s. Atributi rm_index_id â Ă«shtĂ« identifikuesi i indeksit unik, mbi tĂ« cilin do tĂ« bĂ«het fshirja e tuple-ve. Atributi it_index_id â Ă«shtĂ« identifikuesi i indeksit, mbi tĂ« cilin do tĂ« bĂ«het skanimi i tuple-ve. Atributi it_index_type â Ă«shtĂ« tipi i indeksit, mbi tĂ« cilin do tĂ« bĂ«het skanimi i tuple-ve. Atributi filed_no â Ă«shtĂ« numri i fushĂ«s sĂ« tuple-sĂ« me jetĂ«gjatĂ«si. Atributi scan_size â Ă«shtĂ« numri maksimal i tuple-ve qĂ« skanohet nĂ« njĂ« transaksion. Atributi scan_time â Ă«shtĂ« koha e plotĂ« e skanimit nĂ« sekonda.
Parsing i argumenteve nuk do ta trajtojmë. Kjo është një punë e rëndë, por jo e komplikuar, në të cilën do t'ju ndihmojë biblioteka . Vështirësitë mund të shfaqen vetëm me indekset, të cilat kalohen nga Lua si një strukturë e ndërlikuar të dhënash me tipin mp_map, dhe jo me tipet e thjeshta mp_bool, mp_double, mp_int, mp_uint dhe mp_array. Por nuk është e nevojshme të analizoni të gjithë indeksin. Mjafton vetëm të kontrolloni unikësinë e tij, të llogarisni tipin dhe të nxirrni identifikuesin.
Do të rendisim prototipet e të gjitha funksioneve që përdoren për parsing:
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);Tani tani kalojmĂ« te e rĂ«ndĂ«sishmja â logjika e kalimit tĂ« hapĂ«sirĂ«s dhe eliminimit tĂ« tuple-ve. Ădo bllok tuple-ve me njĂ« madhĂ«si maximum scan_size shqyrtohet dhe ndryshohet nĂ«n njĂ« transaksion. NĂ« rast suksesi, kjo transaksion konfirmohet, nĂ« rast gabimi â kthehet mbrapsht. Argumenti pĂ«rfundimtar nĂ« funksionin expirationd_iterate jep njĂ« tregues pĂ«r iteneratorin, nga i cili fillon ose vazhdon skanimi. Ky itenerator inkrementohet brenda derisa tĂ« ndodhi njĂ« gabim, hapĂ«sira tĂ« mbarojĂ«, ose tĂ« krijohet mundĂ«sia pĂ«r tĂ« ndaluar procesin pĂ«rpara kohe. Funksioni expirationd_expired kontrollon kohĂ«n e jetesĂ«s sĂ« tuple-ve, expirationd_delete â eliminon tuple-n, expirationd_breakable â kontrollon nĂ«se duhet tĂ« shkojmĂ« mĂ« tej.
Kodi i funksionit 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 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;
}Kodi i funksionit 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;
}Kodi i funksionit 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);
}Kodi i funksionit expirationd_breakable:
static bool
expirationd_breakable(struct expirationd_task *task)
{
return task->it_index_id != task->rm_index_id && task->it_index_type == ITER_GT;
}Aplikacioni
Mund të shihni kodin burimor në !
Burimi: habr.com
