Kirjutame oma capped expirationd mooduli Tarantoole

Kirjutame oma capped expirationd mooduli Tarantoole

MĂ”ni aeg tagasi seisis meie ees probleem tuple'ite puhastamisest spacies. tarantool. Puhastamine pidi toimuma mitte siis, kui tarantoolil hakkas mĂ€lu otsa saama, vaid ette ja kindla regulaarsusega. Selle ĂŒlesande tĂ€itmiseks on tarantoolis moodul, mis on kirjutatud Lua keeles ja mille nimi on expirationd. PĂ€rast lĂŒhikest kasutamist selle mooduli puhul saime aru, et see ei sobi meile: pidevate suurte andmemahtude puhastamisel hangus Lua GC-s. SeetĂ”ttu mĂ”tlesime vĂ€lja oma capped expirationd mooduli arendamise, lootes, et natiivkeeles kirjutatud kood lahendab meie probleemid kĂ”ige paremini.

Heaks nĂ€iteks on meile olnud tarantooli moodul, mille nimi on memcached. Kasutatav lĂ€henemine pĂ”hineb sellel, et spaciesse luuakse eraldi vĂ€li, milles mÀÀratakse tuple'i eluiga, ehk ttl. Moodul skaneerib taustal spaci, vĂ”rdleb ttl praeguse ajaga ja teeb otsuse, kas tuple'i kustutada vĂ”i mitte. Mooduli memcached kood on lihtne ja elegantne, kuid liiga ĂŒldine. Esiteks ei arvestata indeksi tĂŒĂŒpi, mille alusel toimub lĂ€bimine ja kustutamine. Teiseks skaneeritakse igal lĂ€bimisel kĂ”ik tuple'id, mille arv vĂ”ib olla ĂŒsna suur. Ja kui moodulis expirationd oli esimene probleem lahendatud (puuindeks on eraldatud eraldi klassiks), siis teisele ei ole ikka veel tĂ€helepanu pööratud. Need kolm punkti mÀÀrasid valiku oma koodi kirjutamiseks.

Kirjeldus

Tarantooli dokumentatsioonis on vÀga hea tutvustus koht, kus on kirjas, kuidas kirjutada oma salvestatud protseduure C keeles. Esiteks soovitan sellega tutvuda, et mÔista neid sisendeid kÀsudega ja koodiga, millega allpool kokku puutute. Samuti tasub tÀhelepanu pöörata viitele objektidele, mis on saadaval oma capped mooduli kirjutamise ajal, sealhulgas box, fiber, index ja txn.

Alustame kaugelt ja vaatame, kuidas capped expirationd moodul vÀljastpoolt vÀlja nÀeb:

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Ă€ivitame tarantooli kaustas, kus asub meie libcapped-expirationd.so teek. Teegist eksporditakse kaks funktsiooni: start ja kill. Esmalt tuleb need funktsioonid Lua'sse kĂ€ttesaadavaks teha, kasutades box.schema.func.create ja box.schema.user.grant. SeejĂ€rel loome spetsi, mille tupikud sisaldavad kolm vĂ€lja: esimene - ainulaadne identifikaator, teine - e-posti aadress, kolmas - tupiku eluiga. Esimese vĂ€lja peale ehitame puu-indeksi ja nimetame selle primary. Edasi saame objekti, mis on ĂŒhendatud meie natiivse teegiga.

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 sama moodi nagu Lua's kirjutatud expirationd moodul. Esimeseks argumendiks funktsioonis start antakse ainulaadne töö nime. Teiseks - spetsi identifikaator. Kolmandaks - ainulaadne indeks, mille alusel tupikute kustutamine toimub. Neljandaks - indeks, mille alusel tupikute lĂ€bimine toimub. Viiendaks - tupiku eluiga vĂ€lja number (numereerimine algab 1-st, mitte 0-st!). Kuues ja seitsmes - skaneerimise seaded. 1024 - see on maksimaalne tupikute arv, mida vaadatakse ĂŒhe tehingu jooksul. 3600 - skaneerimise koguaeg sekundites.

Pange tĂ€hele, et lĂ€bimise ja kustutamise puhul kasutatakse nĂ€ites sama indeksit. Kui see on puu-indeks, siis toimub lĂ€bimine vĂ€iksemast vĂ”tmega suuremale. Kui see on mĂ”ni muu, nĂ€iteks hash-indeks, siis toimub lĂ€bimine tavaliselt suvalises jĂ€rjekorras. Ühe skaneerimise jooksul vaadatakse kĂ”iki tupikuid spets.

Teeme spetsi mÔned tupikute sisestused, 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 Ônnestus:

tarantool> box.space.tester.index.primary:select()
---
- - [0, 'user0@tarantool.io', 1576418976]
  - [1, 'user1@tarantool.io', 1576418976]
  - [2, 'user2@tarantool.io', 1576418976]
...

Kordame select'i 60+ sekundi pÀrast (loetleme alates esimesest tupiku sisestamisest) ja nÀeme, et moodul capped expirationd on juba töötanud:

tarantool> box.space.tester.index.primary:select()
---
  - []
...

KĂ€tkestame ĂŒlesande:

capped_connection:call('libcapped-expirationd.kill', {'non-indexed'})

Vaatame teist nÀidet, kus lÀbimise jaoks 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Àikese erandiga. Kolmanda vÀlja peale loome tree-indeksi ja nimetame selle exp. See indeks ei pea olema ainulaadne, erinevalt indeksist nimega primary. LÀbimine toimub exp indeksi kaudu, ja kustutamine toimub primary kaudu. 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 taas sissekande spaisse, lisades mitu tuple, 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 sarnasel viisil veel mÔned tuple:

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 Ônnestus:

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]
...

Kordame select'i 60+ sekundi pÀrast (loetleme alates esimesest tupiku 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]
...

Spaisse on jÀÀnud tuple, mille eluiga on veel umbes 30 sekundit. Veelgi enam, skaneerimine peatus, kui liikuda tuple'ilt, mille ID on 2 ja eluiga 1576421257, tuple'ile, mille ID on 3 ja eluiga 1576421287. Tuplid, mille eluiga on 1576421287 ja enam, ei olnud vaatatud mn eksia, kuna keyd exp indeksi jÀrjekorras on korras. See on see sÀÀst, mida me alguses saavutada soovisime.

KĂ€tkestame ĂŒlesande:

capped_connection:call('libcapped-expirationd.kill', {'indexed'})

Rakendamine

KÔik projekti omaduste kohta rÀÀgib kÔige paremini selle lÀhtekood koodi! Avaldamise raames peatume vaid kÔige olulisematel detailidel, nimelt, ruumi lÀbimise algoritmidel.

Argumente, mida me meetodisse start edastame, sÀilitatakse 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;
};

Attribuut name on ĂŒlesande nimi. Attribuut space_id on ruumi identifikaator. Attribuut rm_index_id on ainulaadse indeksi identifikaator, mille alusel eemaldatakse tupikud. Attribuut it_index_id on indeksi identifikaator, mille alusel tupikuid vaadatakse. Attribuut it_index_type on indeksi tĂŒĂŒp, mille alusel tupikuid vaadatakse. Attribuut filed_no on tupiku eluea seadme number. Attribuut scan_size on maksimaalne tupikute arv, mida vaadatakse ĂŒhe tehingu raames. Attribuut scan_time on tĂ€ieliku skaneerimise aeg sekundites.

Argumetide parsimist ei kĂ€sitle. See on aeganĂ”udev, kuid mitte keeruline töö, milles aitab teid raamatukogu msgpuck. Probleeme vĂ”ivad tekkida ainete osas, mis edastatakse Luast keeruka andmestruktuurina tĂŒĂŒbiga mp_map, mitte lihtse 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 mÀÀramisest ja identifikaatori vĂ€ljavĂ”tmisest.

Loetleme kĂ”igi parsimiseks kasutatavate funktsioonide prototĂŒĂŒbid:

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 liikume kĂ”ige olulisema juurde — ruumi skannimise ja tupikute eemaldamise loogika juurde. Iga tupikute blokk, mille suurus ei ĂŒleta scan_size, kontrollitakse ja muudetakse ĂŒhe tehingu raames. Kui see Ă”nnestub, kinnitatakse tehing, ja kui tekib viga, tĂŒhistatakse see. Viimasena edastatakse funktsiooni expirationd_iterate argumendina nĂ€itaja, millest skannimine algab vĂ”i jĂ€tkub. See nĂ€itaja suureneb hiljem, kuni tekib viga, ruum lĂ”ppeb vĂ”i on vĂ”imalik protsess eelnevalt peatada. Funktsioon expirationd_expired kontrollib tupiku eluaega, expirationd_delete — kustutab tupiku, expirationd_breakable — kontrollib, kas meid on vaja edasi liikuda.

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

Algallikakoodiga saab tutvuda aadressil siit!

Allikas: habr.com

Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega veebisaitidele, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega veebisaitidele, VPS VDS serverid - ProHoster