كائنات "حذف" في جانغو



عاجلاً أم آجلاً ، يواجه المطورون مهمة إزالة البيانات غير الضرورية. وكلما كانت الخدمة أكثر تعقيدًا ، زادت الفروق الدقيقة التي تحتاج إلى أخذها في الاعتبار. في هذه المقالة ، سوف أصف كيف قمنا بتنفيذ "الحذف" في قاعدة بيانات تحتوي على مئات الروابط.

الخلفية


لمراقبة إمكانية تشغيل معظم المشاريع ، تستخدم Mail.ru Group و VKontakte خدمة لتطويرها - المراقبة . بدءاً من تاريخه منذ نهاية عام 2012 ، نما المشروع على مدار 6 سنوات ليصبح نظامًا ضخمًا ، اكتسب الكثير من الوظائف. تقوم المراقبة بانتظام بالتحقق من توفر الخوادم وصحة الردود على الطلبات ، وجمع الإحصاءات حول الذاكرة المستخدمة ، واستخدام وحدة المعالجة المركزية ، إلخ. عندما تتجاوز معلمات الخادم المراقبة القيم المسموح بها ، يتلقى المسؤولون عن الخادم إعلامات في النظام وعن طريق الرسائل القصيرة.

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

حذف


أنا على دراية في عنوان المقال بكلمة "حذف" كتبت في علامات اقتباس. هناك عدة طرق لإزالة كائن من النظام:

  • حذف تماما من قاعدة البيانات ؛
  • تعليم الكائنات على أنها محذوفة ومختبئة من الواجهة. كعلامة ، يمكنك استخدام Boolean أو DateTime لتسجيل أكثر دقة.

التكرار # 1


في البداية ، تم استخدام الطريقة الأولى ، عندما قمنا ببساطة بتنفيذ object.delete() وتم حذف الكائن بكل التبعيات. ولكن بمرور الوقت ، كان علينا أن نتخلى عن هذا النهج ، لأن أحد الكائنات يمكن أن يكون له تبعيات مع ملايين الكائنات الأخرى ، وحذف الجداول المتتالية بشكل صارم. ونظرًا لأن الخدمة تؤدي آلاف الشيكات كل ثانية وتسجيلها ، فقد أدى حظر الجداول إلى تباطؤ خطير في الخدمة ، وهو أمر غير مقبول بالنسبة لنا.

التكرار # 2


لتجنب الأقفال الطويلة ، قررنا حذف البيانات على دفعات. هذا سيسمح بتسجيل بيانات المراقبة الفعلية في الفواصل الزمنية بين عمليات حذف الكائنات. يمكن الحصول على قائمة بجميع الكائنات التي سيتم حذفها في تتالي بالطريقة المستخدمة في لوحة المسؤول عند حذف كائن (عند تأكيد الحذف):

 from django.contrib.admin.util import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([obj]) objects_to_delete = collector.nested() # Recursive delete objects 

تحسن الوضع: تم توزيع الحمل بمرور الوقت ، وبدأ تسجيل البيانات الجديدة بشكل أسرع. لكننا واجهنا على الفور في المأزق التالي. الحقيقة هي أن قائمة الكائنات المراد حذفها يتم تشكيلها في بداية الحذف ، وإذا تمت إضافة كائنات تابعة جديدة في عملية الحذف "الجزئي" ، فلن يمكن حذف العنصر الأصل.

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

التكرار # 3


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

ثم قررنا استخدام الديكور لتجاوز مدير الاستعلام. علاوة على ذلك ، من الأفضل رؤية الشفرة بدلاً من كتابة مائة كلمة.

 def exclude_objects_for_deleted_hosts(*fields): """ Decorator that adds .exclude({field__}is_deleted=True) for model_class.objects.get_queryset :param fields: fields for exclude condition """ def wrapper(model_class): def apply_filters(qs): for field in filter_fields: qs = qs.exclude(**{ '{}is_deleted'.format('{}__'.format(field) if field else ''): True, }) return qs model_class.all_objects = copy.deepcopy(model_class.objects) filter_fields = set(fields) get_queryset = model_class.objects.get_queryset model_class.objects.get_queryset = lambda: apply_filters(get_queryset()) # save info about model decorator setattr(model_class, DECORATOR_DEL_HOST_ATTRIBUTE, filter_fields) return model_class return wrapper 

exclude_objects_for_deleted_hosts(fields) المحددة في نموذج الحقول تلقائيًا عامل تصفية exclude لكل طلب ، مما يؤدي إلى إزالة الإدخالات التي يجب عدم عرضها في الواجهة.

يكفي الآن لجميع الموديلات التي ستتأثر بطريقة ما بالحذف إضافة ديكور:

 @exclude_objects_for_deleted_hosts('host') class Alias(models.Model): host = models.ForeignKey(to=Host, verbose_name='Host', related_name='alias') 

الآن ، من أجل إزالة كائن Host ، فقط قم بتغيير السمة is_deleted :

 host.is_deleted = True # after this save the host and all related objects will be inaccessible host.save() 

تستبعد جميع الاستعلامات تلقائيًا السجلات التي تشير إلى الكائنات البعيدة:

 # model decorator @exclude_objects_for_deleted_hosts('checker__monhost', 'alias__host') CheckerToAlias.objects.filter( alias__hostname__in=['cloud.spb.s', 'cloud.msk.s'] ).values('id') 

اتضح استعلام SQL التالي:

 SELECT monitoring_checkertoalias.id FROM monitoring_checkertoalias INNER JOIN monitoring_checker ON (`monitoring_checkertoalias`.`checker_id` = monitoring_checker.`id`) INNER JOIN Hosts ON (`monitoring_checker`.`monhost_id` = Hosts.`id`) INNER JOIN dcmap_alias ON (`monitoring_checkertoalias`.`alias_id` = dcmap_alias.`id`) INNER JOIN Hosts T5 ON (`dcmap_alias`.`host_id` = T5.`id`) WHERE ( NOT (`Hosts`.`is_deleted` = TRUE) -- ,   monitoring_checker AND NOT (T5.`is_deleted` = TRUE) -- ,   dcmap_alias AND dcmap_alias.name IN ('dir1.server.p', 'dir2.server.p') ); 

كما ترى ، فإن الصلات الإضافية للحقول المحددة في الديكور والتحقق من `is_deleted` = TRUE إضافة `is_deleted` = TRUE إلى الطلب.

قليلا عن الأرقام


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

على وجه التحديد ، في حالتنا ، لكل مستوى من الاعتماد يتم تغريم الطلب حوالي 30 ٪. هذه هي العقوبة القصوى التي نحصل عليها على أكبر طاولة بملايين السجلات ؛ وفي الجداول الأصغر ، يتم تخفيض العقوبة إلى بضعة بالمائة. لحسن الحظ ، لدينا الفهارس اللازمة التي تم تكوينها ، وبالنسبة لمعظم الاستعلامات الحرجة ، كانت الروابط اللازمة موجودة بالفعل ، لذلك لم نشعر بفارق كبير في الأداء.

معرفات فريدة


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

 host.hostname = '{}_{}'.format(host.hostname, uuid.uuid4()) host.is_deleted = True host.save() 

العملية


لكل نموذج جديد أو تبعية ، يحتاج الديكور إلى تحديث إذا لزم الأمر. لتبسيط البحث عن النماذج التابعة ، كتبنا اختبارًا "ذكيًا":

 def test_deleted_host_decorator_for_models(self): def recursive_host_finder(model, cache, path, filters): # cache for skipping looked models cache.add(model) # process all related models for field in (f for f in model._meta.fields if isinstance(f, ForeignKey)): if field.related_model == Host: filters.add(path + field.name) elif field.related_model not in cache: recursive_host_finder(field.related_model, cache.copy(), path + field.name + '__', filters) # check all models for current_model in apps.get_models(): model_filters = getattr(current_model, DECORATOR_DEL_HOST_ATTRIBUTE, set()) found_filters = set() if current_model == Host: found_filters.add('') else: recursive_host_finder(current_model, set(), '', found_filters) if found_filters or model_filters: try: self.assertSetEqual(model_filters, found_filters) except AssertionError as err: err.args = ( '{}\n !!! Fix decorator "exclude_objects_for_deleted_hosts" ' 'for model {}'.format(err.args[0], current_model), ) raise err 

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

خاتمة


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

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


All Articles