Tarantool-д зориулж хугацаа нь дууссан модулийг өөрсдөө бичиж байна

Tarantool-д зориулж хугацаа нь дууссан модулийг өөрсдөө бичиж байна

Хэсэг хугацааны өмнө бид орон зай дахь залгууруудыг цэвэрлэх асуудалтай тулгарсан тарантуул. Цэвэрлэгээг tarantool-ийн санах ой аль хэдийн дууссан үед биш, харин урьдчилан, тодорхой давтамжтайгаар эхлүүлэх шаардлагатай байв. Энэ даалгаврын хувьд tarantool нь Луа хэлээр бичигдсэн модультай дуусах хугацаа. Энэ модулийг богино хугацаанд ашигласны дараа энэ нь бидний хувьд тохиромжгүй гэдгийг ойлгосон: их хэмжээний өгөгдлийг байнга цэвэрлэж байсны улмаас Луа GC-д өлгөгдсөн. Тиймээс бид төрөлх програмчлалын хэл дээр бичигдсэн код нь бидний асуудлыг хамгийн сайн аргаар шийднэ гэж найдаж, өөрийн хязгаарлагдмал хугацаа дууссан модулийг хөгжүүлэх талаар бодсон.

Бидний хувьд сайн жишээ бол tarantool модуль юм memcached. Үүнд ашигласан арга нь орон зайд тусдаа талбар үүсгэгдсэн бөгөөд энэ нь tuple-ийн ашиглалтын хугацааг, өөрөөр хэлбэл ttl-ийг заадаг. Арын арын модуль нь орон зайг сканнердаж, TTL-ийг одоогийн цагтай харьцуулж, tuple устгах эсэхээ шийддэг. 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-ийг ажиллуулдаг. Номын сангаас хоёр функцийг экспортлодог: эхлүүлэх, устгах. Эхний алхам бол box.schema.func.create болон box.schema.user.grant ашиглан Lua-аас эдгээр функцийг ашиглах боломжтой болгох явдал юм. Дараа нь tuple нь зөвхөн гурван талбар агуулсан зай үүсгэ: эхнийх нь өвөрмөц танигч, хоёр дахь нь имэйл, гурав дахь нь tuple-н ашиглалтын хугацаа юм. Бид эхний талбарын орой дээр модны индексийг барьж, түүнийг үндсэн гэж нэрлэдэг. Дараа нь бид төрөлх номын сантайгаа холбогдох объектыг авна.

Бэлтгэл ажил дууссаны дараа эхлүүлэх функцийг ажиллуулна уу:

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

Энэ жишээ нь сканнердах явцад Луа хэл дээр бичигдсэн хугацаа нь дууссан модультай яг адилхан ажиллах болно. Эхлэх функцийн эхний аргумент нь даалгаврын өвөрмөц нэр юм. Хоёр дахь нь орон зай танигч юм. Гурав дахь нь багцуудыг устгах өвөрмөц индекс юм. Дөрөвдүгээрт нь залгууруудыг туулах индекс юм. Тав дахь нь ашиглалтын хугацаатай tuple талбарын тоо (дугаарлах нь 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 ID-тай, 1576421257 дугаартай tuple-аас 3 дугаартай, 1576421287 ашиглалтын хугацаатай tuple руу шилжих үед скан хийх ажиллагаа зогссон. exp индексийн түлхүүрүүд. Энэ бол бидний хамгийн эхэнд хүрэхийг хүсч байсан хэмнэлт юм.

Даалгавраа зогсооё:

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

Реализация

Төслийн бүх шинж чанарыг хэлэх хамгийн сайн арга бол түүний анхны эх сурвалж юм. код! Нийтлэлийн нэг хэсэг болгон бид зөвхөн хамгийн чухал цэгүүд, тухайлбал орон зайг тойрон гарах алгоритмууд дээр анхаарлаа хандуулах болно.

Бидний эхлүүлэх арга руу дамжуулдаг аргументууд нь хугацаа дууссан_даалгавар хэмээх бүтцэд хадгалагддаг:

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 шинж чанар нь ашиглалтын хугацаатай tuple талбарын тоо юм. scan_size атрибут нь нэг гүйлгээнд сканнердсан багцын хамгийн их тоо юм. scan_time шинж чанар нь секундээр бүрэн скан хийх хугацаа юм.

Бид аргументуудыг задлан шинжлэхийг авч үзэхгүй. Энэ бол маш хэцүү боловч энгийн ажил бөгөөд номын сан танд туслах болно msgpuck. Зөвхөн mp_bool, mp_double, mp_int, mp_uint, mp_array гэсэн энгийн төрлүүдийг ашиглаагүй, Lua-аас mp_map төрлийн нарийн төвөгтэй өгөгдлийн бүтэц болгон дамжуулсан индексүүд л хүндрэлтэй байдаг. Гэхдээ индексийг бүхэлд нь задлан шинжлэх шаардлагагүй. Та зүгээр л түүний өвөрмөц байдлыг шалгаж, төрлийг тооцоолж, танигчийг задлах хэрэгтэй.

Бид задлан шинжлэхэд ашигладаг бүх функцүүдийн прототипүүдийг жагсаав.

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

Функцийн кодын хугацаа дууссан_хугацаа дууссан:

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

Дуусах_устгах функцийн код:

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

Функцийн кодын хугацаа дуусах_тасрах боломжтой:

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

сэтгэгдэл нэмэх