
منذ بعض الوقت ، واجهنا مشكلة تنظيف tuples في مساحات tarantool. كان لا بد من البدء في التنظيف ليس عندما نفدت ذاكرة التانتول بالفعل ، ولكن مقدمًا وبتردد معين. Tarantool لديه وحدة نمطية مكتوبة في لوا لهذه المهمة تسمى انتهاء الصلاحية . بعد استخدام هذه الوحدة لفترة قصيرة ، أدركنا أنها غير مناسبة لنا: بالنسبة للتنظيف المستمر للكميات الكبيرة من البيانات ، علق Lua في GC. لذلك ، فكرنا في تطوير وحدة انتهاء الصلاحية المغطاة لدينا ، على أمل أن الشفرة المكتوبة بلغة البرمجة الأصلية ستحل مشكلاتنا بأفضل طريقة.
مثال جيد على ذلك هو وحدة tarantool التي تسمى memcached . تعتمد الطريقة المستخدمة في ذلك على حقيقة أن حقلًا منفصلاً قد تم إدخاله في الفراغ الذي يشار فيه إلى طول عمر المجموعة ، بمعنى آخر ، 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)
للبساطة ، قم بتشغيل 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. الوسيطة الأولى لدالة البدء هي الاسم الفريد للمهمة. والثاني هو معرف الفضاء. والثالث هو فهرس فريد من نوعه حيث سيتم حذف tuples. الرابع هو الفهرس الذي سيتم من خلاله تجاوز التلاميذ. خامسًا - رقم الحقل tuple مع العمر (الترقيم يمتد من 1 وليس 0!). السادس والسابع - إعدادات المسح. 1024 هو الحد الأقصى لعدد tuples التي يمكن عرضها في معاملة واحدة. 3600 - وقت الفحص الكامل في ثوان.
لاحظ أنه يتم استخدام الفهرس نفسه للزحف والحذف في المثال. إذا كان فهرس شجرة ، فسيكون الزحف من المفتاح الأصغر إلى المفتاح الأكبر. إذا كان البعض الآخر ، على سبيل المثال ، مؤشر التجزئة ، فسيتم إجراء النقل ، كقاعدة عامة ، في أمر تعسفي. في فحص واحد ، يتم عرض جميع tuples الفضاء.
لنقم بإدخال العديد من الأنابيب في الفضاء مع عمر 60 ثانية:
box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}
تحقق من نجاح الإدراج:
tarantool> box.space.tester.index.primary:select()
كرر التحديد بعد 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})
مرة أخرى ، سنقوم بإدخال عدة tuples في الفضاء مع عمر 60 ثانية:
box.space.tester:insert{0, 'user0@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{1, 'user1@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{2, 'user2@tarantool.io', math.floor(fiber.time()) + 60}
بعد 30 ثانية ، عن طريق القياس ، أضف عددًا قليلًا من المجموعات الإضافية:
box.space.tester:insert{3, 'user3@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{4, 'user4@tarantool.io', math.floor(fiber.time()) + 60} box.space.tester:insert{5, 'user5@tarantool.io', math.floor(fiber.time()) + 60}
تحقق من نجاح الإدراج:
tarantool> box.space.tester.index.primary:select()
كرر التحديد بعد 60+ ثانية (عد من بداية إدخال المجموعة الأولى) وشاهد أن وحدة انتهاء الصلاحية المغطاة قد عملت بالفعل:
tarantool> box.space.tester.index.primary:select()
بقي Tuples في الفضاء ، الذي يعيش لمدة 30 ثانية. علاوة على ذلك ، توقف المسح عند التبديل من tuple ذي المعرف 2 وعمر 1576421257 إلى tuple ذي المعرف 3 وعمر 1576421287. لم يتم عرض Tuples بعمر 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 هي نوع الفهرس الذي سيتم به اجتياز tuples. السمة filed_no هي رقم الحقل tuple مع العمر. السمة 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; }
رمز انتهاء الصلاحية:
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); }
رمز الدالة Expirationd_breakable:
static bool expirationd_breakable(struct expirationd_task *task) { return task->it_index_id != task->rm_index_id && task->it_index_type == ITER_GT; }
تطبيق
تحقق من شفرة المصدر هنا !