Normalization vs Denormalization

کلاسهای زیر را در نظر بگیرید. این مدل طراحی مشکلات زیادی به وجوده میاره. با آپدیت شدن کلاس اول مجبور هستیم کلاس دوم و سوم رو هم آپدیت کنیم که با فراموش این نکته توی قسمت های مختلف کد باگهای زیادی ایجاد میشه. و همین آپدیت های اضافی کدهای اضافی در نتیجه باگهای بیشتر و سرعت کمتر رو به ارمغان میاره

depenancy زیادی بین کلاسها میبینید. در صورت آپدیت یک Service باید دو کلاس دیگر هم آپدیت شود و این موضوعی نیست که اتوماتیک انجام شود و ایراداتی در نرم افزار ایجاد میکند:

برای حل این موضوع باید کد را Normalize کنیم. یعنی تکرار به حداقل برسد و بجای تکرار اطلاعات موارد تکراری با کلید خارجی تبدیل شود:

حالا با آپدیت هر سرویس دو کلاس دیگر به صورت اتوماتیک آپدیت هستند.

گاهی Denormalization به درد میخورد. وقتی میخایم از یک جدول گزارشگیری داشته باشیم و نیاز به Join نباشد تکرار اطلاعات باعث افزایش بهره وری query می شود و …

cache stampede یا dogpiling problem

این مساله در علوم کامپیوتر به حالتی گفته میشود که یک cache که بسیار پراستفاده است منقضی میشود و همزمان تعداد بسیار زیادی از درخواست برای آن ارسال شده و در نتیجه چون منقضیست تمامی این درخواستها به دیتابیس ارسال میشود و موجب ازدحام دیتابیس میشود.

تنها در سیستمهایی این مشکل ایجاد میشود که سرویس مربوطه تنها با استفاده از cache دارای performance خوبی شده و قابل استفاده است و در این حالت دیگر سیستم قابل استفاده نخواهد بود.

Thundering Herd Problem یا مشکل گله ی رعدآسا

در علوم کامپیوتر مساله ای مطرح است به نام “گله ی رعد آسا” یا Thundering Herd Problem.

وقتی که تعداد زیادی process منتظر یک رخداد یا event هستند، ولی وقتی که این event رخ داد تنها یکی از این process ها باید انجام گیرد. در این حالت بعد از رخداد یک process انجام میگردد و باقی آنها معطل میشوند و یا مکررا دوباره تلاش میکنند و در نتیجه سیستم هنگ میکند. این در حالیست که تنها یکی از آنها باید اجرا میشدند و باقی باید کنسل میشدند.

در UNIX این مشکل به این شکل حل شده است: همه پاسخها در یک file descriptor نوشته می شوند بنابراین پس از پاسخ تنها یکی از process ها یا thread ها اجرا میشوند (بجای اینکه همگی با هم اجرا و سیستم هنگ کند)

مثال دیگر:

فرض کنید یک API داریم و 500 پروسه این API را صدا میزنند. ولی این سرویس تنها میتواند 2 تای آنها را پاسخ دهد. و 488 مورد دیگر Fail می شوند. ولی دوباره تلاش میکنند و این بار این سرویس با 996 درخواست روبروست و مجددا تنها میتواند 2 تای آنها را پاسخ دهد و این سیکل تا زمانی ادامه پیدا میکند که سرویس مختل شود. این هم مثال دیگری از گله ی رعد آساست.

برای حل این مشکل راه حل این است که از فراخوانی اگر Fail شد پس از 2 به توان N ثانیه دوباره تلاش کند. بنابراین دفعه دوم بعد از 4 واحد زمانی سپس 8 ، 16 و… واحد زمانی دوباره تلاش میکند این فاصله زمانی که تابع نمایی exponential هست موجب می شود که سرویس فرصت بازسازی خود را داشته باشد. در ادامه این راه حل میتوان یک عدد تصادفی به عنوان jitter به این بازه زمانی اضافه کرد تا همه فراخوانی ها دوباره همزمان نشوند. یعنی فرمول فراخوانی مجدد میشود (T + 2**N + JITTER)

پر کردن مودال با استفاده از اطلاعات فرم

نیاز بود که توی یک جدول اگر توی سلول یکی از ستونها کلیک کردیم یک مودال باز بشود. اما باید اطلاعاتی مربوط به آن ردیف به مودال پاس بدیم. متوجه شدم که این اطلاعات مربوط به ردیف مذکور را میتوان به عنوان یک پایامتر به سلول مربوطه اضافه کرد مثل:

توی خود مودال یک سری فیلد خالی تعریف میکنیم که بعدا با جاواساکریپ اطلاعات پارامترها رو به اون اضافه کنیم:

الان توی اسکریپت زیر از هر ردیف سلول مربوطه رو بر اساس اسم کلاس میگیریم. بعد از اون سلول پارامترم مربوطه که دیتا بهش وصل شده رو بر میداریم بعد همون مقدارها رو به مودال میدیم:

annotate و عدم اعمال Index

این 2 کد را در نظر بگیرید:

و

با تبدیل اولی به دومی زمان این query از 450 میلی ثانیه به 45 میلی ثانیه کاهش پیدا کرد. چرا؟

دلیل این بود که فیلدهای این مدل همگی Index بودند اما این ایندکسها اعمال نمی شدند چونکه annotate باعث میشود که کل جدول اسکن شود و عملا ایندکسها بی فایده باشند بنابراین حداقل st=0 را به قبل از annotate انتقال دادیم و این باعث شد برای annotate که اتفاقا از WHERE استفاده میکند از ایندکس استفاده شود.

مقایسه دو تکه کد

تفاوت دو کد زیر چیست و کدام بهتر است؟

و

در کد اول اگر thumbnail توسط کاربر انتخاب نشود یک فایل از مسیر mag آپلود میشود. و هنگام پاک شدن آبجکت مربوطه، اگر این آبجکت دارای این فایل پیشفرض بود پاک نشود که البته در این کد مربوطه خود به خود پاک می شود. پس یک باگ وجود دارد. گذشته از این باگ اگر 1000 بار کاربر بدون thumbnail فایل آپلود کند 1000 فایل تکراری ذخیره میشود.

در ضمن اگر فایل بهر دلیلی پاک شود یا نام آن تغییر کند سیستم دچار مشکل میشود.

در کد دوم اما اگر کاربر فایلی انتخاب نکند هیچ فایل پیش فرضی آپلود نمیشود بنابراین فایل تکراری دیده نمی شود و فضا هدر نمی روند.

از طرفی یک فایل static پاک نشده و هرکجا یک سند thumbnail نداشته باشد با متد get_thumbnail_url این فایل نشان داده میشود و دیگر سیستم دچار باگهای ناخواسته نمی شود.

تغییر queryset توی فرم جنگو

توی یک فرم میخوایم فیلدی که لیستی را به کاربر نشان میدهد را تغییری بدیم که همه مواردی که توی اون فیلد توی دیتابیس نیست را نشان ندهیم.

صحت سنجی فرمت فایل توی فرم جنگو

چطور توی یک فرم اگر یک فایل آپلود شده از یک فرمت خاص نبود ارور برگردونیم

Concurrency vs Parallelism

Concurrency به وقتی گفته میشه چند تسک روی زمانهایی که با هم overlap دارن انجام میشن، نه اینکه لزوما در یک زمان انجام بشوند. مثل multitasking مثلا multi-thread

Parallelism یعنی وقتی واقعا کارها همزمان انجام میشن، مثلا 2 تا تسک روی دو تا CPU مجزا انجام می شوند Multi-Processing

تبدیل n+1 عدد query به 3 query

این یک مثال واقعی از بهبود یک پروژه است. query اولیه به شکل زیر بود:

با ابزار django debug toolbar تعداد query های این تکه کد را مرور کردم و جاهایی که به سرور query زده میشود به این شکل است:

  1. در خط Branch.objects.get(name__iexact=”bss”).office_set.all() یک query به دیتابیس زده میشود.

2. در خط for el in bss_branch: یک query به دیتابیس زده می شود.

3. در خط for svc in el.services.all(): یک query به دیتابیس زده میشود. یعنی اگر در bss_branch تعداد N المان وجود داشته باشد به ازای هر کدام یک query در این قسمت زده خواهد شد.

یعنی برای هر el که تعداد آن به اندازه bss_branch هست یک query جدید داریم

بنا براین در کد بالا یک query زده شده و branch ها استخراج شده (در قسمت 1) که ترجمه آن:

در قسمت دوم یک query زده شده و office های آن branch استخراج شده:

در قسمت سوم به ازای هر کدام از office ها یک query زده شده و service های آن استخراج شده به عنوان مثال یکیش رو آوردم:

پس در مجموع N+1 درخواست دیتابیس query زدن شد.

حالا اینو بهینه میکنیم به کد زیر:

در این نمونه کد query های زیر را داریم:

  1. در خط bss_branch = Branch.objects.get(name__iexact=”bss”).office_set.prefetch_related(‘services’) یک query به دیتابیس داریم.
  2. در خط for el in bss_branch: یک query زده شده و office های branch را استخراج کرده.
  3. دوباره در همان خط یک query زده شده و service های تمامی office ها یک جا استخراج شده در این قسمت دیگر N بار query زده نشده

query قسمت 1

query قسمت 2

query قسمت 3

همانطور که توی کد بالا میبینید توی خط آخر تمامی query ها ادغام شده اند

بنابراین N+1 دستور query به 3 query تبدیل شد