Kirjutame tarantooli jaoks oma piiratud aegunud mooduli

Kirjutame tarantooli jaoks oma piiratud aegunud mooduli

Mõni aeg tagasi seisime silmitsi ruumides olevate korstnate puhastamise probleemiga tarantool. Puhastamist tuli alustada mitte siis, kui tarantooli mälu juba otsa sai, vaid ette ja teatud sagedusega. Selle ülesande jaoks on tarantoolil Lua keeles kirjutatud moodul nimega aegumist. Pärast selle mooduli lühikest kasutamist saime aru, et see meile ei sobi: pideva suurte andmemahtude puhastamise tõttu rippus Lua GC-s. Seetõttu mõtlesime välja töötada oma capped expirated mooduli, lootes, et emakeeles kirjutatud kood lahendab meie probleemid parimal võimalikul viisil.

Hea näide meile oli tarantooli moodul nimega mälukaart. Selles kasutatav lähenemine põhineb sellel, et ruumi luuakse eraldi väli, mis näitab korteeži eluiga ehk teisisõnu ttl. Taustal olev moodul skannib ruumi, võrdleb TTL-i praeguse ajaga ja otsustab, kas korteež kustutada või mitte. Vahemällu salvestatud mooduli kood on lihtne ja elegantne, kuid liiga üldine. Esiteks ei võta see arvesse roomatava ja kustutatava indeksi tüüpi. Teiseks skannitakse igal käigul kõik korteežid, mille arv võib olla päris suur. Ja kui aegunud moodulis sai esimene probleem lahendatud (puuindeks eraldati eraldi klassiks), siis teine ​​ei saanud ikkagi tähelepanu. Need kolm punkti määrasid valiku minu enda koodi kirjutamise kasuks.

Kirjeldus

Tarantooli dokumentatsioonil on väga hea õpetus selle kohta, kuidas kirjutada oma salvestatud protseduure C-vormingus. Kõigepealt soovitan teil sellega tutvuda, et mõista allpool kuvatavaid käskude ja koodidega lisasid. Samuti tasub sellele tähelepanu pöörata viide objektidele, mis on saadaval teie enda piiratud mooduli kirjutamisel, nimelt kast, kiud, indeks и txn.

Alustame kaugelt ja vaatame, milline näeb välja aegunud 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äivitame tarantooli kataloogis, kus asub meie teek libcapped-expirationd.so. Teegist eksporditakse kaks funktsiooni: start ja kill. Esimene samm on teha need funktsioonid Luast kättesaadavaks, kasutades selleks box.schema.func.create ja box.schema.user.grant. Seejärel looge ruum, mille korteežides on ainult kolm välja: esimene on kordumatu identifikaator, teine ​​​​e-post ja kolmas on korteeži eluiga. Ehitame esimese välja peale puuindeksi ja nimetame seda esmaseks. Järgmisena saame ühendusobjekti oma emaraamatukoguga.

Pärast ettevalmistustööd käivitage käivitusfunktsioon:

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 skannimise ajal täpselt samamoodi nagu aegunud moodul, mis on kirjutatud Lua keeles. Algfunktsiooni esimene argument on ülesande kordumatu nimi. Teine on ruumi identifikaator. Kolmas on kordumatu indeks, mille abil kortereid kustutatakse. Neljas on indeks, mille järgi kortereid läbitakse. Viies on elueaga korteeživälja number (numeratsioon algab 1-st, mitte 0-st!). Kuues ja seitsmes on skannimise sätted. 1024 on maksimaalne korduste arv, mida saab ühe tehinguga vaadata. 3600 – täielik skannimisaeg sekundites.

Pange tähele, et näide kasutab roomamiseks ja kustutamiseks sama indeksit. Kui see on puu indeks, siis läbimine toimub väiksemast võtmest suuremale. Kui on mõni muu, näiteks räsiindeks, siis läbimine toimub reeglina juhuslikus järjekorras. Kõik ruumikorpused skannitakse ühe skaneerimisega.

Sisestame ruumi mitu korda, mille eluiga on 60 sekundit:

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}

Kontrollime, kas sisestamine õnnestus:

tarantool> box.space.tester.index.primary:select()
---
- - [0, '[email protected]', 1576418976]
  - [1, '[email protected]', 1576418976]
  - [2, '[email protected]', 1576418976]
...

Korrame valimist 60+ sekundi pärast (arvestades esimese korteeži sisestamise algusest) ja vaatame, et piiranguga aegunud moodul on juba töödeldud:

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

Lõpetame ülesande:

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

Vaatame teist näidet, kus roomamiseks 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 mõned erandid. Ehitame kolmanda välja peale puuindeksi ja nimetame seda exp. See indeks ei pea olema kordumatu, erinevalt indeksist, mida nimetatakse esmaseks. Läbimine toimub exp indeksi järgi ja kustutamine esmase järgi. Mäletame, et varem tehti mõlemat ainult esmase indeksi abil.

Pärast ettevalmistustööd käivitame käivitamisfunktsiooni 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})

Sisestame ruumi uuesti mitu korrust elueaga 60 sekundit:

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 sekundi pärast lisame analoogia põhjal veel mõned korteid:

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}

Kontrollime, kas sisestamine õnnestus:

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

Korrame valimist 60+ sekundi pärast (arvestades esimese korteeži sisestamise algusest) ja vaatame, et piiranguga aegunud moodul on juba töödeldud:

tarantool> box.space.tester.index.primary:select()
---
- - [3, '[email protected]', 1576421287]
  - [4, '[email protected]', 1576421287]
  - [5, '[email protected]', 1576421287]
...

Ruumi on veel jäänud mõned kordid, millel on elada veel umbes 30 sekundit. Veelgi enam, skannimine peatus, kui liikus korteežilt ID-ga 2 ja elueaga 1576421257 korteeži ID-ga 3 ja elueaga 1576421287. Kortereid, mille eluiga on 1576421287 või rohkem, ei skaneeritud tellimise tõttu exp-indeksi võtmed. See on kokkuhoid, mida tahtsime kohe alguses saavutada.

Lõpetame ülesande:

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

Реализация

Parim viis projekti kõigist omadustest rääkida on selle algallikas. kood! Väljaande raames keskendume ainult kõige olulisematele punktidele, nimelt ruumi möödaviigu algoritmidele.

Algmeetodile edastatavad argumendid salvestatakse struktuuri 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 ülesande nimi. Atribuut space_id on ruumi identifikaator. Atribuut rm_index_id on kordumatu indeksi identifikaator, mille järgi kortereid kustutatakse. Atribuut it_index_id on indeksi identifikaator, mille abil kortereid läbitakse. Atribuut it_index_type on indeksi tüüp, mille abil kortereid läbitakse. Atribuut filed_no on elueaga korteeživälja number. Atribuut scan_size on maksimaalne arv kortereid, mida ühes tehingus kontrollitakse. Atribuut scan_time on kogu skannimise aeg sekundites.

Me ei võta arvesse argumentide sõelumist. See on vaevarikas, kuid lihtne töö, millega raamatukogu teid aitab msgpuck. Raskused võivad tekkida ainult indeksite puhul, mis edastatakse Luast keeruka andmestruktuurina tüübiga mp_map, mitte aga kasutades lihtsaid tüüpe mp_bool, mp_double, mp_int, mp_uint ja mp_array. Kuid kogu indeksit pole vaja sõeluda. Peate lihtsalt kontrollima selle ainulaadsust, arvutama tüübi ja eraldama identifikaatori.

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);

Liigume nüüd edasi kõige olulisema juurde – ruumi möödahiilimise ja korteežide kustutamise loogika juurde. Iga korduste plokk, mis ei ole suurem kui scan_size, skannitakse ja muudetakse ühe tehinguga. Edu korral see tehing sooritatakse; vea ilmnemisel tühistatakse. Funktsiooni expirationd_iterate viimane argument on osuti iteraatorile, millest skannimine algab või jätkub. Seda iteraatorit suurendatakse sisemiselt, kuni ilmneb tõrge, ruum saab otsa või protsessi ei ole võimalik eelnevalt peatada. Funktsioon expirationd_expired kontrollib korteeži eluiga, expirationd_delete kustutab korteeži, expirationd_breakable kontrollib, kas meil on vaja edasi liikuda.

Expirationd_iterate funktsiooni 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 < 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;
}

Funktsiooni kood 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 funktsioonikood:

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 kood 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;
}

Taotlus

Lähtekoodi saate vaadata aadressil siin!

Allikas: www.habr.com

Lisa kommentaar