tarantool үчүн өзүбүздүн чектелген мөөнөтү аяктаган модулду жазуу

tarantool үчүн өзүбүздүн чектелген мөөнөтү аяктаган модулду жазуу

Бир нече убакыт мурун биз мейкиндиктерди тазалоо көйгөйүнө туш болгонбуз тарантоол. Тазалоону tarantool эстутуму түгөнгөндө эмес, алдын ала жана белгилүү бир жыштыкта ​​баштоо керек болчу. Бул тапшырма үчүн, tarantool деп аталган Lua жазылган модулу бар мөөнөтү. Бул модулду бир аз убакыт колдонгондон кийин, биз ал бизге ылайыктуу эмес экенин түшүндүк: көп көлөмдөгү маалыматтарды дайыма тазалоодон улам Луа GCде илинип калды. Ошондуктан, биз эне программалоо тилинде жазылган код биздин көйгөйлөрүбүздү эң жакшы жол менен чечет деп үмүттөнүп, мөөнөтү бүтүп калган модулубузду иштеп чыгуу жөнүндө ойлондук.

Биз үчүн жакшы үлгү деп аталган tarantool модулу болду memcached. Анда колдонулган ыкма мейкиндикте өзүнчө талаа түзүлүп, кортеждин иштөө мөөнөтүн, башкача айтканда ttl. Фондогу модул мейкиндикти сканерлейт, TTLди учурдагы убакыт менен салыштырат жана кортежди жок кылуу же жок кылууну чечет. Memcached модулдун коду жөнөкөй жана жарашыктуу, бирок өтө жалпы. Биринчиден, ал сойлоп жана жок кылынган индекстин түрүн эске албайт. Экинчиден, ар бир өтүүдө бардык кортеждер сканерден өткөрүлөт, алардын саны абдан чоң болушу мүмкүн. Ал эми мөөнөтү бүткөн модулда биринчи маселе чечилсе (дарактын индекси өзүнчө класска бөлүнгөн), анда экинчисине дагы деле көңүл бурулган эмес. Бул үч пункт менин өзүмдүн кодумду жазуу үчүн тандоону алдын ала аныктаган.

баяндоо

Tarantool үчүн документтер абдан жакшы окуу куралы сакталган процедураларыңызды C тилинде кантип жазуу керектиги жөнүндө. Биринчиден, мен сизге төмөндө пайда боло турган буйруктар жана коддор менен кошумчаларды түшүнүү үчүн аны менен таанышууну сунуштайм. Буга да көңүл буруу керек шилтеме өзүңүздүн капталган модулуңузду жазууда жеткиликтүү болгон объекттерге, атап айтканда кутуча, була, көрсөткүч и txn.

Келгиле, алыстан баштайлы жана мөөнөтү бүтүп калган модулдун сыртынан кандай экенин карап көрөлү:

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)

Жөнөкөйлүк үчүн, libcapped-expirationd.so китепканабыз жайгашкан каталогдо tarantool программасын ишке киргизебиз. Китепканадан эки функция экспорттолот: баштоо жана өлтүрүү. Биринчи кадам бул функцияларды Luaдан box.schema.func.create жана box.schema.user.grant аркылуу жеткиликтүү кылуу. Андан кийин кортеждери үч гана талааны камтый турган мейкиндикти түзүңүз: биринчиси - уникалдуу идентификатор, экинчиси - электрондук почта, үчүнчүсү - кортеждин иштөө мөөнөтү. Биз биринчи талаанын үстүнө дарак индексин куруп, аны негизги деп атайбыз. Андан кийин биз өз китепканабызга байланыш объектисин алабыз.

Даярдоо иштеринен кийин, баштоо функциясын иштетиңиз:

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

Бул мисал сканерлөө учурунда Луада жазылган мөөнөтү бүткөн модулу менен бирдей иштейт. Баштоо функциясынын биринчи аргументи бул тапшырманын уникалдуу аталышы. Экинчиси мейкиндиктин идентификатору. Үчүнчүсү - кортеждер жок кылына турган уникалдуу индекс. Төртүнчүсү - кортеждер өтүүчү индекс. Бешинчи - өмүр бою кортеж талаасынын саны (номерлөө 1 эмес, 0ден башталат!). Алтынчы жана жетинчи - сканерлөө жөндөөлөрү. 1024 - бул бир транзакцияда көрүүгө мүмкүн болгон кортеждердин максималдуу саны. 3600 — секунданын ичинде толук сканерлөө убактысы.

Мисалы, сойлоп чыгуу жана жок кылуу үчүн бир эле индексти колдоноорун эске алыңыз. Эгерде бул дарактын индекси болсо, анда өтүү кичине ачкычтан чоңураак ачкычка чейин жүргүзүлөт. Эгерде кандайдыр бир башка, мисалы, таштанды индекси бар болсо, анда траверсал, эреже катары, туш келди тартипте жүргүзүлөт. Бардык мейкиндик кортеждери бир сканерден сканерленет.

Келгиле, мейкиндикке 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}

Киргизүү ийгиликтүү болгонун текшерип көрөлү:

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

Келгиле, тандоону 60+ секунддан кийин кайталап көрөлү (биринчи кортежди киргизүүнүн башталышынан баштап эсепке алуу менен) жана мөөнөтү бүткөн модулдун жабылганын карап көрөлү:

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

Келгиле, тапшырманы токтотолу:

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

Крол үчүн өзүнчө индекс колдонулган экинчи мисалды карап көрөлү:

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)

Бул жерде бардыгы биринчи мисалдагыдай эле, кээ бир учурларды эске албаганда. Үчүнчү талаанын үстүнө дарак индексин куруп, аны эксп деп атайбыз. Бул индекс негизги деп аталган индекстен айырмаланып, уникалдуу болууга тийиш эмес. Кыдырып өтүү экс индекси боюнча, ал эми жок кылуу баштапкы көрсөткүч боюнча жүргүзүлөт. Мурда экөө тең негизги индексти колдонуу менен гана жасалганы эсибизде.

Даярдоо иштеринен кийин биз жаңы аргументтер менен баштоо функциясын иштетебиз:

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

Келгиле, мейкиндикке дагы бир нече кортежди 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}

30 секунддан кийин, окшоштук боюнча, биз дагы бир нече кортежди кошобуз:

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}

Киргизүү ийгиликтүү болгонун текшерип көрөлү:

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

Келгиле, тандоону 60+ секунддан кийин кайталап көрөлү (биринчи кортежди киргизүүнүн башталышынан баштап эсепке алуу менен) жана мөөнөтү бүткөн модулдун жабылганын карап көрөлү:

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

Космосто дагы 30 секунд жашай турган кээ бир кортеждер бар. Мындан тышкары, идентификатору 2 жана иштөө мөөнөтү 1576421257 болгон кортежден ID 3 жана 1576421287 колдонуу мөөнөтү бар кортежге өткөндө сканерлөө токтоп калды. 1576421287 же андан көп колдонуу мөөнөтү бар кортеждер буйрутмалангандыктан сканерленген эмес. exp индексинин ачкычтары. Бул биз эң башында жетишүүнү каалаган үнөмдөө.

Келгиле, тапшырманы токтотолу:

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

Реализация

Долбоордун бардык өзгөчөлүктөрү жөнүндө айтуунун эң жакшы жолу - бул анын баштапкы булагы. коду! Басылманын алкагында биз эң маанилүү пункттарга, тактап айтканда, мейкиндикти айланып өтүү алгоритмдерине гана токтолобуз.

Биз баштоо ыкмасына өткөргөн аргументтер 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;
};

Name атрибуту - тапшырманын аталышы. space_id атрибуту боштук идентификатору болуп саналат. rm_index_id атрибуту - бул кортеждер жок кылына турган уникалдуу индекстин идентификатору. it_index_id атрибуту - бул кортеждер өтүүчү индекстин идентификатору. it_index_type атрибуту - кортеждер өтүүчү индекстин түрү. filed_no атрибуту өмүр бою кортеж талаасынын саны. scan_size атрибуту - бул бир транзакцияда сканерленген кортеждердин максималдуу саны. scan_time атрибуту секунддардагы толук сканерлөө убактысы.

Биз аргументтерди талдоону карабайбыз. Бул түйшүктүү, бирок жөнөкөй жумуш, аны менен китепкана сизге жардам берет msgpuck. Кыйынчылыктар Луадан mp_map түрү менен татаал маалымат структурасы катары берилген индекстер менен гана пайда болушу мүмкүн, бирок mp_bool, mp_double, mp_int, mp_uint жана mp_array жөнөкөй түрлөрүн колдонбостон. Бирок бүтүндөй индексти талдоонун кереги жок. Сиз жөн гана анын уникалдуулугун текшерип, түрүн эсептеп, идентификаторду чыгарып алышыңыз керек.

Биз талдоо үчүн колдонулган бардык функциялардын прототиптерин тизмектейбиз:

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

Эми эң негизги нерсеге – мейкиндикти айланып өтүү жана кортеждерди жок кылуу логикасына өтөбүз. scan_sizeден чоң эмес кортеждердин ар бир блогу бир транзакциянын алкагында сканерленет жана өзгөртүлөт. Ийгиликтүү болсо, бул транзакция жасалат; ката кетсе, ал артка жылдырылат. expirationd_iterate функциясынын акыркы аргументи сканерлөө башталган же улана турган итератордун көрсөткүчү болуп саналат. Бул итератор ката пайда болгонго чейин, бош орун түгөнүп калганга чейин же процессти алдын ала токтотуу мүмкүн болбой калганга чейин көбөйтүлөт. expirationd_expired функциясы кортеждин иштөө мөөнөтүн текшерет, expirationd_delete кортежди жок кылат, expirationd_breakable мындан ары өтүшүбүз керекпи же жокпу текшерет.

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

Функция коду 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 функция коду:

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

Функция коду 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;
}

арыз

Сиз булак кодун көрө аласыз бул жерде!

Source: www.habr.com

Комментарий кошуу