Pagsusulat ng sarili nating naka-cap na expirationd module para sa tarantool

Pagsusulat ng sarili nating naka-cap na expirationd module para sa tarantool

Noong nakaraan, nahaharap tayo sa problema sa paglilinis ng mga tuple sa mga espasyo tarantool. Ang paglilinis ay kailangang simulan hindi kapag ang tarantool ay naubusan na ng memorya, ngunit nang maaga at sa isang tiyak na dalas. Para sa gawaing ito, ang tarantool ay mayroong module na nakasulat sa Lua na tinatawag pag-expire. Matapos gamitin ang module na ito sa maikling panahon, napagtanto namin na hindi ito angkop para sa amin: dahil sa patuloy na paglilinis ng malaking halaga ng data, nag-hang si Lua sa GC. Samakatuwid, naisip namin ang tungkol sa pagbuo ng aming sariling naka-cap na expirationd module, umaasa na ang code na nakasulat sa isang katutubong programming language ay malulutas ang aming mga problema sa pinakamahusay na posibleng paraan.

Isang magandang halimbawa para sa amin ang tinatawag na tarantool module memcached. Ang diskarte na ginamit dito ay batay sa katotohanan na ang isang hiwalay na patlang ay nilikha sa espasyo, na nagpapahiwatig ng buhay ng tuple, sa madaling salita, ttl. Ini-scan ng module sa background ang espasyo, ikinukumpara ang TTL sa kasalukuyang oras at nagpapasya kung tatanggalin ang tuple o hindi. Ang memcached module code ay simple at eleganteng, ngunit masyadong generic. Una, hindi nito isinasaalang-alang ang uri ng index na gina-crawl at tinatanggal. Pangalawa, sa bawat pass lahat ng tuple ay na-scan, ang bilang nito ay maaaring malaki. At kung sa nag-expire na module ang unang problema ay nalutas (ang tree index ay pinaghiwalay sa isang hiwalay na klase), kung gayon ang pangalawa ay hindi pa rin nakatanggap ng anumang pansin. Ang tatlong puntong ito ay paunang natukoy ang pagpili pabor sa pagsulat ng sarili kong code.

ОписаниС

Ang dokumentasyon para sa tarantool ay may napakahusay pagtuturo tungkol sa kung paano isulat ang iyong mga naka-imbak na pamamaraan sa C. Una sa lahat, iminumungkahi kong pamilyar ka dito upang maunawaan ang mga pagsingit na iyon na may mga utos at code na lilitaw sa ibaba. Ito rin ay nagkakahalaga ng pagbibigay pansin sa sanggunian sa mga bagay na available kapag nagsusulat ng sarili mong naka-cap na module, ibig sabihin kahon, hibla, index ΠΈ txn.

Magsimula tayo sa malayo at tingnan kung ano ang hitsura ng naka-cap na expiration na module mula sa labas:

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)

Para sa pagiging simple, inilunsad namin ang tarantool sa direktoryo kung saan matatagpuan ang aming libcapped-expirationd.so library. Dalawang function ang na-export mula sa library: simulan at patayin. Ang unang hakbang ay gawing available ang mga function na ito mula sa Lua gamit ang box.schema.func.create at box.schema.user.grant. Pagkatapos ay lumikha ng isang puwang na ang mga tuple ay maglalaman lamang ng tatlong mga field: ang una ay isang natatanging identifier, ang pangalawa ay ang email, at ang pangatlo ay ang haba ng buhay ng tuple. Bumubuo kami ng tree index sa tuktok ng unang field at tinatawag itong pangunahin. Susunod na makuha namin ang object ng koneksyon sa aming katutubong library.

Pagkatapos ng gawaing paghahanda, patakbuhin ang start function:

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

Ang halimbawang ito ay gagana sa panahon ng pag-scan na eksaktong kapareho ng nag-expire na module, na nakasulat sa Lua. Ang unang argumento sa start function ay ang natatanging pangalan ng gawain. Ang pangalawa ay ang space identifier. Ang pangatlo ay isang natatanging index kung saan ang mga tuple ay tatanggalin. Ang ikaapat ay ang index kung saan ang mga tuple ay dadaanan. Ang panglima ay ang bilang ng field ng tuple na may panghabambuhay (nagsisimula ang pagnunumero sa 1, hindi 0!). Ang ikaanim at ikapito ay mga setting ng pag-scan. Ang 1024 ay ang maximum na bilang ng mga tuple na maaaring matingnan sa isang transaksyon. 3600 β€” buong oras ng pag-scan sa mga segundo.

Tandaan na ang halimbawa ay gumagamit ng parehong index para sa pag-crawl at pagtanggal. Kung ito ay isang index ng puno, kung gayon ang traversal ay isinasagawa mula sa mas maliit na susi hanggang sa mas malaki. Kung mayroong ilang iba pa, halimbawa, hash index, pagkatapos ay ang traversal ay isinasagawa, bilang isang panuntunan, sa random na pagkakasunud-sunod. Ang lahat ng space tuple ay ini-scan sa isang pag-scan.

Magpasok tayo ng ilang tuple sa espasyo na may habang buhay na 60 segundo:

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}

Suriin natin kung matagumpay ang pagpasok:

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

Ulitin natin ang piliin pagkatapos ng 60+ segundo (nagbibilang mula sa simula ng pagpasok ng unang tuple) at tingnan na ang naka-cap na expiration na module ay naproseso na:

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

Itigil natin ang gawain:

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

Tingnan natin ang pangalawang halimbawa kung saan ginagamit ang isang hiwalay na index para sa pag-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)

Ang lahat dito ay kapareho ng sa unang halimbawa, na may ilang mga pagbubukod. Bumubuo kami ng tree index sa ibabaw ng ikatlong field at tinatawag itong exp. Ang index na ito ay hindi kailangang maging natatangi, hindi katulad ng index na tinatawag na pangunahin. Ang traversal ay isasagawa sa pamamagitan ng exp index, at pagtanggal sa pamamagitan ng primary. Natatandaan namin na dati pareho ay ginawa lamang gamit ang pangunahing index.

Pagkatapos ng gawaing paghahanda, pinapatakbo namin ang start function na may mga bagong argumento:

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

Magpasok muli tayo ng ilang tuple sa espasyo na may habang buhay na 60 segundo:

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}

Pagkatapos ng 30 segundo, sa pamamagitan ng pagkakatulad, magdaragdag kami ng ilan pang tuple:

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}

Suriin natin kung matagumpay ang pagpasok:

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

Ulitin natin ang piliin pagkatapos ng 60+ segundo (nagbibilang mula sa simula ng pagpasok ng unang tuple) at tingnan na ang naka-cap na expiration na module ay naproseso na:

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

Mayroon pa ring ilang tuple na natitira sa espasyo na magkakaroon ng humigit-kumulang 30 segundo upang mabuhay. Bukod dito, huminto ang pag-scan nang lumipat mula sa isang tuple na may ID na 2 at panghabambuhay na 1576421257 patungo sa isang tuple na may ID na 3 at panghabambuhay na 1576421287. Ang mga tuple na may habang-buhay na 1576421287 o higit pa ay hindi na-scan dahil sa pag-order ng ang mga exp index key. Ito ang savings na gusto nating makamit sa simula pa lang.

Itigil natin ang gawain:

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

Pagpapatupad

Ang pinakamahusay na paraan upang sabihin ang tungkol sa lahat ng mga tampok ng isang proyekto ay ang orihinal na pinagmulan nito. kodigo! Bilang bahagi ng publikasyon, tututuon lamang namin ang pinakamahalagang punto, katulad ng mga algorithm ng space bypass.

Ang mga argumento na ipinapasa namin sa paraan ng pagsisimula ay naka-imbak sa isang istraktura na tinatawag na 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;
};

Ang katangian ng pangalan ay ang pangalan ng gawain. Ang space_id attribute ay ang space identifier. Ang attribute na rm_index_id ay ang identifier ng natatanging index kung saan matatanggal ang mga tuple. Ang attribute na it_index_id ay ang identifier ng index kung saan dadaanan ang mga tuple. Ang attribute na it_index_type ay ang uri ng index kung saan dadaanan ang mga tuple. Ang filed_no attribute ay ang bilang ng tuple field na may panghabambuhay. Ang katangiang scan_size ay ang maximum na bilang ng mga tuple na na-scan sa isang transaksyon. Ang katangian ng scan_time ay ang buong oras ng pag-scan sa mga segundo.

Hindi namin isasaalang-alang ang pag-parse ng mga argumento. Ito ay isang maingat ngunit simpleng trabaho, kung saan ang library ay makakatulong sa iyo msgpuck. Ang mga kahirapan ay maaari lamang lumitaw sa mga index na ipinasa mula sa Lua bilang isang kumplikadong istraktura ng data na may uri ng mp_map, at hindi gumagamit ng mga simpleng uri na mp_bool, mp_double, mp_int, mp_uint at mp_array. Ngunit hindi na kailangang i-parse ang buong index. Kailangan mo lamang suriin ang pagiging natatangi nito, kalkulahin ang uri at kunin ang identifier.

Inilista namin ang mga prototype ng lahat ng mga function na ginagamit para sa pag-parse:

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

Ngayon ay lumipat tayo sa pinakamahalagang bagay - ang lohika ng pag-bypass sa espasyo at pagtanggal ng mga tuple. Ang bawat bloke ng tuple na hindi hihigit sa scan_size ay ini-scan at binago sa ilalim ng iisang transaksyon. Kung matagumpay, gagawin ang transaksyong ito; kung may nangyaring error, ibabalik ito. Ang huling argumento sa expirationd_iterate function ay isang pointer sa iterator kung saan magsisimula o magpapatuloy ang pag-scan. Ang iterator na ito ay dinadagdagan sa loob hanggang sa magkaroon ng error, maubos ang espasyo, o hindi posibleng ihinto ang proseso nang maaga. Sinusuri ng function na expirationd_expired ang buhay ng isang tuple, tinatanggal ng expirationd_delete ang isang tuple, sinusuri ng expirationd_breakable kung kailangan nating magpatuloy.

Expirationd_iterate function code:

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

Function code 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 function code:

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

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

App

Maaari mong tingnan ang source code sa dito!

Pinagmulan: www.habr.com

Magdagdag ng komento