Skriuwen fan ús eigen capped expirationd module foar tarantool

Skriuwen fan ús eigen capped expirationd module foar tarantool

In skoft lyn hawwe wy te krijen mei it probleem fan it skjinmeitsjen fan tupels yn romten tarantool. It skjinmeitsjen moast wurde begon net doe't tarantool al út it ûnthâld rûn, mar foarôf en op in bepaalde frekwinsje. Foar dizze taak hat tarantool in module skreaun yn Lua neamd ferfaldatum. Nei it brûken fan dizze module foar in koarte tiid, wy realisearre dat it wie net geskikt foar ús: troch konstante skjinmeitsjen fan grutte hoemannichten gegevens, Lua hong yn 'e GC. Dêrom hawwe wy tochten oer it ûntwikkeljen fan ús eigen capped expiration module, yn 'e hoop dat de koade skreaun yn in memmetaal programmeartaal soe oplosse ús problemen op de bêste mooglik wize.

In goed foarbyld foar ús wie de tarantool-module neamd memcached. De oanpak dy't dêryn brûkt wurdt, is basearre op it feit dat yn 'e romte in apart fjild ûntstiet, dat it libben fan 'e tupel oanjout, mei oare wurden, ttl. De module op 'e eftergrûn scant de romte, fergeliket de TTL mei de aktuele tiid en beslút oft de tuple wiskje of net. De memcached modulekoade is ienfâldich en elegant, mar te generyk. Earst hâldt it gjin rekken mei it type yndeks dat wurdt krûpt en wiske. Twads, op elke pas wurde alle tuples skansearre, wêrfan it oantal frij grut kin wêze. En as yn 'e ferrinnende module it earste probleem oplost waard (de beamyndeks waard opdield yn in aparte klasse), dan krige de twadde noch gjin oandacht. Dizze trije punten bepale de kar foar it skriuwen fan myn eigen koade.

beskriuwing

De dokumintaasje foar tarantool hat in hiel goed tutorial oer hoe't jo jo bewarre prosedueres yn C skriuwe. Alderearst stel ik foar dat jo jo dermei fertroud meitsje om dy ynfoegingen te begripen mei kommando's en koade dy't hjirûnder ferskine. It is ek wurdich omtinken te jaan referinsje oan objekten dy't beskikber binne by it skriuwen fan jo eigen capped module, nammentlik doaze, fibre, yndeks и txn.

Litte wy fan 'e fierte begjinne en sjoch nei hoe't in ôfsletten module fan bûten útsjocht:

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)

Foar ienfâld lansearje wy tarantool yn 'e map wêr't ús libcapped-expirationd.so bibleteek leit. Twa funksjes wurde eksportearre út de bibleteek: start en kill. De earste stap is om dizze funksjes beskikber te meitsjen fan Lua mei help fan box.schema.func.create en box.schema.user.grant. Meitsje dan in romte wêrfan de tupels mar trije fjilden sille befetsje: de earste is in unike identifier, de twadde is e-post, en de tredde is it libben fan 'e tuple. Wy bouwe in beam yndeks boppe op it earste fjild en neame it primêr. Dêrnei krije wy it ferbiningsobjekt nei ús eigen bibleteek.

Nei it tariedende wurk útfiere de startfunksje:

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

Dit foarbyld sil wurkje by it scannen krekt itselde as de ferrinne module, dy't skreaun is yn Lua. It earste argumint foar de startfunksje is de unike namme fan 'e taak. De twadde is de romte identifier. De tredde is in unike yndeks wêrmei't tuples sille wurde wiske. De fjirde is de yndeks wêrmei't de tupels oerstutsen wurde. De fyfde is it nûmer fan it tuple fjild mei lifetime (nûmering begjint fan 1, net 0!). De sechsde en sânde binne scanynstellingen. 1024 is it maksimum oantal tuples dat kin wurde besjoen yn ien transaksje. 3600 - folsleine scantiid yn sekonden.

Tink derom dat it foarbyld deselde yndeks brûkt foar it krûpen en wiskjen. As dit in beam-yndeks is, dan wurdt de trochgong útfierd fan 'e lytsere kaai nei de gruttere. As d'r in oare is, bygelyks, hash-yndeks, dan wurdt de trochgong útfierd, as regel, yn willekeurige folchoarder. Alle romte tuples wurde skansearre yn ien scan.

Litte wy ferskate tupels yn 'e romte ynfoegje mei in libben fan 60 sekonden:

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}

Litte wy kontrolearje dat de ynfoegje suksesfol wie:

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

Litte wy de seleksje werhelje nei 60+ sekonden (tellen fan it begjin fan it ynfoegjen fan 'e earste tuple) en sjoch dat de ôfsletten module al ferwurke is:

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

Litte wy de taak stopje:

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

Litte wy nei in twadde foarbyld sjen wêr't in aparte yndeks wurdt brûkt foar de crawl:

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)

Alles is hjir itselde as yn it earste foarbyld, mei in pear útsûnderings. Wy bouwe in beam yndeks boppe op it tredde fjild en neame it exp. Dizze yndeks hoecht net unyk te wêzen, yn tsjinstelling ta de yndeks neamd primêr. Traversal sil wurde útfierd troch exp-yndeks, en wiskjen troch primêr. Wy ûnthâlde dat earder beide waarden dien allinnich mei help fan de primêre yndeks.

Nei it tariedende wurk rinne wy ​​de startfunksje mei nije arguminten:

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

Litte wy wer ferskate tupels yn 'e romte ynfoegje mei in libben fan 60 sekonden:

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}

Nei 30 sekonden, troch analogy, sille wy in pear mear tuples tafoegje:

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}

Litte wy kontrolearje dat de ynfoegje suksesfol wie:

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

Litte wy de seleksje werhelje nei 60+ sekonden (tellen fan it begjin fan it ynfoegjen fan 'e earste tuple) en sjoch dat de ôfsletten module al ferwurke is:

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

D'r binne noch wat tupels oer yn 'e romte dy't noch sa'n 30 sekonden hawwe om te libjen. Boppedat stoppe de scan by it ferpleatsen fan in tuple mei in ID fan 2 en in libben fan 1576421257 nei in tuple mei in ID fan 3 en in lifetime fan 1576421287. Tuples mei in libben fan 1576421287 of mear waarden net skansearre fanwegen it bestellen fan de exp index kaaien. Dit is de besparring dy't wy oan it begjin realisearje woene.

Litte wy de taak stopje:

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

Ymplemintaasje

De bêste manier om te fertellen oer alle funksjes fan in projekt is de oarspronklike boarne. code! As ûnderdiel fan 'e publikaasje sille wy allinich rjochtsje op' e wichtichste punten, nammentlik romte-bypass-algoritmen.

De arguminten dy't wy trochjaan oan 'e startmetoade wurde opslein yn in struktuer neamd 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;
};

De namme attribút is de namme fan de taak. It attribút space_id is de romte-identifikaasje. It attribút rm_index_id is de identifier fan 'e unike yndeks wêrmei't tuples sille wurde wiske. It it_index_id-attribút is de identifier fan 'e yndeks wêrmei't tuples trochrinne wurde. It attribút it_index_type is it type yndeks wêrmei't tuples trochrinne wurde. It attribút filed_no is it nûmer fan it tuplefjild mei it libben. It scan_size-attribút is it maksimum oantal tuples dat yn ien transaksje wurdt skansearre. It scan_time-attribút is de folsleine scantiid yn sekonden.

Wy sille it parsearjen fan arguminten net beskôgje. Dit is in mânske mar ienfâldige baan, wêrmei't de biblioteek jo helpt msgpuck. Swierrichheden kinne allinnich ûntstean mei yndeksen dy't wurde trochjûn út Lua as in komplekse gegevens struktuer mei de mp_map type, en net mei help fan de ienfâldige typen mp_bool, mp_double, mp_int, mp_uint en mp_array. Mar it is net nedich om de hiele yndeks te parsearjen. Jo moatte gewoan de eigenheid kontrolearje, it type berekkenje en de identifier ekstrahearje.

Wy listje de prototypen fan alle funksjes dy't brûkt wurde foar 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);

No litte wy oergean nei it wichtichste ding - de logika fan romte omgean en tuples wiskje. Elk blok fan tuples net grutter dan scan_size wurdt skansearre en wizige ûnder ien transaksje. As suksesfol, wurdt dizze transaksje ynset; as flater optreedt, wurdt it weromrôle. It lêste argumint foar de funksje expirationd_iterate is in oanwizer nei de iterator wêrfan it scannen begjint of trochgiet. Dizze iterator wurdt yntern ferhege oant in flater optreedt, de romte rint op, of it is net mooglik om it proses foarôf te stopjen. De funksje expirationd_expired kontrolearret it libben fan in tuple, expirationd_delete wisket in tuple, expirationd_breakable kontrolearret oft wy moatte fierder.

Expirationd_iterate funksjekoade:

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

Funksjekoade 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 funksje koade:

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

Expirationd_breakable funksje koade:

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

Applikaasje

Jo kinne de boarnekoade besjen op hjir!

Boarne: www.habr.com

Add a comment