Ysgrifennu ein modiwl darfodedig capio ein hunain ar gyfer tarantool

Ysgrifennu ein modiwl darfodedig capio ein hunain ar gyfer tarantool

Beth amser yn ôl roeddem yn wynebu'r broblem o lanhau tuples mewn gofodau tarantool. Roedd yn rhaid dechrau glanhau nid pan oedd tarantool eisoes yn rhedeg allan o'r cof, ond ymlaen llaw ac ar amlder penodol. Ar gyfer y dasg hon, mae gan tarantool fodiwl wedi'i ysgrifennu yn Lua o'r enw darfod. Ar ôl defnyddio'r modiwl hwn am gyfnod byr, sylweddolom nad oedd yn addas i ni: oherwydd glanhau cyson o symiau mawr o ddata, roedd Lua yn hongian yn y GC. Felly, buom yn meddwl am ddatblygu ein modiwl darfodedig â chapio ein hunain, gan obeithio y byddai'r cod a ysgrifennwyd mewn iaith raglennu frodorol yn datrys ein problemau yn y ffordd orau bosibl.

Enghraifft dda i ni oedd y modiwl tarantool a elwir memcached. Mae'r dull a ddefnyddir ynddo yn seiliedig ar y ffaith bod maes ar wahân yn cael ei greu yn y gofod, sy'n dynodi oes y tuple, mewn geiriau eraill, ttl. Mae'r modiwl yn y cefndir yn sganio'r gofod, yn cymharu'r TTL â'r amser presennol ac yn penderfynu a ddylid dileu'r tuple ai peidio. Mae cod y modiwl memcached yn syml a chain, ond yn rhy generig. Yn gyntaf, nid yw'n ystyried y math o fynegai sy'n cael ei gropian a'i ddileu. Yn ail, mae pob tuples yn cael ei sganio ar bob tocyn, a gall y nifer fod yn eithaf mawr. Ac os cafodd y broblem gyntaf ei datrys yn y modiwl a ddaeth i ben (rhannwyd y mynegai coed yn ddosbarth ar wahân), yna ni chafodd yr ail un sylw o hyd. Roedd y tri phwynt hyn yn rhagflaenu'r dewis o blaid ysgrifennu fy nghod fy hun.

Disgrifiad

Mae gan y ddogfennaeth ar gyfer tarantool dda iawn tiwtorial am sut i ysgrifennu eich gweithdrefnau storio yn C. Yn gyntaf oll, rwy'n awgrymu eich bod yn ymgyfarwyddo ag ef er mwyn deall y mewnosodiadau hynny gyda gorchmynion a chod a fydd yn ymddangos isod. Mae hefyd yn werth talu sylw i cyfeiriad i wrthrychau sydd ar gael wrth ysgrifennu eich modiwl capio eich hun, sef blwch, ffibr, mynegai и txn.

Gadewch i ni ddechrau o bell ac edrych ar sut olwg sydd ar fodiwl sydd wedi dod i ben wedi'i gapio o'r tu allan:

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)

Er mwyn symlrwydd, rydym yn lansio tarantool yn y cyfeiriadur lle mae ein llyfrgell libcapped-expirationd.so wedi'i lleoli. Mae dwy swyddogaeth yn cael eu hallforio o'r llyfrgell: cychwyn a lladd. Y cam cyntaf yw sicrhau bod y swyddogaethau hyn ar gael gan Lua gan ddefnyddio box.schema.func.create a box.schema.user.grant. Yna creu gofod y bydd ei tuples yn cynnwys dim ond tri maes: y cyntaf yn ddynodwr unigryw, yr ail yw e-bost, a'r trydydd yw oes y tuple. Rydym yn adeiladu mynegai coed ar ben y cae cyntaf ac yn ei alw'n gynradd. Nesaf cawn y gwrthrych cysylltiad i'n llyfrgell frodorol.

Ar ôl y gwaith paratoi, rhedwch y swyddogaeth gychwyn:

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

Bydd yr enghraifft hon yn gweithio yn ystod sganio yn union yr un fath â'r modiwl sydd wedi dod i ben, sydd wedi'i ysgrifennu yn Lua. Y ddadl gyntaf i'r swyddogaeth gychwyn yw enw unigryw'r dasg. Yr ail yw'r dynodwr gofod. Mae'r trydydd yn fynegai unigryw lle bydd tuples yn cael eu dileu. Y pedwerydd yw'r mynegai ar gyfer croesi'r tuples. Y pumed yw nifer y cae tuple ag oes (rhifo yn dechrau o 1, nid 0!). Mae'r chweched a'r seithfed yn osodiadau sganio. 1024 yw uchafswm nifer y tuples y gellir eu gweld mewn un trafodiad. 3600 - amser sgan llawn mewn eiliadau.

Sylwch fod yr enghraifft yn defnyddio'r un mynegai ar gyfer cropian a dileu. Os yw hwn yn fynegai coeden, yna mae'r llwybr yn cael ei wneud o'r cywair llai i'r un mwy. Os oes mynegai hash arall, er enghraifft, yna mae'r llwybr yn cael ei wneud, fel rheol, mewn trefn ar hap. Mae pob tuples gofod yn cael eu sganio mewn un sgan.

Gadewch i ni fewnosod sawl tuples yn y gofod gydag oes o 60 eiliad:

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}

Gadewch i ni wirio bod y mewnosodiad yn llwyddiannus:

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

Gadewch i ni ailadrodd y dewis ar ôl 60+ eiliad (gan gyfrif o ddechrau mewnosod y tuple cyntaf) a gweld bod y modiwl wedi'i gapio wedi dod i ben eisoes wedi prosesu:

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

Gadewch i ni atal y dasg:

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

Edrychwn ar ail enghraifft lle mae mynegai ar wahân yn cael ei ddefnyddio ar gyfer y cropian:

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)

Mae popeth yma yr un peth ag yn yr enghraifft gyntaf, gydag ychydig eithriadau. Rydym yn adeiladu mynegai coed ar ben y trydydd cae ac yn ei alw'n exp. Nid oes rhaid i'r mynegai hwn fod yn unigryw, yn wahanol i'r mynegai a elwir yn gynradd. Bydd tramwy yn cael ei wneud yn ôl mynegai exp, a dileu yn ôl cynradd. Cofiwn i'r ddau gael eu gwneud o'r blaen gan ddefnyddio'r mynegai cynradd yn unig.

Ar ôl y gwaith paratoi, rydym yn rhedeg y swyddogaeth gychwyn gyda dadleuon newydd:

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

Gadewch i ni fewnosod sawl tuples yn y gofod eto gydag oes o 60 eiliad:

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}

Ar ôl 30 eiliad, trwy gyfatebiaeth, byddwn yn ychwanegu ychydig mwy o tuples:

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}

Gadewch i ni wirio bod y mewnosodiad yn llwyddiannus:

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

Gadewch i ni ailadrodd y dewis ar ôl 60+ eiliad (gan gyfrif o ddechrau mewnosod y tuple cyntaf) a gweld bod y modiwl wedi'i gapio wedi dod i ben eisoes wedi prosesu:

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

Mae yna rai tuples ar ôl yn y gofod o hyd a fydd â thua 30 eiliad arall i fyw. Ar ben hynny, daeth y sgan i ben wrth symud o hwll ag ID o 2 ac oes o 1576421257 i hwll ag ID o 3 ac oes o 1576421287. Ni sganiwyd twplau ag oes o 1576421287 neu fwy oherwydd archebu yr allweddi mynegai exp. Dyma’r arbedion yr oeddem am eu cyflawni ar y cychwyn cyntaf.

Gadewch i ni atal y dasg:

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

Gweithredu

Y ffordd orau o ddweud am holl nodweddion prosiect yw ei ffynhonnell wreiddiol. код! Fel rhan o'r cyhoeddiad, byddwn yn canolbwyntio ar y pwyntiau pwysicaf yn unig, sef, algorithmau ffordd osgoi gofod.

Mae'r dadleuon rydyn ni'n eu trosglwyddo i'r dull cychwyn yn cael eu storio mewn strwythur o'r enw 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;
};

Y briodwedd enw yw enw'r dasg. Y briodwedd space_id yw'r dynodwr gofod. Y briodwedd rm_index_id yw dynodwr y mynegai unigryw a ddefnyddir i ddileu tuples. Y briodwedd it_index_id yw dynodwr y mynegai a ddefnyddir i groesi tuples. Y briodwedd it_index_type yw'r math o fynegai y bydd tuples yn cael ei groesi drwyddo. Y briodwedd filed_no yw nifer y maes tuple ag oes. Y briodwedd scan_size yw'r nifer uchaf o duples sy'n cael eu sganio mewn un trafodiad. Y briodwedd scan_time yw'r amser sgan llawn mewn eiliadau.

Ni fyddwn yn ystyried dosrannu dadleuon. Mae hon yn waith manwl ond syml, a bydd y llyfrgell yn eich helpu chi msgpuck. Dim ond gyda mynegeion sy'n cael eu trosglwyddo o Lua fel strwythur data cymhleth gyda'r math mp_map y gall anawsterau godi, a pheidio â defnyddio'r mathau syml mp_bool, mp_double, mp_int, mp_uint a mp_array. Ond nid oes angen dosrannu'r mynegai cyfan. Mae angen i chi wirio ei unigrywiaeth, cyfrifo'r math a thynnu'r dynodwr.

Rydym yn rhestru'r prototeipiau o'r holl swyddogaethau a ddefnyddir ar gyfer dosrannu:

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

Nawr, gadewch i ni symud ymlaen at y peth pwysicaf - y rhesymeg o osgoi gofod a dileu tuples. Mae pob bloc o tuples nad yw'n fwy na scan_size yn cael ei sganio a'i addasu o dan un trafodiad. Os yw'n llwyddiannus, mae'r trafodiad hwn wedi'i ymrwymo; os bydd gwall, caiff ei rolio'n ôl. Mae'r ddadl olaf i'r ffwythiant expirationd_iterate yn bwyntydd i'r iterator y mae'r sganio'n dechrau neu'n parhau ohono. Cynyddir yr iterator hwn yn fewnol nes bod gwall yn digwydd, mae'r gofod yn rhedeg allan, neu nid yw'n bosibl atal y broses ymlaen llaw. Mae'r ffwythiant expirationd_expired yn gwirio oes tuple, mae expirationd_delete yn dileu tuple, mae expirationd_breakable yn gwirio a oes angen i ni symud ymlaen.

Cod swyddogaeth_iterate_exppirationd:

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

Cod swyddogaeth wedi dod i ben_wedi dod i ben :

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

Wedi dod i ben_dileu cod swyddogaeth:

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

Cod swyddogaeth darfodadwy_breakable:

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

Cais

Gallwch weld y cod ffynhonnell yn yma!

Ffynhonnell: hab.com

Ychwanegu sylw