Գրում ենք մեր սեփական ծածկույթով ժամկետանց մոդուլը tarantool-ի համար

Գրում ենք մեր սեփական ծածկույթով ժամկետանց մոդուլը tarantool-ի համար

Որոշ ժամանակ առաջ մենք բախվեցինք բացատներում բուկլետները մաքրելու խնդրին tarantool. Մաքրումը պետք էր սկսել ոչ թե այն ժամանակ, երբ տարանտուլը արդեն սպառվում էր հիշողությունից, այլ նախօրոք և որոշակի հաճախականությամբ։ Այս առաջադրանքի համար tarantool-ն ունի Lua-ով գրված մոդուլ, որը կոչվում է ժամկետի ավարտը. Այս մոդուլը կարճ ժամանակ օգտագործելուց հետո հասկացանք, որ այն մեզ հարմար չէ. մեծ քանակությամբ տվյալների անընդհատ մաքրման պատճառով Լուան կախվել է GC-ում: Հետևաբար, մենք մտածեցինք մշակելու մեր սեփական սահմանափակված ժամկետանց մոդուլը՝ հուսալով, որ մայրենի ծրագրավորման լեզվով գրված կոդը լավագույնս կլուծի մեր խնդիրները։

Մեզ համար լավ օրինակ էր tarantool մոդուլը, որը կոչվում էր հուշում. Դրանում կիրառվող մոտեցումը հիմնված է այն բանի վրա, որ տարածության մեջ ստեղծվում է առանձին դաշտ, որը ցույց է տալիս tuple-ի կյանքի տևողությունը, այլ կերպ ասած՝ 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)

Պարզության համար մենք գործարկում ենք tarantool այն գրացուցակում, որտեղ գտնվում է մեր libcapped-expirationd.so գրադարանը: Գրադարանից արտահանվում է երկու գործառույթ՝ սկսել և սպանել: Առաջին քայլը այս գործառույթները 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})

Այս օրինակը սկանավորման ժամանակ կաշխատի ճիշտ այնպես, ինչպես ժամկետանց մոդուլը, որը գրված է Lua-ով: Մեկնարկային ֆունկցիայի առաջին արգումենտը առաջադրանքի եզակի անվանումն է: Երկրորդը տարածության նույնացուցիչն է: Երրորդը եզակի ինդեքս է, որով կջնջվեն tuples-ները: Չորրորդը այն ցուցանիշն է, որով կանցնեն զույգերը: Հինգերորդը կյանքի տևողությամբ բազմակի դաշտի թիվն է (համարակալումը սկսվում է 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)

Այստեղ ամեն ինչ նույնն է, ինչ առաջին օրինակում, մի քանի բացառություններով: Մենք երրորդ դաշտի վերևում կառուցում ենք ծառի ինդեքս և այն անվանում ենք exp: Պարտադիր չէ, որ այս ցուցանիշը եզակի լինի՝ ի տարբերություն առաջնային կոչվող ինդեքսի։ Անցումը կիրականացվի էքսպրես ինդեքսով, իսկ ջնջումը` առաջնային: Մենք հիշում ենք, որ նախկինում երկուսն էլ արվում էին միայն առաջնային ինդեքսով։

Նախապատրաստական ​​աշխատանքից հետո մենք գործարկում ենք start ֆունկցիան նոր արգումենտներով.

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 ID-ով և 1576421257 տևողությամբ tuple-ից 3 ID-ով և 1576421287 տևողությամբ tuple տեղափոխվեց: 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;
};

Անվան հատկանիշը առաջադրանքի անունն է: Space_id հատկանիշը տարածության նույնացուցիչն է: rm_index_id հատկանիշը եզակի ինդեքսի նույնացուցիչն է, որով կջնջվեն բազմապատիկները: it_index_id հատկանիշն այն ինդեքսի նույնացուցիչն է, որով կանցնեն բազմակիները: It_index_type հատկանիշը ինդեքսի տեսակն է, որով կանցնեն տուպլերը: Filed_no հատկանիշը բազմակի դաշտի թիվն է կյանքի տևողությամբ: Scan_size հատկանիշը բազմակի առավելագույն քանակն է, որոնք սկանավորվում են մեկ գործարքում: Scan_time հատկանիշը սկանավորման ամբողջական ժամանակը վայրկյաններով է:

Մենք չենք քննարկի վերլուծական փաստարկները: Սա քրտնաջան, բայց պարզ աշխատանք է, որով գրադարանը կօգնի ձեզ msgpuck. Դժվարություններ կարող են առաջանալ միայն Lua-ից փոխանցված ինդեքսների դեպքում՝ որպես տվյալների բարդ կառուցվածք 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 ֆունկցիան ստուգում է tuple-ի կյանքը, expirationd_delete ջնջում է tuple, 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;
}

App

Դուք կարող եք դիտել աղբյուրի կոդը այստեղ այստեղ!

Source: www.habr.com

Добавить комментарий