Tarantool üçün öz müddəti bitmiş modulumuzu yazırıq

Tarantool üçün öz müddəti bitmiş modulumuzu yazırıq

Bir müddət əvvəl boşluqlarda tuplelərin təmizlənməsi problemi ilə qarşılaşdıq tarantool. Təmizliyə tarantool artıq yaddaşı tükəndiyi zaman deyil, əvvəlcədən və müəyyən bir tezlikdə başlamalı idi. Bu tapşırıq üçün tarantool-da Lua dilində yazılmış bir modul var son istifadə müddəti. Qısa müddət ərzində bu moduldan istifadə etdikdən sonra bunun bizim üçün uyğun olmadığını başa düşdük: böyük miqdarda məlumatların daimi təmizlənməsi səbəbindən Lua GC-də asıldı. Buna görə də, doğma proqramlaşdırma dilində yazılmış kodun problemlərimizi ən yaxşı şəkildə həll edəcəyinə ümid edərək, müddəti bitmiş öz modulumuzu inkişaf etdirməyi düşündük.

Bizim üçün yaxşı bir nümunə adlanan tarantool modulu idi memcached. Onda istifadə olunan yanaşma fəzada tüllərin ömrünü, başqa sözlə, ttl-ni göstərən ayrıca sahənin yaradılmasına əsaslanır. Arxa fonda olan modul məkanı skan edir, TTL-ni cari vaxtla müqayisə edir və silsilənin silinib-silinməyəcəyinə qərar verir. Memcached modul kodu sadə və zərif, lakin çox ümumidir. Birincisi, taranan və silinən indeksin növünü nəzərə almır. İkincisi, hər keçiddə sayı olduqca böyük ola bilən bütün tuplelər skan edilir. İstifadə müddəti bitmiş modulda birinci problem həll olundusa (ağac indeksi ayrıca bir sinifə ayrıldı), ikincisinə hələ də diqqət yetirilmədi. Bu üç məqam öz kodumu yazmağın lehinə seçimi əvvəlcədən müəyyənləşdirdi.

Təsvir

Tarantool üçün sənədlər çox yaxşıdır dərslik C-də saxlanan prosedurlarınızı necə yazmağınız haqqında. İlk növbədə, aşağıda görünəcək əmrlər və kodlar olan əlavələri başa düşmək üçün sizə onunla tanış olmağı təklif edirəm. Buna da diqqət yetirməyə dəyər istinad öz qapaqlı modulunuzu yazarkən mövcud olan obyektlərə, yəni qutu, lif, indeks и txn.

Uzaqdan başlayaq və müddəti bitmiş modulun kənardan necə göründüyünə baxaq:

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)

Sadəlik üçün libcapped-expirationd.so kitabxanamızın yerləşdiyi kataloqda tarantool-u işə salırıq. Kitabxanadan iki funksiya ixrac olunur: başlamaq və öldürmək. İlk addım box.schema.func.create və box.schema.user.grant istifadə edərək bu funksiyaları Lua-dan əlçatan etməkdir. Sonra dəstləri yalnız üç sahəni ehtiva edəcək bir boşluq yaradın: birincisi unikal identifikator, ikincisi e-poçt, üçüncüsü isə tuplenin ömrüdür. Birinci sahənin üstündə bir ağac indeksi qururuq və onu əsas adlandırırıq. Sonra doğma kitabxanamızla əlaqə obyektini alırıq.

Hazırlıq işindən sonra başlanğıc funksiyasını işə salın:

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

Bu nümunə skan zamanı Lua dilində yazılmış müddəti bitmiş modulla eyni şəkildə işləyəcək. Başlanğıc funksiyasının ilk arqumenti tapşırığın unikal adıdır. İkincisi boşluq identifikatorudur. Üçüncüsü, tuplelərin silinəcəyi unikal indeksdir. Dördüncüsü, çubuqların keçiləcəyi indeksdir. Beşincisi, ömür boyu olan tuple sahəsinin nömrəsidir (nömrələmə 1-dan deyil, 0-dən başlayır!). Altıncı və yeddinci skan parametrləridir. 1024, bir əməliyyatda baxıla bilən dələlərin maksimum sayıdır. 3600 — saniyələrdə tam skan müddəti.

Qeyd edək ki, nümunə tarama və silmək üçün eyni indeksdən istifadə edir. Bu ağac indeksidirsə, keçid kiçik açardan böyüyə doğru aparılır. Əgər başqa, məsələn, hash indeksi varsa, o zaman keçid, bir qayda olaraq, təsadüfi qaydada həyata keçirilir. Bütün boşluq dəstləri bir skanda skan edilir.

Ömrü 60 saniyə olan fəzaya bir neçə dəst daxil edək:

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}

Daxiletmənin uğurlu olub olmadığını yoxlayaq:

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

Gəlin seçimi 60+ saniyədən sonra təkrarlayaq (birinci dəftərin daxil edilməsinin əvvəlindən hesablanır) və bitmiş modulun artıq işləndiyini görək:

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

Tapşırığı dayandıraq:

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

Tarama üçün ayrıca indeksin istifadə olunduğu ikinci nümunəyə baxaq:

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)

Burada hər şey bir neçə istisna olmaqla, birinci nümunədəki kimidir. Üçüncü sahənin üstündə ağac indeksi qururuq və onu exp adlandırırıq. Bu indeks ilkin adlanan indeksdən fərqli olaraq unikal olmalıdır. Keçmə eks indeksi ilə, silinmə isə birincil tərəfindən həyata keçiriləcək. Xatırlayırıq ki, əvvəllər hər ikisi yalnız ilkin indeksdən istifadə edilirdi.

Hazırlıq işindən sonra başlanğıc funksiyasını yeni arqumentlərlə işlədirik:

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

Gəlin, 60 saniyəlik bir ömür ilə yenidən boşluğa bir neçə dəst daxil edək:

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 saniyədən sonra bənzətmə ilə daha bir neçə dəst əlavə edəcəyik:

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}

Daxiletmənin uğurlu olub olmadığını yoxlayaq:

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

Gəlin seçimi 60+ saniyədən sonra təkrarlayaq (birinci dəftərin daxil edilməsinin əvvəlindən hesablanır) və bitmiş modulun artıq işləndiyini görək:

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

Kosmosda hələ də yaşamaq üçün təxminən 30 saniyə qalacaq bəzi tuplelər qalıb. Üstəlik, identifikatoru 2 və ömrü 1576421257 olan dəstdən identifikatoru 3 və ömrü 1576421287 olan dəstdən keçərkən skan dayandırıldı. Ömrü 1576421287 və ya daha çox olan dəstlər sıralanmasına görə skan edilmədi. exp indeksi düymələri. Bu, ən əvvəldən əldə etmək istədiyimiz qənaətdir.

Tapşırığı dayandıraq:

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

Tətbiq

Layihənin bütün xüsusiyyətləri haqqında məlumat verməyin ən yaxşı yolu onun orijinal mənbəyidir. kod! Nəşrin bir hissəsi olaraq, biz yalnız ən vacib məqamlara, yəni kosmik keçid alqoritmlərinə diqqət yetirəcəyik.

Başlanğıc metoduna verdiyimiz arqumentlər expirationd_task adlı strukturda saxlanılır:

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

Ad atributu tapşırığın adıdır. space_id atributu boşluq identifikatorudur. rm_index_id atributu, dəstlərin silinəcəyi unikal indeksin identifikatorudur. it_index_id atributu çubuqların keçiləcəyi indeksin identifikatorudur. it_index_type atributu dələlərin keçiləcəyi indeks növüdür. filed_no atributu ömür boyu olan tuple sahəsinin sayıdır. scan_size atributu bir əməliyyatda skan edilən dələlərin maksimum sayıdır. scan_time atributu saniyələrlə tam skan vaxtıdır.

Biz arqumentlərin təhlilini nəzərdən keçirməyəcəyik. Bu, zəhmətli, lakin sadə bir işdir və kitabxana sizə kömək edəcəkdir msgpuck. Çətinliklər yalnız mp_map tipli mürəkkəb verilənlər strukturu kimi Lua-dan ötürülən və sadə mp_bool, mp_double, mp_int, mp_uint və mp_array tiplərindən istifadə edilməyən indekslərlə yarana bilər. Ancaq bütün indeksi təhlil etməyə ehtiyac yoxdur. Sadəcə onun unikallığını yoxlamaq, növü hesablamaq və identifikatoru çıxarmaq lazımdır.

Biz təhlil üçün istifadə olunan bütün funksiyaların prototiplərini sadalayırıq:

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

İndi keçək ən vacib şeyə - boşluqdan yan keçmək və tupleləri silmək məntiqinə. scan_size-dən böyük olmayan hər bir blok bloku bir əməliyyat altında skan edilir və dəyişdirilir. Uğurlu olarsa, bu əməliyyat həyata keçirilir, xəta baş verərsə, geri qaytarılır. expirationd_iterate funksiyasının sonuncu arqumenti skanlamanın başladığı və ya davam etdiyi iteratorun göstəricisidir. Bu iterator xəta baş verənə, boşluq bitənə və ya prosesi əvvəlcədən dayandırmaq mümkün olmayana qədər daxili olaraq artırılır. expirationd_expired funksiyası qovluğun ömrünü yoxlayır, expirationd_delete silsiləni silir, expirationd_breakable bizim davam etməyimiz lazım olub-olmadığını yoxlayır.

Expirationd_iterate funksiya kodu:

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

Funksiya kodunun müddəti bitdi_keçdi:

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 funksiya kodu:

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

Funksiya kodu 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

Mənbə koduna burada baxa bilərsiniz burada!

Mənbə: www.habr.com

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