Tarantool үшін өзіміздің жарамдылық мерзімі біткен модульді жазу

Tarantool үшін өзіміздің жарамдылық мерзімі біткен модульді жазу

Біраз уақыт бұрын біз кеңістіктердегі кортеждерді тазалау мәселесіне тап болдық тарантул. Тазалауды tarantool жады таусылған кезде емес, алдын ала және белгілі бір жиілікте бастау керек болды. Бұл тапсырма үшін tarantool-да Lua тілінде жазылған модуль бар жарамдылық мерзімі. Бұл модульді аз уақыт пайдаланғаннан кейін біз оның бізге жарамсыз екенін түсіндік: үлкен көлемдегі деректерді үнемі тазалауға байланысты Луа GC-де ілулі тұрды. Сондықтан, біз өзіміздің жарамдылық мерзімі біткен модульді әзірлеу туралы ойладық, ана тілде жазылған код біздің мәселелерімізді ең жақсы жолмен шешеді деп үміттендік.

Біз үшін жақсы үлгі деп аталатын tarantool модулі болды memcached. Онда қолданылатын тәсіл кеңістікте кортеждің өмір сүру уақытын, басқаша айтқанда, ttl көрсететін жеке өрістің құрылуына негізделген. Фондағы модуль бос орынды сканерлейді, TTL-ді ағымдағы уақытпен салыстырады және кортежді жою немесе жою туралы шешім қабылдайды. Memcached модуль коды қарапайым және талғампаз, бірақ тым жалпы. Біріншіден, ол тексерілетін және жойылатын индекс түрін есепке алмайды. Екіншіден, әрбір өтуде барлық кортеждер сканерленеді, олардың саны айтарлықтай үлкен болуы мүмкін. Ал егер мерзімі біткен модульде бірінші мәселе шешілсе (ағаш индексі жеке сыныпқа бөлінген), екіншісіне әлі де назар аударылмады. Бұл үш тармақ менің жеке кодты жазу пайдасына таңдауды алдын ала анықтады.

сипаттамасы

Tarantool үшін құжаттама өте жақсы оқу құралы С тілінде сақталатын процедураларды қалай жазу керектігі туралы. Ең алдымен, төменде пайда болатын пәрмендер мен кодтары бар кірістірулерді түсіну үшін онымен танысуды ұсынамын. Оған да назар аударған жөн анықтама өзіңіздің жабық модульіңізді жазу кезінде қол жетімді нысандарға, атап айтқанда қорап, талшықты, көрсеткіш и 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})

Бұл мысал сканерлеу кезінде Lua тілінде жазылған жарамдылық мерзімі аяқталған модуль сияқты жұмыс істейді. Бастау функциясының бірінші аргументі тапсырманың бірегей атауы болып табылады. Екіншісі - кеңістік идентификаторы. Үшіншісі - кортеждер жойылатын бірегей индекс. Төртінші - кортеждер өтетін индекс. Бесінші - қызмет ету мерзімі бар кортеж өрісінің нөмірі (нөмірлеу 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 деп атаймыз. Бұл индекс бастапқы деп аталатын индекстен айырмашылығы бірегей болуы шарт емес. Жылжыту экс индексі бойынша, ал жою бастапқы бойынша жүзеге асырылады. Бұрын екеуі де тек бастапқы индекс арқылы жасалғаны есімізде.

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

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 болатын кортежден идентификаторы 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_bool, mp_double, mp_int, mp_uint және mp_array қарапайым түрлерін пайдаланбай, mp_map түрі бар күрделі деректер құрылымы ретінде Lua-дан берілген индекстермен ғана туындауы мүмкін. Бірақ бүкіл индексті талдаудың қажеті жоқ. Тек оның бірегейлігін тексеріп, түрін есептеп, идентификаторды шығарып алу керек.

Біз талдау үшін қолданылатын барлық функциялардың прототиптерін тізімдейміз:

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

Қолданба

Бастапқы кодты мына жерден көре аласыз осында!

Ақпарат көзі: www.habr.com

пікір қалдыру