Að skrifa okkar eigin útlokaða einingu fyrir tarantool

Að skrifa okkar eigin útlokaða einingu fyrir tarantool

Fyrir nokkru síðan stóðum við frammi fyrir því vandamáli að þrífa túpa í rýmum tarantool. Ekki þurfti að hefja hreinsun þegar minnið var að klárast í tarantool heldur fyrirfram og á ákveðinni tíðni. Fyrir þetta verkefni hefur tarantool einingu sem er skrifuð í Lua sem heitir fyrning. Eftir að hafa notað þessa einingu í stuttan tíma komumst við að því að hún hentaði okkur ekki: Vegna stöðugrar hreinsunar á miklu magni af gögnum hékk Lua í GC. Þess vegna hugsuðum við um að þróa okkar eigin lokuðu útrunnaeiningu í von um að kóðinn sem skrifaður er á móðurmáli myndi leysa vandamál okkar á besta mögulega hátt.

Gott dæmi fyrir okkur var tarantool einingin sem heitir skyndiminni. Nálgunin sem notuð er í henni byggist á því að í rýminu myndast sérstakt reit sem gefur til kynna líftíma túpelsins, með öðrum orðum ttl. Einingin í bakgrunni skannar rýmið, ber saman TTL við núverandi tíma og ákveður hvort eyða eigi tuplenum eða ekki. Memcached einingakóði er einfaldur og glæsilegur, en of almennur. Í fyrsta lagi tekur það ekki tillit til tegundar vísitölu sem verið er að skríða og eyða. Í öðru lagi, í hverri umferð eru allir túllur skannaðar, en fjöldi þeirra getur verið nokkuð mikill. Og ef fyrsta vandamálið var leyst í útrunninni einingu (trévísitalan var aðskilin í sérstakan flokk), þá fékk sá seinni enn enga athygli. Þessir þrír punktar ákváðu fyrirfram valið í þágu þess að skrifa eigin kóða.

Lýsing

Skjölin fyrir tarantool hafa mjög gott kennsluefni um hvernig á að skrifa vistuð verklag í C. Í fyrsta lagi legg ég til að þú kynnir þér það til að skilja innskotin með skipunum og kóða sem munu birtast hér að neðan. Það er líka þess virði að gefa gaum tilvísun að hlutum sem eru tiltækir þegar þú skrifar þína eigin afmörkuðu einingu, þ.e kassi, trefjar, Vísitala и txn.

Við skulum byrja úr fjarska og skoða hvernig eining með loki sem er útrunnið lítur út að utan:

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)

Til einföldunar ræsum við tarantool í möppunni þar sem libcapped-expirationd.so bókasafnið okkar er staðsett. Tvær aðgerðir eru fluttar út úr bókasafninu: byrja og drepa. Fyrsta skrefið er að gera þessar aðgerðir aðgengilegar frá Lua með því að nota box.schema.func.create og box.schema.user.grant. Búðu síðan til svæði þar sem túllarnir innihalda aðeins þrjá reiti: sá fyrsti er einstakt auðkenni, sá annar er tölvupóstur og sá þriðji er líftími túllsins. Við byggjum trjávísitölu ofan á fyrsta reitinn og köllum það aðal. Næst fáum við tengingarhlutinn í innfædda bókasafnið okkar.

Eftir undirbúningsvinnu skaltu keyra byrjunaraðgerðina:

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

Þetta dæmi mun virka við skönnun nákvæmlega eins og útrunninn eining, sem er skrifuð í Lua. Fyrstu rökin fyrir upphafsfallinu eru einstakt heiti verkefnisins. Annað er rýmisauðkennið. Þriðja er einstök vísitala þar sem túllum verður eytt. Fjórða er vísitalan sem farið verður yfir túpurnar. Það fimmta er númer tuple reitsins með líftíma (talning byrjar frá 1, ekki 0!). Sjötta og sjöunda eru skönnunarstillingar. 1024 er hámarksfjöldi tuples sem hægt er að skoða í einni færslu. 3600 — fullur skannatími í sekúndum.

Athugaðu að dæmið notar sömu vísitölu til að skríða og eyða. Ef þetta er trjávísitala, þá fer yfirferðin frá minni lyklinum yfir í þann stærri. Ef það er einhver önnur, til dæmis kjötkássavísitala, þá fer yfirferðin að jafnaði fram í handahófskenndri röð. Allir geimtúllur eru skannaðar í einni skönnun.

Við skulum setja nokkra tuple inn í rýmið með líftíma upp á 60 sekúndur:

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}

Við skulum athuga hvort innsetningin hafi tekist:

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

Við skulum endurtaka valið eftir 60+ sekúndur (talið frá upphafi innsetningar fyrsta túpunnar) og sjáum að lokuð útrunnin eining hefur þegar unnið:

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

Hættum verkefninu:

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

Við skulum skoða annað dæmi þar sem sérstök vísitala er notuð fyrir skrið:

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)

Hér er allt eins og í fyrra dæminu, með nokkrum undantekningum. Við byggjum trjávísitölu ofan á þriðja reitinn og köllum það exp. Þessi vísitala þarf ekki að vera einstök, ólíkt þeirri vísitölu sem kallast aðal. Flutningur verður framkvæmdur með exp index, og eyðing með aðal. Við munum að áður var hvort tveggja gert með því að nota aðalvísitöluna.

Eftir undirbúningsvinnuna keyrum við byrjunarfallið með nýjum rökum:

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

Við skulum setja nokkra tuple inn í rýmið aftur með líftíma upp á 60 sekúndur:

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}

Eftir 30 sekúndur, á hliðstæðan hátt, munum við bæta við nokkrum túllum í viðbót:

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}

Við skulum athuga hvort innsetningin hafi tekist:

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

Við skulum endurtaka valið eftir 60+ sekúndur (talið frá upphafi innsetningar fyrsta túpunnar) og sjáum að lokuð útrunnin eining hefur þegar unnið:

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

Það eru enn nokkrir túplar eftir í rýminu sem munu hafa um það bil 30 sekúndur í viðbót til að lifa. Ennfremur stöðvaðist skönnun þegar farið var úr túllu með auðkenni 2 og líftíma 1576421257 í túllu með auðkenni 3 og líftíma 1576421287. Túpelar með líftíma 1576421287 eða meira voru ekki skannaðar vegna pöntunar á exp index lyklana. Þetta er sparnaðurinn sem við vildum ná strax í upphafi.

Hættum verkefninu:

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

Framkvæmd

Besta leiðin til að segja frá öllum eiginleikum verkefnis er upprunaleg heimild þess. kóða! Sem hluti af útgáfunni munum við einblína aðeins á mikilvægustu atriðin, nefnilega reiknirit fyrir framhjáhlaup geimsins.

Rökin sem við sendum til upphafsaðferðarinnar eru geymd í uppbyggingu sem kallast 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;
};

Nafnaeigindið er heiti verkefnisins. Eigindið space_id er auðkenni rýmis. Eigindin rm_index_id er auðkenni einstaka vísitölunnar sem túllum verður eytt með. Eigindin it_index_id er auðkenni vísitölunnar sem farið verður í gegnum tuples. Eigindin it_index_type er tegund vísitölunnar sem farið verður í gegnum tuples. Filed_no eigindin er númer tuple reitsins með líftíma. Scan_size eigindin er hámarksfjöldi tuples sem eru skannaðar í einni færslu. Scan_time eigindin er fullur skannatími í sekúndum.

Við munum ekki íhuga að flokka rök. Þetta er vandað en einfalt starf, sem bókasafnið mun hjálpa þér með msgpuck. Erfiðleikar geta aðeins komið upp með vísitölum sem eru sendar frá Lua sem flókið gagnaskipulag með mp_map gerðinni, en ekki með einföldu gerðunum mp_bool, mp_double, mp_int, mp_uint og mp_array. En það er engin þörf á að flokka alla vísitöluna. Þú þarft bara að athuga sérstöðu þess, reikna út tegundina og draga út auðkennið.

Við skráum frumgerðir allra aðgerða sem eru notaðar við þáttun:

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

Nú skulum við halda áfram að því mikilvægasta - rökfræðinni um að fara framhjá plássi og eyða túllum. Hver blokk af túllum sem ekki eru stærri en scan_size er skannaður og breytt í einni færslu. Ef vel tekst til er þessi viðskipti framin; ef villa kemur upp er henni snúið til baka. Síðasta rökin við expirationd_iterate fallið er bendi á endurtekið sem skönnun hefst eða heldur áfram frá. Þessi endurtekning er aukinn innbyrðis þar til villa kemur upp, plássið klárast eða ekki er hægt að stöðva ferlið fyrirfram. Aðgerðin expirationd_expired athugar líftíma tuple, expirationd_delete eyðir tuple, expirationd_breakable athugar hvort við þurfum að halda áfram.

Expirationd_iterate aðgerðakóði:

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

Aðgerðarkóði 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 aðgerðakóði:

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

Aðgerðarkóði 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;
}

umsókn

Þú getur skoðað frumkóðann á hér!

Heimild: www.habr.com

Bæta við athugasemd