Tarantool-erako gure mugatutako iraungitze modulua idazten

Tarantool-erako gure mugatutako iraungitze modulua idazten

Duela denbora pixka bat espazioetan tupleak garbitzearen arazoarekin topo egin genuen tarantoola. Garbiketa hasi behar zen ez tarantool jada memoria agortzen ari zenean, aldez aurretik eta maiztasun jakin batean baizik. Zeregin horretarako, tarantool-ek Lua-n idatzitako modulu bat du iraungitzea. Modulu hau denbora laburrean erabili ondoren, guretzat egokia ez zela konturatu ginen: datu kopuru handien etengabeko garbiketa zela eta, Lua GC-n zintzilikatu zen. Hori dela eta, iraungitze-modulu propioa garatzea pentsatu genuen, jatorrizko programazio-lengoaia batean idatzitako kodeak gure arazoak ahalik eta ondoen konponduko zituelakoan.

Adibide ona guretzat izeneko tarantool modulua izan zen memcached. Bertan erabiltzen den planteamendua espazioan eremu bereizi bat sortzen dela oinarritzen da, eta horrek tuplearen iraupena adierazten du, hau da, ttl. Atzeko planoan dagoen moduluak espazioa eskaneatzen du, TTL uneko orduarekin alderatzen du eta tupla ezabatu ala ez erabakitzen du. Memcached moduluaren kodea sinplea eta dotorea da, baina generikoegia. Lehenik eta behin, ez du kontuan hartzen arakatzen eta ezabatzen ari den indize mota. Bigarrenik, pase bakoitzean tupla guztiak eskaneatzen dira, eta horien kopurua nahiko handia izan daiteke. Eta iraungitako moduluan lehenengo arazoa konpondu bazen (zuhaitz-indizea klase bereizi batean banatu zen), bigarrenak oraindik ez zuen arretarik jaso. Hiru puntu hauek nire kodea idaztearen aldeko hautua aldez aurretik erabaki zuten.

Description

Tarantoolaren dokumentazioak oso ona du tutoretza C-n gordetako prozedurak nola idatzi buruz. Lehenik eta behin, ezagutzea gomendatzen dizut behean agertuko diren komando eta kodea duten txertaketa horiek ulertzeko. Arreta jartzea ere merezi du erreferentzia Zure modulua idaztean erabilgarri dauden objektuei, hots koadroan, zuntz, indizea ΠΈ txn.

Has gaitezen urrunetik eta begira gaitezen iraungitze-modulu bat kanpotik nolakoa den:

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)

Sinpletasuna lortzeko, tarantool abiarazten dugu gure libcapped-expirationd.so liburutegia dagoen direktorioan. Liburutegitik bi funtzio esportatzen dira: hasi eta hil. Lehen urratsa funtzio hauek Lua-tik eskuragarri jartzea da, box.schema.func.create eta box.schema.user.grant erabiliz. Ondoren, sortu espazio bat zeinen tuplek hiru eremu baino ez dituzten izango: lehenengoa identifikatzaile bakarra da, bigarrena posta elektronikoa eta hirugarrena tuplaren bizitza. Lehenengo eremuaren gainean zuhaitz-indize bat eraikitzen dugu eta lehen deitzen diogu. Ondoren, konexio objektua gure jatorrizko liburutegira jasoko dugu.

Prestaketa lanak egin ondoren, exekutatu hasiera funtzioa:

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

Adibide honek eskaneatzean iraungitako moduluaren berdin funtzionatuko du, Lua-n idatzita dagoena. Hasierako funtzioaren lehen argumentua zereginaren izen bakarra da. Bigarrena espazioaren identifikatzailea da. Hirugarrena tuplak ezabatuko diren indize bakarra da. Laugarrena tuplak zeharkatuko dituen indizea da. Bosgarrena bizitza-denbora duen tupla eremuaren zenbakia da (zenbaketa 1etik hasten da, ez 0tik!). Seigarrena eta zazpigarrena eskaneatzeko ezarpenak dira. 1024 transakzio bakarrean ikus daitezkeen tupla kopurua da. 3600 β€” eskaneatu denbora osoa segundotan.

Kontuan izan adibideak indize bera erabiltzen duela arakatzeko eta ezabatzeko. Hau zuhaitz-indizea bada, zeharkatzea gako txikiagotik handiagora egiten da. Beste bat bada, adibidez, hash indizea, orduan zeharkatzea, oro har, ausazko ordenan egiten da. Espazio-tupla guztiak eskaneatzen dira eskaneatu batean.

Sar ditzagun hainbat tupla espazioan 60 segundoko bizitzarekin:

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}

Egiaztatu dezagun txertaketa arrakastatsua izan dela:

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

Errepikatu dezagun aukeraketa 60 segundo baino gehiago igaro ondoren (lehen tuplaren txertaketaren hasieratik zenbatuta) eta ikus dezagun mugatutako iraungitze modulua dagoeneko prozesatu dela:

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

Utz dezagun zeregina:

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

Ikus dezagun bigarren adibide bat arakatzeko indize bereizia erabiltzen den:

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)

Hemen dena lehen adibideko berdina da, salbuespenak salbuespen. Hirugarren eremuaren gainean zuhaitz-indize bat eraikitzen dugu eta exp deitzen diogu. Indize honek ez du zertan bakarra izan, primario deritzon indizeak ez bezala. Zeharkaldia exp indizearen bidez egingo da, eta ezabaketa primarioaren bidez. Gogoratzen dugu lehen biak indize primarioa soilik erabiliz egiten zirela.

Prestaketa lanaren ondoren, hasierako funtzioa exekutatzen dugu argumentu berriekin:

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

Sar ditzagun hainbat tupla espazioan berriro 60 segundoko bizitzarekin:

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 segundo igaro ondoren, analogiaz, tupla batzuk gehituko ditugu:

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}

Egiaztatu dezagun txertaketa arrakastatsua izan dela:

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

Errepikatu dezagun aukeraketa 60 segundo baino gehiago igaro ondoren (lehen tuplaren txertaketaren hasieratik zenbatuta) eta ikus dezagun mugatutako iraungitze modulua dagoeneko prozesatu dela:

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

Oraindik 30 segundo inguru bizitzeko gehiago izango duten tupla batzuk geratzen dira espazioan. Gainera, 2 ID eta 1576421257 bizitzako tupla batetik 3 ID eta 1576421287 bizitzako tupla batera igarotzean gelditu zen. exp indizearen teklak. Hau da hasiera-hasieran lortu nahi genuen aurrezkia.

Utz dezagun zeregina:

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

Inplementazioa

Proiektu baten ezaugarri guztiak kontatzeko modurik onena jatorrizko iturria da. kodea! Argitalpenaren zati gisa, puntu garrantzitsuenetan bakarrik zentratuko gara, hots, espazioaren saihesbide-algoritmoetan.

Hasiera metodoari pasatzen dizkiogun argumentuak expirationd_task izeneko egitura batean gordetzen dira:

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 atributua zereginaren izena da. space_id atributua espazioaren identifikatzailea da. rm_index_id atributua tuplak ezabatuko diren indize esklusiboaren identifikatzailea da. it_index_id atributua tuplak zeharkatuko dituen indizearen identifikatzailea da. it_index_type atributua tuplak zeharkatuko dituen indize mota da. Filed_no atributua bizitza osoa duen tupla eremuaren zenbakia da. Scan_size atributua transakzio batean eskaneatzen den gehieneko tupla kopurua da. Scan_time atributua eskaneatu denbora osoa segundotan da.

Ez dugu kontuan hartuko argumentuak analizatzea. Lan neketsua baina sinplea da, eta horrekin liburutegiak lagunduko dizu msgpuck. Zailtasunak Luatik mp_map motako datu-egitura konplexu gisa pasatzen diren indizeekin soilik sor daitezke, eta mp_bool, mp_double, mp_int, mp_uint eta mp_array mota sinpleak ez erabiliz. Baina ez dago indize osoa analizatu beharrik. Bere berezitasuna egiaztatu, mota kalkulatu eta identifikatzailea atera besterik ez duzu behar.

Analisirako erabiltzen diren funtzio guztien prototipoak zerrendatzen ditugu:

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

Orain joan gaitezen garrantzitsuenera: espazioa saihestu eta tuplak ezabatzearen logika. Scan_size baino handiagoa ez den tupla-bloke bakoitza eskaneatu eta aldatzen da transakzio bakar baten arabera. Arrakasta bada, transakzio hau konprometituko da; errorea gertatzen bada, atzera egingo da. expirationd_iterate funtzioaren azken argumentua eskaneatzea hasten edo jarraitzen duen iteragailuaren erakuslea da. Iteratzaile hau barnean handitzen da errore bat gertatu arte, espazioa agortu arte edo ezin da prozesua aldez aurretik geldiarazi. expirationd_expired funtzioak tupla baten iraupena egiaztatzen du, expirationd_delete-k tupla bat ezabatzen du, expirationd_breakable-k aurrera egin behar dugun egiaztatzen du.

Expirationd_iterate funtzioaren kodea:

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

Funtzio-kodea 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 funtzio-kodea:

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

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

Eskaera

Iturburu kodea hemen ikus dezakezu Hemen!

Iturria: www.habr.com

Gehitu iruzkin berria