الروك - سائق rocksdb لارلانغ

مقدمة


على الإنترنت ، هناك الكثير من المعلومات والنقاشات حول اختيار نهج sql / nosql ، بالإضافة إلى إيجابيات وسلبيات تخزين KV أو آخر. ما تقرأه الآن ليس دليل rocksdb أو الإثارة لاستخدام هذا المستودع وسائقي لذلك. أود أن أشارك النتيجة المؤقتة لتحسين عملية تطوير NIF لـ Erlang. تقدم هذه المقالة محركًا عمليًا لـ rocksdb ، تم تطويره على مدار أمسيات.


لذلك ، نشأت المهمة في أحد المشاريع عن معالجة موثوقة لحجم كبير من الأحداث. يستغرق كل حدث من 50 إلى 350 بايت ، ويتم إنشاء أكثر من 80 مليون حدث لكل عقدة في اليوم. فقط أريد أن نلاحظ أن قضايا التسامح مع الخطأ لتسليم الرسالة إلى العقد لا يتم النظر فيها. أيضا أحد قيود المعالجة هو التغيير الذري والثابت لمجموعة الأحداث.


وبالتالي ، فإن المتطلبات الرئيسية للسائق هي:


  1. الموثوقية
  2. الأداء
  3. الأمن (بالمعنى القانوني)
  4. الوظيفة:
    • جميع وظائف kv الأساسية
    • عائلات العمود
    • المعاملات
    • ضغط البيانات
    • دعم تكوين التخزين المرن
  5. قاعدة الرمز الدنيا

نظرة عامة على الحلول الموجودة


  • erocksdb هو حل من مطوري leofs. تشمل المزايا الاستحسان في مشروع حقيقي. من خلال السلبيات - قاعدة رمز قديمة وعدم وجود المعاملات. يعتمد برنامج التشغيل هذا على rocksdb 4.13.
  • لدى rockse عدد من القيود ، على سبيل المثال ، عدم وجود خيارات التكوين ، ولكن الأهم من ذلك ، يجب أن تكون جميع المفاتيح والقيم عبارة عن سلاسل. دخل في المراجعة فقط كمثال على عدد من السائقين الذين ينفذون وظيفة أو أخرى ويحدون من وظيفة أخرى.
  • erlang-rocksdb هو مشروع كامل المواصفات ، بدأ تطويره في عام 2014. مثل erocksdb يستخدم في المشاريع الحقيقية. لديها قاعدة كود كبيرة في C / C ++ ووظائف واسعة. هذا المحرك مناسب للممارسة العامة والاستخدام في معظم المشاريع.

بعد تحليل سريع للوضع الحالي مع السائقين erlang لـ rocksdb ، أصبح من الواضح أنه لا أحد منهم يلبي تمامًا متطلبات المشروع. على الرغم من أنه من الممكن استخدام erlang-rocksdb ، كانت هناك أمسيات مجانية ، وبعد التطوير والتنفيذ الناجحين لمرشح بلوم على الصدأ والفضول: هل من الممكن تنفيذ جميع متطلبات المشروع الحالي وتنفيذ معظم الوظائف في NIF في فترة زمنية قصيرة؟


الروك


Rocker هو NIF لـ Erlang ، باستخدام غلاف Rust لـ rocksdb. الميزات الرئيسية هي الأمان والأداء والحد الأدنى من قاعدة التعليمات البرمجية. يتم تخزين المفاتيح والبيانات في شكل ثنائي ، والتي لا تفرض أي قيود على تنسيق التخزين. في الوقت الحالي ، المشروع مناسب للاستخدام في حلول الطرف الثالث.
كود المصدر موجود في مستودع المشروع .


نظرة عامة على API


فتحة القاعدة


يمكن العمل مع قاعدة البيانات في وضعين:


  1. إجمالي مساحة المفتاح. في هذا الوضع ، سيتم وضع جميع مفاتيحك في مجموعة واحدة. يتيح لك Rocksdb تكوين خيارات التخزين للمهام الحالية بمرونة. بناءً على ذلك ، يمكن فتح قاعدة البيانات بطريقتين:


    • باستخدام مجموعة قياسية من الخيارات


      rocker:open_default(<<"/project/priv/db_default_path">>) -> {ok, Db}. 

      ستكون نتيجة هذه العملية مؤشر للعمل مع قاعدة البيانات ، وسيتم حظر قاعدة البيانات لأية محاولات أخرى لفتحها. سيتم إلغاء تأمين قاعدة البيانات تلقائيًا بعد مسح هذا المؤشر.


    • إما تكوين خيارات المهمة
       {ok, Db} = rocker:open(<<"/project/priv/db_path">>, #{ create_if_missing => true, set_max_open_files => 1000, set_use_fsync => false, set_bytes_per_sync => 8388608, optimize_for_point_lookup => 1024, set_table_cache_num_shard_bits => 6, set_max_write_buffer_number => 32, set_write_buffer_size => 536870912, set_target_file_size_base => 1073741824, set_min_write_buffer_number_to_merge => 4, set_level_zero_stop_writes_trigger => 2000, set_level_zero_slowdown_writes_trigger => 0, set_max_background_compactions => 4, set_max_background_flushes => 4, set_disable_auto_compactions => true, set_compaction_style => universal }). 

  2. الانقسام إلى عدة مساحات. يتم تخزين المفاتيح في مجموعات الأعمدة المسماة ، ويمكن أن يكون لكل مجموعة أعمدة خيارات مختلفة. دعنا نفكر في مثال على فتح قاعدة بيانات بخيارات قياسية لجميع مجموعات الأعمدة
     {ok, Db} = case rocker:list_cf(BookDbPath) of {ok, CfList} -> rocker:open_cf_default(BookDbPath, CfList); _Else -> CfList = [], rocker:open_default(BookDbPath) end. 

إزالة القاعدة


لحذف قاعدة البيانات بشكل صحيح ، يجب عليك استدعاء rocker:destroy(Path). في هذه الحالة ، لا ينبغي استخدام القاعدة.


استعادة قاعدة البيانات بعد الفشل


في حالة فشل النظام ، يمكن استعادة قاعدة البيانات باستخدام طريقة rocker:repair(Path) ، وتتكون هذه العملية من 4 خطوات:


  1. البحث عن ملف
  2. استعادة الجداول عن طريق لعب WAL
  3. استخراج البيانات الوصفية
  4. سجل الواصف

تكوين عائلة عمود


 Cf = <<"testcf1">>, rocker:create_cf_default(Db, Cf) -> ok. 

إزالة عائلة العمود


 Cf = <<"testcf1">>, rocker:drop_cf(Db, Cf) -> ok. 

عمليات CRUD


إدخال البيانات الرئيسية

 rocker:put(Db, <<"key">>, <<"value">>) -> ok. 

الحصول على البيانات عن طريق المفتاح

 rocker:get(Db, <<"key">>) -> {ok, <<"value">>} | notfound 

حذف البيانات الرئيسية

 rocker:delete(Db, <<"key">>) -> ok. 

إدخال البيانات الرئيسية في CF

 rocker:put_cf(Db, <<"testcf">>, <<"key">>, <<"value">>) -> ok. 

استرجاع البيانات الرئيسية داخل CF

 rocker:get_cf(Db, <<"testcf">>, <<"key">>) -> {ok, <<"value">>} | notfound 

إزالة مفتاح CF

 rocker:delete_cf(Db, <<"testcf">>, <<"key">>) -> ok 

المكرر


كما تعلم ، فإن أحد المبادئ الأساسية لـ rocksdb هو التخزين المنظم للمفاتيح. هذه الميزة مفيدة للغاية في المهام الحقيقية. لاستخدامها ، نحتاج إلى تكرار البيانات. يحتوي Rocksdb على عدة أوضاع لتمرير البيانات (يمكن العثور على أمثلة كود تفصيلية في الاختبارات ):


  • من بداية الجدول. الروك هو المسؤول عن ذلك في المكرر {'start'}
  • من نهاية الجدول: {'end'}
  • بدءًا من مفتاح معين للأمام {'from', Key, forward}
  • بدءًا من مفتاح معين للخلف {'from', Key, reverse}

تجدر الإشارة إلى أن هذه الأوضاع تعمل أيضًا لتمرير البيانات المخزنة في مجموعات الأعمدة.


قم بإنشاء مكرر

 rocker:iterator(Db, {'start'}) -> {ok, Iter}. 

الاختيار المكرر

 rocker:iterator_valid(Iter) -> {ok, true} | {ok, false}. 

إنشاء مكرر لل CF

 rocker:iterator_cf(Db, Cf, {'start'}) -> {ok, Iter}. 

إنشاء البادئة المكرر

يتطلب مكرر البادئة تحديد طول البادئة بشكل صريح عند إنشاء قاعدة البيانات.


 {ok, Db} = rocker:open(Path, #{ prefix_length => 3 }). 

مثال على إنشاء مكرر باستخدام بادئة "aaa":


 {ok, Iter} = rocker:prefix_iterator(Db, <<"aaa">>). 

إنشاء البادئة المكرر ل CF

مشابه prefix_length البادئة السابقة ، فإنه يتطلب prefix_length لمجموعة الأعمدة


 {ok, Iter} = rocker:prefix_iterator_cf(Db, Cf, <<"aaa">>). 

احصل على العنصر التالي

تقوم الطريقة بإرجاع المفتاح / القيمة التالية ، أو موافق إذا اكتمل المكرر.


 rocker:next(Iter) -> {ok, <<"key">>, <<"value">>} | ok 

المعاملات


من الشائع إلى حد ما ضرورة تسجيل التغييرات في وقت واحد لمجموعة رئيسية. يسمح لك Rocker بدمج عمليات CRUD داخل مجموعة مشتركة وضمن CF.
يوضح هذا المثال العمل مع المعاملات:


 {ok, 6} = rocker:tx(Db, [ {put, <<"k1">>, <<"v1">>}, {put, <<"k2">>, <<"v2">>}, {delete, <<"k0">>, <<"v0">>}, {put_cf, Cf, <<"k1">>, <<"v1">>}, {put_cf, Cf, <<"k2">>, <<"v2">>}, {delete_cf, Cf, <<"k0">>, <<"v0">>} ]). 

الأداء


يمكنك العثور على اختبار أداء في مجموعة الاختبار. يعرض حوالي 30 كيلو RPS للكتابة و 200 كيلو RPS للقراءة على جهازي. في الظروف الحقيقية ، يمكنك توقع 15-20 ألف RPS للكتابة وحوالي 120 ألف RPS للقراءة بمتوسط ​​حجم بيانات يبلغ حوالي 1 كيلوبايت لكل مفتاح والعدد الإجمالي للمفاتيح أكثر من 1 مليار.


الخلاصة


سمح لنا تطوير وتطبيق Rocker في مشروعنا بتقليل وقت استجابة النظام وزيادة الموثوقية وتقليل وقت إعادة التشغيل. تم الحصول على هذه المزايا بأقل تكاليف التطوير والتنفيذ.


بالنسبة لي ، استنتجت أنه بالنسبة لمشروعات Erlang التي تتطلب التحسين ، فإن استخدام Rust هو الأمثل. تمكن Erlang من تنفيذ 95٪ من التعليمات البرمجية بسرعة وفعالية ، بينما يقوم Rust بإعادة كتابة / إضافة مثبط 5٪ دون المساومة على موثوقية النظام بشكل عام.


ملاحظة: هناك تجربة إيجابية في تطوير NIF لحسابات الدقة التعسفية في Erlang ، والتي يمكن كتابتها في مقالة منفصلة. أود أن أوضح ما إذا كان موضوع NIF على Rust مثيرًا للاهتمام للمجتمع؟

Source: https://habr.com/ru/post/ar413353/


All Articles