Kuandika moduli yetu wenyewe iliyopitwa na wakati kwa tarantool

Kuandika moduli yetu wenyewe iliyopitwa na wakati kwa tarantool

Wakati fulani uliopita tulikabiliwa na tatizo la kusafisha tuples katika nafasi tarantool. Usafishaji ulipaswa kuanza sio wakati tarantool ilikuwa tayari kukosa kumbukumbu, lakini mapema na kwa mzunguko fulani. Kwa kazi hii, tarantool ina moduli iliyoandikwa kwa Lua inayoitwa kumalizika muda wake. Baada ya kutumia moduli hii kwa muda mfupi, tuligundua kuwa haifai kwetu: kutokana na kusafisha mara kwa mara kwa kiasi kikubwa cha data, Lua alipachikwa kwenye GC. Kwa hivyo, tulifikiria juu ya kuunda moduli yetu iliyomalizika muda wake, tukitumaini kwamba msimbo ulioandikwa katika lugha ya asili ya programu ungetatua matatizo yetu kwa njia bora zaidi.

Mfano mzuri kwetu ulikuwa moduli ya tarantool inayoitwa imechapwa. Njia inayotumiwa ndani yake inategemea ukweli kwamba shamba tofauti linaundwa katika nafasi, ambayo inaonyesha maisha ya tuple, kwa maneno mengine, ttl. Sehemu ya chinichini huchanganua nafasi, inalinganisha TTL na wakati wa sasa na kuamua kama kufuta nakala au la. Msimbo wa moduli iliyohifadhiwa ni rahisi na maridadi, lakini ni ya jumla sana. Kwanza, haizingatii aina ya faharisi ambayo inatambazwa na kufutwa. Pili, kwa kila kupita nakala zote zinachanganuliwa, idadi ambayo inaweza kuwa kubwa kabisa. Na ikiwa katika moduli iliyomalizika muda wake shida ya kwanza ilitatuliwa (faharisi ya mti iligawanywa katika darasa tofauti), basi ya pili bado haikupokea uangalifu wowote. Hoja hizi tatu ziliamua chaguo la kupendelea kuandika nambari yangu mwenyewe.

Description

Nyaraka za tarantool zina nzuri sana mafunzo kuhusu jinsi ya kuandika taratibu zako zilizohifadhiwa katika C. Kwanza kabisa, napendekeza ujitambulishe nayo ili kuelewa kuingiza hizo kwa amri na kanuni ambazo zitaonekana hapa chini. Inafaa pia kuzingatia kumbukumbu kwa vitu vinavyopatikana wakati wa kuandika moduli yako mwenyewe iliyofungwa, ambayo ni sanduku, fiber, index ΠΈ txn.

Wacha tuanze kutoka mbali na tuangalie jinsi moduli iliyomalizika muda wake inaonekana kutoka nje:

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)

Kwa urahisi, tunazindua tarantool katika saraka ambapo maktaba yetu ya libcapped-expirationd.so iko. Vitendaji viwili vinasafirishwa kutoka kwa maktaba: anza na kuua. Hatua ya kwanza ni kufanya vipengele hivi vipatikane kutoka kwa Lua kwa kutumia box.schema.func.create na box.schema.user.grant. Kisha unda nafasi ambayo nakala zake zitakuwa na sehemu tatu tu: ya kwanza ni kitambulisho cha kipekee, ya pili ni barua pepe, na ya tatu ni maisha ya nakala. Tunajenga index ya mti juu ya shamba la kwanza na kuiita msingi. Ifuatayo tunapata kitu cha uunganisho kwenye maktaba yetu ya asili.

Baada ya kazi ya maandalizi, endesha kazi ya kuanza:

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

Mfano huu utafanya kazi wakati wa skanning sawa kabisa na moduli iliyoisha muda wake, ambayo imeandikwa kwa Lua. Hoja ya kwanza kwa chaguo la kukokotoa la kuanza ni jina la kipekee la kazi. Ya pili ni kitambulisho cha nafasi. Ya tatu ni faharasa ya kipekee ambayo nakala zitafutwa. Ya nne ni fahirisi ambayo nakala zitapitiwa. Ya tano ni nambari ya uwanja wa tuple na maisha yote (kuhesabu huanza kutoka 1, sio 0!). Ya sita na ya saba ni mipangilio ya skanning. 1024 ndio idadi ya juu zaidi ya nakala zinazoweza kutazamwa katika muamala mmoja. 3600 - muda kamili wa kuchanganua kwa sekunde.

Kumbuka kuwa mfano hutumia faharasa sawa kwa kutambaa na kufuta. Ikiwa hii ni index ya mti, basi traversal inafanywa kutoka kwa ufunguo mdogo hadi mkubwa. Ikiwa kuna nyingine, kwa mfano, faharisi ya hashi, basi upitishaji unafanywa, kama sheria, kwa mpangilio wa nasibu. Nakala zote za nafasi huchanganuliwa katika tambazo moja.

Wacha tuingize nakala kadhaa kwenye nafasi na maisha ya sekunde 60:

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}

Wacha tuangalie ikiwa uingizaji ulifanikiwa:

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

Wacha turudie chaguo baada ya sekunde 60+ (kuhesabu tangu mwanzo wa kuingizwa kwa nakala ya kwanza) na tuone kuwa moduli iliyomalizika muda wake tayari imechakatwa:

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

Wacha tusitishe kazi:

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

Wacha tuangalie mfano wa pili ambapo faharisi tofauti inatumika kwa utambazaji:

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)

Kila kitu hapa ni sawa na katika mfano wa kwanza, isipokuwa chache. Tunaunda index ya mti juu ya shamba la tatu na kuiita exp. Faharasa hii si lazima iwe ya kipekee, tofauti na faharasa inayoitwa msingi. Upitishaji utatekelezwa kwa faharasa ya exp, na kufutwa kwa msingi. Tunakumbuka kwamba hapo awali zote mbili zilifanywa kwa kutumia tu faharasa ya msingi.

Baada ya kazi ya maandalizi, tunaendesha kazi ya kuanza na hoja mpya:

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

Wacha tuingize nakala kadhaa kwenye nafasi tena na maisha ya sekunde 60:

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}

Baada ya sekunde 30, kwa mlinganisho, tutaongeza nakala chache zaidi:

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}

Wacha tuangalie ikiwa uingizaji ulifanikiwa:

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

Wacha turudie chaguo baada ya sekunde 60+ (kuhesabu tangu mwanzo wa kuingizwa kwa nakala ya kwanza) na tuone kuwa moduli iliyomalizika muda wake tayari imechakatwa:

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

Bado kuna nakala zilizosalia kwenye nafasi ambazo zitakuwa na takriban sekunde 30 za kuishi. Zaidi ya hayo, skanisho ilikoma wakati wa kuhama kutoka kwenye tuple yenye kitambulisho cha 2 na maisha ya 1576421257 hadi kwenye tuple yenye kitambulisho cha 3 na maisha ya 1576421287. Tuples zilizo na maisha ya 1576421287 au zaidi hazikuchanganuliwa kutokana na kuagiza funguo za exp index. Hii ni akiba ambayo tulitaka kufikia mwanzoni kabisa.

Wacha tusitishe kazi:

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

Utekelezaji

Njia bora ya kusema juu ya vipengele vyote vya mradi ni chanzo chake cha asili. code! Kama sehemu ya uchapishaji, tutazingatia tu pointi muhimu zaidi, yaani, algorithms ya bypass ya nafasi.

Hoja tunazopitisha kwa njia ya kuanza zimehifadhiwa katika muundo unaoitwa expiration_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;
};

Sifa ya jina ni jina la kazi. Sifa ya kitambulisho cha nafasi ni kitambulisho cha nafasi. rm_index_id sifa ni kitambulisho cha faharasa ya kipekee ambayo nakala zitafutwa. Sifa ya it_index_id ni kitambulisho cha faharasa ambayo nakala zitapitishwa. Sifa ya it_index_type ni aina ya faharasa ambayo nakala zitapitiwa. Sifa ya filed_no ni nambari ya sehemu ya tuple yenye maisha yote. Sifa ya ukubwa_changanuzi ni idadi ya juu zaidi ya nakala ambazo huchanganuliwa katika muamala mmoja. Sifa ya_muda wa kuchanganua ni muda kamili wa kuchanganua kwa sekunde.

Hatutazingatia kuchanganua hoja. Hii ni kazi ngumu lakini rahisi, ambayo maktaba itakusaidia msgpuck. Ugumu unaweza tu kutokea kwa faharasa zinazopitishwa kutoka Lua kama muundo changamano wa data na aina ya mp_map, na bila kutumia aina rahisi mp_bool, mp_double, mp_int, mp_uint na mp_array. Lakini hakuna haja ya kuchanganua index nzima. Unahitaji tu kuangalia upekee wake, uhesabu aina na utoe kitambulisho.

Tunaorodhesha prototypes za kazi zote ambazo hutumiwa kwa uchanganuzi:

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

Sasa hebu tuendelee kwenye jambo muhimu zaidi - mantiki ya kupitisha nafasi na kufuta tuples. Kila sehemu ya nakala zisizozidi ukubwa wa scan_size huchanganuliwa na kurekebishwa chini ya muamala mmoja. Ikifaulu, muamala huu unafanywa; hitilafu ikitokea, inarejeshwa. Hoja ya mwisho kwa chaguo za kukokotoa za expirationd_iterate ni kielekezi kwa kiboreshaji ambapo uchanganuzi huanza au kuendelea. Kirudishi hiki kinaongezwa ndani hadi hitilafu hutokea, nafasi itaisha, au haiwezekani kusimamisha mchakato mapema. Chaguo za kukokotoa expirationd_expired hukagua maisha ya nakala, expirationd_delete hufuta nakala, expirationd_breakable hukagua kama tunahitaji kuendelea.

Nambari ya utendaji kazi expirationd_iterate:

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

Msimbo wa kukokotoa umekwisha muda_umeisha:

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 msimbo wa kazi:

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

Msimbo wa utendakazi 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;
}

Programu

Unaweza kutazama msimbo wa chanzo kwa hapa!

Chanzo: mapenzi.com

Kuongeza maoni