منذ بعض الوقت واجهنا مشكلة تنظيف الصفوف في المساحات
ومن الأمثلة الجيدة بالنسبة لنا وحدة Tarantool التي تسمى
وصف
وثائق Tarantool جيدة جدًا
لنبدأ من بعيد ونلقي نظرة على الشكل الذي تبدو عليه الوحدة منتهية الصلاحية من الخارج:
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. الوسيطة الأولى لوظيفة البداية هي الاسم الفريد للمهمة. والثاني هو معرف الفضاء. والثالث هو فهرس فريد سيتم من خلاله حذف الصفوف. والرابع هو المؤشر الذي سيتم من خلاله اجتياز الصفوف. الخامس هو رقم حقل الصف مع العمر (الترقيم يبدأ من 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، والحذف بواسطة الأساسي. نتذكر أنه في السابق تم إجراء كليهما باستخدام الفهرس الأساسي فقط.
بعد الانتهاء من الأعمال التحضيرية، نقوم بتشغيل وظيفة البداية باستخدام وسائط جديدة:
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;
};
سمة الاسم هي اسم المهمة. السمة space_id هي معرف المساحة. السمة rm_index_id هي معرف الفهرس الفريد الذي سيتم من خلاله حذف المجموعات. السمة it_index_id هي معرف الفهرس الذي سيتم من خلاله اجتياز الصف. السمة it_index_type هي نوع الفهرس الذي سيتم من خلاله اجتياز الصف. السمة file_no هي رقم حقل الصف مع العمر. السمة scan_size هي الحد الأقصى لعدد المجموعات التي يتم فحصها في معاملة واحدة. السمة scan_time هي وقت الفحص الكامل بالثواني.
لن نفكر في تحليل الحجج. هذه مهمة شاقة ولكنها بسيطة، وستساعدك المكتبة فيها
ندرج النماذج الأولية لجميع الوظائف المستخدمة للتحليل:
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