نوشتن ماژول منقضی شده خودمان برای تارانتول

نوشتن ماژول منقضی شده خودمان برای تارانتول

مدتی پیش با مشکل نظافت تاپل ها در فضاها مواجه بودیم رتیل. تمیز کردن باید نه زمانی که حافظه رتیل در حال اتمام بود، بلکه از قبل و با فرکانس مشخص شروع می شد. برای این کار، tarantool یک ماژول دارد که به زبان Lua نوشته شده است به نام انقضاء. پس از استفاده کوتاه از این ماژول، متوجه شدیم که برای ما مناسب نیست: به دلیل تمیز کردن مداوم مقادیر زیادی از داده ها، Lua در GC آویزان شد. بنابراین، ما به توسعه ماژول منقضی شده خودمان فکر کردیم، به این امید که کدهای نوشته شده در یک زبان برنامه نویسی بومی مشکلات ما را به بهترین شکل ممکن حل کند.

یک مثال خوب برای ما ماژول tarantool به نام بود memcached. رویکرد به کار رفته در آن بر این اساس است که یک فیلد مجزا در فضا ایجاد می شود که نشان دهنده طول عمر تاپل و به عبارتی ttl است. ماژول در پس‌زمینه فضا را اسکن می‌کند، TTL را با زمان فعلی مقایسه می‌کند و تصمیم می‌گیرد که آیا تاپل را حذف کند یا خیر. کد ماژول memcached ساده و ظریف است، اما بیش از حد عمومی است. اولاً، نوع شاخصی که در حال خزیدن و حذف است را در نظر نمی گیرد. ثانیاً، در هر گذر، تمام تاپل ها اسکن می شوند که تعداد آنها می تواند بسیار زیاد باشد. و اگر در ماژول منقضی شده اولین مشکل حل شد (شاخص درختی به یک کلاس جداگانه جدا شد) ، دومی هنوز هیچ توجهی دریافت نکرد. این سه نقطه انتخاب را به نفع نوشتن کد خودم از پیش تعیین کرد.

شرح

مستندات برای رتیل بسیار خوب است آموزش در مورد نحوه نوشتن رویه های ذخیره شده خود در 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 ما در آن قرار دارد راه اندازی می کنیم. دو تابع از کتابخانه صادر می شود: شروع و کشتن. اولین قدم این است که این توابع را با استفاده از box.schema.func.create و box.schema.user.grant از Lua در دسترس قرار دهید. سپس فضایی ایجاد کنید که تاپل های آن فقط شامل سه فیلد باشد: اولی یک شناسه منحصر به فرد، دومی ایمیل و سومی طول عمر تاپل است. در بالای فیلد اول یک شاخص درختی می سازیم و آن را اولیه می نامیم. سپس شیء اتصال را به کتابخانه اصلی خود دریافت می کنیم.

پس از کار مقدماتی، تابع start را اجرا کنید:

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 نوشته شده است کار می کند. اولین آرگومان تابع start نام منحصر به فرد وظیفه است. دومی شناسه فضا است. سومین شاخص منحصر به فردی است که توسط آن تاپل ها حذف می شوند. چهارمین شاخصی است که توسط آن تاپل ها طی خواهند شد. پنجمین عدد فیلد تاپل با طول عمر است (شماره‌گذاری از 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 می نامیم. این شاخص برخلاف شاخصی که اولیه نامیده می شود، لازم نیست منحصر به فرد باشد. پیمایش با نمایه 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 و طول عمر 1576421257 به یک تاپل با شناسه 3 و طول عمر 1576421287 متوقف شد. تاپل های با طول عمر 1576421287 یا بیشتر به دلیل ترتیب دادن اسکن نشدند. کلیدهای شاخص exp این پس‌اندازی است که در همان ابتدا می‌خواستیم به آن برسیم.

بیایید کار را متوقف کنیم:

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

اجرا

بهترین راه برای بیان همه ویژگی های یک پروژه منبع اصلی آن است. رمز! به عنوان بخشی از انتشار، ما فقط بر روی مهمترین نکات، یعنی الگوریتم های دور زدن فضا تمرکز خواهیم کرد.

آرگومان هایی که به متد start ارسال می کنیم در ساختاری به نام 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. مشکلات فقط با نمایه‌هایی که از 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 طول عمر یک تاپل را بررسی می‌کند، 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

اضافه کردن نظر