Die skryf van ons eie beperkte verval module vir tarantool

Die skryf van ons eie beperkte verval module vir tarantool

'n Tyd gelede het ons gekonfronteer met die probleem om tupels in spasies skoon te maak tarantool. Skoonmaak moes begin word nie wanneer tarantool reeds min geheue gehad het nie, maar vooraf en teen 'n sekere frekwensie. Vir hierdie taak het tarantool 'n module wat in Lua geskryf is genaamd verstryking. Nadat ons hierdie module vir 'n kort tydjie gebruik het, het ons besef dat dit nie vir ons geskik is nie: as gevolg van konstante skoonmaak van groot hoeveelhede data, het Lua in die GC gehang. Daarom het ons daaraan gedink om ons eie beperkte vervalmodule te ontwikkel, met die hoop dat die kode wat in 'n inheemse programmeertaal geskryf is, ons probleme op die beste moontlike manier sou oplos.

'n Goeie voorbeeld vir ons was die tarantool-module genaamd memcached. Die benadering wat daarin gebruik word, is gebaseer op die feit dat 'n aparte veld in die ruimte geskep word, wat die leeftyd van die tupel aandui, met ander woorde, ttl. Die module in die agtergrond skandeer die spasie, vergelyk die TTL met die huidige tyd en besluit of die tupel uitgevee moet word of nie. Die memcached module-kode is eenvoudig en elegant, maar te generies. Eerstens neem dit nie die tipe indeks wat deurkruis en uitgevee word, in ag nie. Tweedens, op elke pas word alle tupels geskandeer, waarvan die aantal redelik groot kan wees. En as die eerste probleem in die verstrykte module opgelos is (die boomindeks is in 'n aparte klas geskei), dan het die tweede een steeds geen aandag gekry nie. Hierdie drie punte het die keuse vooraf bepaal ten gunste van die skryf van my eie kode.

Beskrywing

Die dokumentasie vir tarantool het 'n baie goeie tutoriaal oor hoe om jou gestoorde prosedures in C te skryf. Eerstens stel ek voor dat jy jouself daarmee vertroud maak om daardie insetsels met opdragte en kode wat hieronder sal verskyn, te verstaan. Dit is ook die moeite werd om aandag aan te gee verwysing aan voorwerpe wat beskikbaar is wanneer jy jou eie beperkte module skryf, naamlik boks, vesel, indeks ΠΈ txn.

Kom ons begin van ver af en kyk hoe 'n module met 'n afgeslote vervaldatum van buite af lyk:

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)

Vir eenvoud begin ons tarantool in die gids waar ons libcapped-expirationd.so-biblioteek geleΓ« is. Twee funksies word vanaf die biblioteek uitgevoer: begin en doodmaak. Die eerste stap is om hierdie funksies vanaf Lua beskikbaar te stel deur box.schema.func.create en box.schema.user.grant te gebruik. Skep dan 'n spasie waarvan die tupels slegs drie velde sal bevat: die eerste is 'n unieke identifiseerder, die tweede is e-pos, en die derde is die leeftyd van die tupel. Ons bou 'n boomindeks bo-op die eerste veld en noem dit primΓͺr. Volgende kry ons die verbindingsvoorwerp na ons inheemse biblioteek.

Na die voorbereidende werk, voer die beginfunksie uit:

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

Hierdie voorbeeld sal tydens skandering presies dieselfde werk as die module wat verval het, wat in Lua geskryf is. Die eerste argument vir die beginfunksie is die unieke naam van die taak. Die tweede is die spasie-identifiseerder. Die derde is 'n unieke indeks waardeur tupels uitgevee sal word. Die vierde is die indeks waardeur die tupels deurkruis sal word. Die vyfde is die nommer van die tupelveld met leeftyd (nommering begin by 1, nie 0 nie!). Die sesde en sewende is skandeerinstellings. 1024 is die maksimum aantal tupels wat in 'n enkele transaksie besigtig kan word. 3600 β€” volle skanderingstyd in sekondes.

Let daarop dat die voorbeeld dieselfde indeks gebruik vir deurkruip en uitvee. As dit 'n boomindeks is, word die deurkruising van die kleiner sleutel na die groter een uitgevoer. As daar 'n ander, byvoorbeeld, hash-indeks is, word die deurkruising, as 'n reΓ«l, in ewekansige volgorde uitgevoer. Alle spasie-tupels word in een skandering geskandeer.

Kom ons plaas verskeie tupels in die spasie met 'n leeftyd van 60 sekondes:

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}

Kom ons kyk of die invoeging suksesvol was:

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

Kom ons herhaal die keuse na 60+ sekondes (tel vanaf die begin van die invoeging van die eerste tupel) en sien dat die beperkte verstrykte module reeds verwerk is:

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

Kom ons stop die taak:

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

Kom ons kyk na 'n tweede voorbeeld waar 'n aparte indeks vir die kruip gebruik word:

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 hier is dieselfde as in die eerste voorbeeld, met 'n paar uitsonderings. Ons bou 'n boomindeks bo-op die derde veld en noem dit exp. Hierdie indeks hoef nie uniek te wees nie, anders as die indeks wat primΓͺr genoem word. Traversering sal uitgevoer word deur exp-indeks, en verwydering deur primΓͺre. Ons onthou dat albei voorheen slegs met die primΓͺre indeks gedoen is.

Na die voorbereidende werk, loop ons die beginfunksie met nuwe argumente:

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

Kom ons plaas weer verskeie tupels in die spasie met 'n leeftyd van 60 sekondes:

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}

Na 30 sekondes, na analogie, sal ons nog 'n paar tupels byvoeg:

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}

Kom ons kyk of die invoeging suksesvol was:

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

Kom ons herhaal die keuse na 60+ sekondes (tel vanaf die begin van die invoeging van die eerste tupel) en sien dat die beperkte verstrykte module reeds verwerk is:

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

Daar is nog 'n paar tupels oor in die spasie wat nog sowat 30 sekondes sal hΓͺ om te lewe. Boonop het die skandering gestop toe daar van 'n tupel met 'n ID van 2 en 'n leeftyd van 1576421257 na 'n tupel met 'n ID van 3 en 'n leeftyd van 1576421287 beweeg is. Tupels met 'n leeftyd van 1576421287 of meer is nie geskandeer nie as gevolg van die bestelling van die exp indeks sleutels. Dit is die besparings wat ons heel aan die begin wou behaal.

Kom ons stop die taak:

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

Implementering

Die beste manier om oor al die kenmerke van 'n projek te vertel, is die oorspronklike bron daarvan. kode! As deel van die publikasie sal ons slegs op die belangrikste punte fokus, naamlik ruimteomleidingsalgoritmes.

Die argumente wat ons na die beginmetode deurgee word gestoor in 'n struktuur genaamd 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;
};

Die naam-kenmerk is die naam van die taak. Die space_id kenmerk is die spasie identifiseerder. Die rm_index_id kenmerk is die identifiseerder van die unieke indeks waardeur tuples uitgevee sal word. Die it_index_id kenmerk is die identifiseerder van die indeks waardeur tupels deurkruis sal word. Die it_index_type kenmerk is die tipe indeks waardeur tupels deurkruis sal word. Die filed_no-kenmerk is die nommer van die tuple-veld met leeftyd. Die scan_size-kenmerk is die maksimum aantal tupels wat in een transaksie geskandeer word. Die scan_time-kenmerk is die volle skanderingstyd in sekondes.

Ons sal nie die ontleed van argumente oorweeg nie. Dit is 'n moeisame maar eenvoudige werk waarmee die biblioteek jou sal help boodskappie. Moeilikhede kan slegs ontstaan ​​met indekse wat vanaf Lua as 'n komplekse datastruktuur met die mp_map-tipe oorgedra word, en nie die eenvoudige tipes mp_bool, mp_double, mp_int, mp_uint en mp_array gebruik nie. Maar dit is nie nodig om die hele indeks te ontleed nie. U hoef net die uniekheid daarvan te kontroleer, die tipe te bereken en die identifiseerder te onttrek.

Ons lys die prototipes van alle funksies wat vir ontleding gebruik word:

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

Kom ons gaan nou oor na die belangrikste ding - die logika om spasie te omseil en tupels uit te vee. Elke blok tupels wat nie groter is as scan_size nie, word onder 'n enkele transaksie geskandeer en gewysig. As dit suksesvol is, word hierdie transaksie gepleeg; as fout voorkom, word dit teruggerol. Die laaste argument vir die expirationd_iterate-funksie is 'n wyser na die iterator vanwaar skandering begin of voortgaan. Hierdie iterator word intern verhoog totdat 'n fout voorkom, die spasie opraak of dit nie moontlik is om die proses vooraf te stop nie. Die funksie expirationd_expired kontroleer die leeftyd van 'n tupel, expirationd_delete verwyder 'n tupel, expirationd_breakable kontroleer of ons moet aanbeweeg.

Expirationd_iterate funksie kode:

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

Funksie kode 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 funksie kode:

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

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

Artikels

U kan die bronkode sien by hier!

Bron: will.com

Voeg 'n opmerking