Vi skriver vÄr capped expirationd modul för tarantool

Vi skriver vÄr capped expirationd modul för tarantool

För en tid sedan stÀlldes vi inför problemet att rensa tuppluster i utrymmen. tarantool. Rensningen behövde inte startas nÀr tarantool redan hade slut pÄ minne, utan i förvÀg och med viss periodicitet. För detta ÀndamÄl finns en modul i tarantool, skriven i Lua, som kallas expirationd. Efter en kort tids anvÀndning av denna modul insÄg vi att den inte passade oss: vid permanenta rensningar av stora datamÀngder hÀngde Lua i GC. DÀrför började vi tÀnka pÄ att utveckla vÄr egen capped expirationd-modul, i förhoppningen att koden, skriven pÄ ett inbyggt programmeringssprÄk, skulle lösa vÄra problem pÄ bÀsta sÀtt.

Ett bra exempel för oss var modulen tarantool som heter memcached. Den anvÀnda metoden baseras pÄ att en separat fÀlt skapas i utrymmet, dÀr livslÀngden för tupplusten anges, med andra ord ttl. Modulen skannar i bakgrunden utrymmet, jÀmför ttl med den aktuella tiden och fattar beslut om att ta bort tupplusten eller inte. Koden för modulen memcached Àr enkel och elegant, men för generell. För det första tar den inte hÀnsyn till indexets typ för genomgÄng och borttagning. För det andra skannas alla tuppluster vid varje genomgÄng, vars antal kan vara ganska stort. Och i expirationd-modulen löstes det första problemet (tree-indexet har avsatts i en separat klass), men det andra har fortfarande inte uppmÀrksammats. Dessa tre punkter avgjorde valet av att skriva vÄr egen kod.

Beskrivning

I dokumentationen till tarantool finns det en mycket bra handledning om hur man skriver sina egna lagrade procedurer pÄ C-sprÄket. Jag rekommenderar först att bekanta sig med den för att förstÄ de insatser med kommandon och kod som kommer att dyka upp nedan. Det Àr ocksÄ vÀrt att notera referensen till objekt som Àr tillgÀngliga nÀr man skriver sin egen capped modul, nÀmligen till box, fiber, index och txn.

LÄt oss börja frÄn början och se hur den capped expirationd-modulen ser ut utifrÄn:

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)

För enkelhetens skull kör vi tarantool i katalogen dĂ€r vĂ„rt bibliotek libcapped-expirationd.so ligger. FrĂ„n biblioteket exporteras tvĂ„ funktioner: start och kill. Först mĂ„ste vi göra dessa funktioner tillgĂ€ngliga frĂ„n Lua med hjĂ€lp av box.schema.func.create och box.schema.user.grant. Sedan skapar vi ett space vars tuples kommer att innehĂ„lla endast tre fĂ€lt: det första — ett unikt identifierare, det andra — en e-postadress, det tredje — livslĂ€ngden för tuple. Över det första fĂ€ltet bygger vi ett tree-index och kallar det primary. Vi fĂ„r Ă€ven en anslutningsobjekt till vĂ„rt inbyggda bibliotek.

Efter förberedelserna kör vi funktionen start:

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

Detta exempel kommer att fungera pĂ„ samma sĂ€tt som modulen expirationd, som Ă€r skriven i Lua. Det första argumentet till funktionen start överför ett unikt namn för uppgiften. Det andra — identifieraren för spacet. Det tredje — det unika indexet som anvĂ€nds för att ta bort tuples. Det fjĂ€rde — indexet som anvĂ€nds för att gĂ„ igenom tuples. Det femte — numret pĂ„ tuple-fĂ€ltet med livslĂ€ngd (numreringen börjar vid 1, inte 0!). Det sjĂ€tte och sjunde — instĂ€llningar för skanning. 1024 — det maximala antalet tuples som granskas inom en transaktion. 3600 — tiden för en fullstĂ€ndig skanning i sekunder.

Observera att samma index anvÀnds för bÄde genomgÄng och borttagning i exemplet. Om det Àr ett tree-index, sker genomgÄngen frÄn lÀgre till högre nyckel. Om det Àr nÄgot annat, till exempel ett hash-index, sÄ sker genomgÄngen ofta i slumpmÀssig ordning. I en skanning granskas alla tuples i spacet.

LÄt oss göra en insÀttning i spacet av flera tuples med livslÀngd pÄ 60 sekunder:

box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}

LÄt oss kontrollera att insÀttningen lyckades:

tarantool> box.space.tester.index.primary:select()
---
- - [0, 'user0@tarantool.io', 1576418976]
  - [1, 'user1@tarantool.io', 1576418976]
  - [2, 'user2@tarantool.io', 1576418976]
...

Vi upprepar select efter 60+ sekunder (rÀknat frÄn starten av insÀttningen av den första tuplen) och ser att modulen capped expirationd redan har arbetat:

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

Vi stoppar uppgiften:

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

LÄt oss titta pÄ det andra exemplet, dÀr en separat index anvÀnds för genomgÄng:

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 Àr allt detsamma som i det första exemplet, med en liten skillnad. OvanpÄ det tredje fÀltet bygger vi en tree-index och kallar den exp. Denna index behöver inte vara unik, till skillnad frÄn indexet som kallas primary. GenomgÄngen kommer att ske via exp-indexet, medan borttagningen sker via primary. Vi minns att tidigare bÄde detta och det andra endast gjordes med hjÀlp av primary-indexet.

Efter förberedelserna startar vi funktionen start med nya argument:

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

LÄt oss Äterigen göra en insÀttning i space med flera tuples med en livslÀngd pÄ 60 sekunder:

box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}

Om 30 sekunder lÀgger vi till ytterligare nÄgra tuples pÄ samma sÀtt:

box.space.tester:insert{3, 'user3@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{4, 'user4@tarantool.io', math.floor(fiber.time()) + 60}
box.space.tester:insert{5, 'user5@tarantool.io', math.floor(fiber.time()) + 60}

LÄt oss kontrollera att insÀttningen lyckades:

tarantool> box.space.tester.index.primary:select()
---
- - [0, 'user0@tarantool.io', 1576421257]
  - [1, 'user1@tarantool.io', 1576421257]
  - [2, 'user2@tarantool.io', 1576421257]
  - [3, 'user3@tarantool.io', 1576421287]
  - [4, 'user4@tarantool.io', 1576421287]
  - [5, 'user5@tarantool.io', 1576421287]
...

Vi upprepar select efter 60+ sekunder (rÀknat frÄn starten av insÀttningen av den första tuplen) och ser att modulen capped expirationd redan har arbetat:

tarantool> box.space.tester.index.primary:select()
---
- - [3, 'user3@tarantool.io', 1576421287]
  - [4, 'user4@tarantool.io', 1576421287]
  - [5, 'user5@tarantool.io', 1576421287]
...

I space finns fortfarande tuples som har en livslÀngd pÄ cirka 30 sekunder kvar. Dessutom stannade skanningen vid övergÄngen frÄn tuple med identifieraren 2 och livslÀngden 1576421257 till tuple med identifieraren 3 och livslÀngden 1576421287. Tuples med livslÀngden 1576421287 och mer har inte granskats pÄ grund av ordningen pÄ nycklarna i exp-indexet. Detta Àr just den ekonomin vi ville uppnÄ frÄn början.

Vi stoppar uppgiften:

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

Implementering

BÀsta sÀttet att berÀtta om alla sÀrdrag i projektet Àr alltid dess ursprungliga kod! I den hÀr publikation kommer vi bara att fokusera pÄ de mest viktiga punkterna, nÀmligen algoritmerna för att genomgÄ space.

Argumenten som vi skickar till metoden start sparas i en struktur som kallas 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;
};

Attributet name Àr namnet pÄ uppgiften. Attributet space_id Àr identifieraren för utrymmet. Attributet rm_index_id Àr identifieraren för den unika indexen som anvÀnds för att ta bort tupler. Attributet it_index_id Àr identifieraren för indexet som anvÀnds för att traversera tupler. Attributet it_index_type Àr typen av index som anvÀnds för att traversera tupler. Attributet filed_no Àr numret pÄ fÀltet av tuplen med livslÀngd. Attributet scan_size Àr det maximala antalet tupler som granskas i en transaktion. Attributet scan_time Àr tiden för en fullstÀndig skanning i sekunder.

Vi kommer inte att diskutera parsning av argument. Det Àr ett arbetet som krÀver noggrannhet, men det Àr inte svÄrt, och biblioteket msgpuckkan vara till hjÀlp. SvÄrigheter kan endast uppstÄ med index som skickas frÄn Lua i form av en komplex dataskstruktur av typen mp_map, och inte med hjÀlp av enkla typer som mp_bool, mp_double, mp_int, mp_uint och mp_array. Men det Àr inte nödvÀndigt att parsa hela indexet. Det rÀcker att kontrollera dess unika identitet, berÀkna typen och extrahera identifieraren.

LÄt oss lista prototyperna för alla funktioner som anvÀnds för parsning:

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

Nu gĂ„r vi vidare till det viktigaste — logiken för att traversera utrymmet och radera tupler. Varje block av tupler som Ă€r högst scan_size granskas och Ă€ndras under en och samma transaktion. Vid framgĂ„ng committas denna transaktion, vid fel rullas den tillbaka. Den sista argumentet i funktionen expirationd_iterate Ă€r en pekare till iteratorn dĂ€r skanningen börjar eller fortsĂ€tter. Denna iterator inkrementeras inuti tills ett fel intrĂ€ffar, utrymmet tar slut, eller det finns möjlighet att avbryta processen i förvĂ€g. Funktionen expirationd_expired kontrollerar livslĂ€ngden för tuplen, expirationd_delete tar bort tuplen, expirationd_breakable kontrollerar om vi behöver gĂ„ vidare.

Koden för funktionen 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 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;
}

Koden för funktionen 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;
}

Koden för funktionen expirationd_delete:

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

Koden för funktionen expirationd_breakable:

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

Applikation

Du kan bekanta dig med kÀllkoden pÄ ). Ett typiskt exempel pÄ anvÀndning:!

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster