Mēs rakstām savu tarantool moduli ar ierobežotu derīguma termiņu

Mēs rakstām savu tarantool moduli ar ierobežotu derīguma termiņu

Pirms kāda laika mēs saskārāmies ar telpu tÄ«rÄ«Å”anas problēmu tarantools. TÄ«rÄ«Å”ana bija jāuzsāk nevis tad, kad tarantolam jau beidzās atmiņa, bet gan iepriekÅ” un ar noteiktu biežumu. Å im uzdevumam tarantool ir Lua valodā rakstÄ«ts modulis ar nosaukumu derÄ«guma termiņŔ. ÄŖslaicÄ«gi izmantojot Å”o moduli, mēs sapratām, ka tas mums nav piemērots: pastāvÄ«gas liela datu apjoma tÄ«rÄ«Å”anas dēļ Lua karājās GC. Tāpēc mēs domājām par sava ierobežotā derÄ«guma moduļa izstrādi, cerot, ka dzimtajā programmÄ“Å”anas valodā rakstÄ«tais kods atrisinās mÅ«su problēmas vislabākajā iespējamajā veidā.

Labs piemērs mums bija tarantool modulis ar nosaukumu memcached. Tajā izmantotā pieeja ir balstÄ«ta uz to, ka telpā tiek izveidots atseviŔķs lauks, kas norāda kortedža dzÄ«ves ilgumu, citiem vārdiem sakot, ttl. Modulis fonā skenē vietu, salÄ«dzina TTL ar paÅ”reizējo laiku un izlemj, vai dzēst korektoru vai nē. Memcached moduļa kods ir vienkārÅ”s un elegants, taču pārāk vispārÄ«gs. Pirmkārt, tajā nav ņemts vērā indeksa veids, kas tiek pārmeklēts un dzēsts. Otrkārt, katrā piegājienā tiek skenēti visi korteži, kuru skaits var bÅ«t diezgan liels. Un, ja modulÄ«, kuram beidzies derÄ«guma termiņŔ, tika atrisināta pirmā problēma (koka indekss tika sadalÄ«ts atseviŔķā klasē), tad otrajam joprojām netika pievērsta uzmanÄ«ba. Å ie trÄ«s punkti iepriekÅ” noteica izvēli par labu sava koda rakstÄ«Å”anai.

Apraksts

Tarantool dokumentācijai ir ļoti laba pamācÄ«ba par to, kā rakstÄ«t savas saglabātās procedÅ«ras C valodā. Vispirms iesaku jums ar to iepazÄ«ties, lai saprastu tos ieliktņus ar komandām un kodu, kas parādÄ«sies tālāk. Ir arÄ« vērts pievērst uzmanÄ«bu atsauce objektiem, kas ir pieejami, rakstot savu ierobežoto moduli, proti kaste, Ŕķiedra, indekss Šø txn.

Sāksim no tālienes un apskatīsim, kā modulis ar ierobežotu derīguma termiņu izskatās no ārpuses:

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)

VienkārŔības labad mēs palaižam tarantool direktorijā, kurā atrodas mÅ«su bibliotēka libcapped-expirationd.so. No bibliotēkas tiek eksportētas divas funkcijas: start un kill. Pirmais solis ir padarÄ«t Ŕīs funkcijas pieejamas no Lua, izmantojot box.schema.func.create un box.schema.user.grant. Pēc tam izveidojiet atstarpi, kuras kortežā bÅ«s tikai trÄ«s lauki: pirmais ir unikāls identifikators, otrais ir e-pasts, bet treÅ”ais ir kortedža kalpoÅ”anas laiks. Mēs izveidojam koka indeksu virs pirmā lauka un saucam to par primāro. Tālāk mēs iegÅ«stam savienojuma objektu ar mÅ«su dzimto bibliotēku.

Pēc sagatavoÅ”anas darba palaidiet starta funkciju:

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

Å is piemērs skenÄ“Å”anas laikā darbosies tieÅ”i tāpat kā modulis, kuram beidzies derÄ«guma termiņŔ, kas ir rakstÄ«ts Lua valodā. Pirmais sākuma funkcijas arguments ir uzdevuma unikālais nosaukums. Otrais ir telpas identifikators. TreÅ”ais ir unikāls indekss, ar kuru tiks dzēsti korteži. Ceturtais ir indekss, ar kuru tiks Ŕķērsoti koreži. Piektais ir kortedža lauka numurs ar kalpoÅ”anas laiku (numerācija sākas no 1, nevis 0!). Sestais un septÄ«tais ir skenÄ“Å”anas iestatÄ«jumi. 1024 ir maksimālais kortežu skaits, ko var skatÄ«t vienā darÄ«jumā. 3600 ā€” pilns skenÄ“Å”anas laiks sekundēs.

Ņemiet vērā, ka piemērā tiek izmantots viens un tas pats indekss pārmeklÄ“Å”anai un dzÄ“Å”anai. Ja tas ir koka indekss, tad pārvietoÅ”anās tiek veikta no mazākās atslēgas uz lielāko. Ja ir kāds cits, piemēram, hash indekss, tad ŔķērsoÅ”ana parasti tiek veikta nejauŔā secÄ«bā. Visas telpas kopas tiek skenētas vienā skenÄ“Å”anas reizē.

Telpā ievietosim vairākus koreŔus, kuru kalpoŔanas laiks ir 60 sekundes:

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}

Pārbaudīsim, vai ievietoŔana bija veiksmīga:

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

Atkārtosim atlasi pēc 60+ sekundēm (skaitot no pirmā kortedža ievietoÅ”anas sākuma) un pārbaudÄ«sim, vai modulis, kuram ir beidzies derÄ«guma termiņŔ, jau ir apstrādāts:

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

Pārtrauksim uzdevumu:

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

ApskatÄ«sim otru piemēru, kur pārmeklÄ“Å”anai tiek izmantots atseviŔķs rādÄ«tājs:

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)

Å eit viss ir tāds pats kā pirmajā piemērā, ar dažiem izņēmumiem. Mēs izveidojam koka indeksu virs treŔā lauka un saucam to par exp. Å im indeksam nav jābÅ«t unikālam atŔķirÄ«bā no indeksa, ko sauc par primāro. ApmeklÄ“Å”ana tiks veikta pēc exp indeksa, un dzÄ“Å”ana tiks veikta ar primāro. Mēs atceramies, ka iepriekÅ” abi tika veikti, izmantojot tikai primāro indeksu.

Pēc sagatavoÅ”anas darba mēs palaižam starta funkciju ar jauniem argumentiem:

capped_connection:call('libcapped-expirationd.start', {'indexed', box.space.tester.id, box.space.tester.index.primary, box.space.tester.index.exp, 3, 1024, 3600})

Atkal ievietosim telpā vairākas korteces ar 60 sekunžu kalpoŔanas laiku:

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}

Pēc 30 sekundēm, pēc analoģijas, mēs pievienosim vēl dažus virknes:

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}

Pārbaudīsim, vai ievietoŔana bija veiksmīga:

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

Atkārtosim atlasi pēc 60+ sekundēm (skaitot no pirmā kortedža ievietoÅ”anas sākuma) un pārbaudÄ«sim, vai modulis, kuram ir beidzies derÄ«guma termiņŔ, jau ir apstrādāts:

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

Vietā joprojām ir palikuÅ”i daži elementi, kuriem atlikuÅ”as vēl aptuveni 30 sekundes. Turklāt skenÄ“Å”ana tika pārtraukta, pārejot no kortedža ar ID 2 un kalpoÅ”anas laiku 1576421257 uz kortei ar ID 3 un kalpoÅ”anas laiku 1576421287. Korekti ar kalpoÅ”anas laiku 1576421287 vai vairāk netika skenēti exp indeksa atslēgu secÄ«bas dēļ. Tas ir ietaupÄ«jums, ko gribējām panākt paŔā sākumā.

Pārtrauksim uzdevumu:

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

IevieŔana

Labākais veids, kā pastāstÄ«t par visām projekta iezÄ«mēm, ir tā sākotnējais avots. kods! Publikācijas ietvaros mēs koncentrēsimies tikai uz svarÄ«gākajiem punktiem, proti, kosmosa apieÅ”anas algoritmiem.

Argumenti, ko nododam sākuma metodei, tiek saglabāti struktūrā, ko sauc par 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 atribÅ«ts ir uzdevuma nosaukums. AtribÅ«ts space_id ir telpas identifikators. AtribÅ«ts rm_index_id ir unikālā indeksa identifikators, ar kuru tiks dzēsti korteži. AtribÅ«ts it_index_id ir indeksa identifikators, ar kuru tiks Ŕķērsoti koreži. AtribÅ«ts it_index_type ir indeksa veids, ar kuru tiks Ŕķērsoti korteži. AtribÅ«ts filed_no ir kortedža lauka numurs ar kalpoÅ”anas laiku. AtribÅ«ts scan_size ir maksimālais korežu skaits, kas tiek skenētas vienā darÄ«jumā. AtribÅ«ts scan_time ir pilns skenÄ“Å”anas laiks sekundēs.

Mēs neapsvērsim argumentu parsÄ“Å”anu. Tas ir rÅ«pÄ«gs, bet vienkārÅ”s darbs, ar kuru bibliotēka jums palÄ«dzēs msgpuck. GrÅ«tÄ«bas var rasties tikai ar indeksiem, kas tiek nodoti no Lua kā sarežģītas datu struktÅ«ras ar mp_map tipu, nevis izmantojot vienkārÅ”us veidus mp_bool, mp_double, mp_int, mp_uint un mp_array. Bet nav nepiecieÅ”ams parsēt visu indeksu. Jums vienkārÅ”i jāpārbauda tā unikalitāte, jāaprēķina veids un jāizņem identifikators.

Mēs uzskaitām visu parsÄ“Å”anai izmantoto funkciju prototipus:

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

Tagad pāriesim pie vissvarÄ«gākās lietas - atstarpes apieÅ”anas un koreÅ”u dzÄ“Å”anas loÄ£ikas. Katrs koreÅ”u bloks, kas nav lielāks par scan_size, tiek skenēts un modificēts vienā transakcijā. Ja tas ir veiksmÄ«gs, Å”is darÄ«jums tiek veikts; ja rodas kļūda, tas tiek atcelts. Pēdējais arguments funkcijai expirationd_iterate ir rādÄ«tājs uz iteratoru, no kura sākas vai turpinās skenÄ“Å”ana. Å is iterators tiek palielināts iekŔēji, lÄ«dz rodas kļūda, beidzas vieta vai procesu nav iespējams apturēt iepriekÅ”. Funkcija expirationd_expired pārbauda kortedža kalpoÅ”anas laiku, expirationd_delete dzÄ“Å” koreÅ”u, expirationd_breakable pārbauda, ā€‹ā€‹vai mums ir jāturpina.

Expirationd_iterate funkcijas kods:

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

Funkcijas kods 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 funkcijas kods:

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

Expirated_breakable funkcijas kods:

static bool
expirationd_breakable(struct expirationd_task *task)
{
  return task->it_index_id != task->rm_index_id && task->it_index_type == ITER_GT;
}

Pieteikums

JÅ«s varat apskatÄ«t avota kodu vietnē Å”eit!

Avots: www.habr.com

Pievieno komentāru