برنامه خود را مقیاس‌پذیر کنید: بهینه سازی عملکرد ORM
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 7 دقیقه

برنامه خود را مقیاس‌پذیر کنید: بهینه سازی عملکرد ORM

در این مقاله مجموعه‌ای از استراتژی‌های بهینه سازی ORM را با شما عزیزان به اشتراک خواهم گذاشت که تقریبا در هر پروژه بک اندی از آن‌ها استفاده می‌کنم.

مطمئنم که هر یک از ما از کند بودن یا حتی کرش کردن یک ماشین یا برنامه شکایت داشته‌ایم و سپس وقت خود را در فروم‌ها می‌گذرانیم و منتظر نتایج یک پرسش طولانی هستیم.

چگونه می‌توانیم این مشکل را برطرف کنیم؟

برای گرفتن پاسخ با ما همراه باشید.

پایگاه داده یک منبع مشترک است

چرا بسیاری از مشکلات فنی توسط پایگاه داده ایجاد می‌شود؟

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

اگر یک درخواست کند باشد، بعید است که بر سایر موارد تأثیر بگذارد. درست است؟

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

مراقب باشید اگر فکر می‌کنید که "اگر این قطعه کد بهینه نشده باشد، اشکالی ندارد". یک دسترسی کوچک به پایگاه داده می‌تواند دیتابیس شما را تحت فشار قرار دهد و باعث یک تجربه منفی برای کاربران شما شود.

مشکل درخواست‌های پایگاه داده N+1

مشکل N+1 چیست؟

این یک مشکل معمول است که با استفاده از ORM برای تعامل با پایگاه داده ایجاد می‌شود و یک مشکل در کد زنی SQL نیست.

وقتی از ORM مانند Eloquent استفاده می‌کنید، همیشه مشخص نیست که چه زمان‌هایی پرس و جو می‌شود. برای این مشکل خاص ما در مورد رابطه‌ها صحبت می‌کنیم.

هر ORM به شما امکان می‌دهد روابط بین موجودیت‌هایی را که یک API برای حرکت در ساختار پایگاه داده دارد، اعلام کنید.

"مقاله و نویسنده" مثال بسیار خوبی است.

/*
 * Each Article belongs to an Author
 */
$article = Article::find("1");
echo $article->author->name; 

/*
 * Each Author has many Articles
 */
foreach (Article::all() as $article)
{
    echo $article->title;
}

اما باید با دقت و با استفاده از روابط درون یک حلقه کد زنی کنیم.

به مثال زیر نگاهی بیندارید.

می‌خواهیم نام نویسنده را در کنار عنوان مقاله اضافه کنیم. با بهره‌گیری از ORM می‌توانیم رابطه یک به یک بین مقاله و نویسنده را جستجو کنیم تا نام آن را بدست آوریم.

واقعا ساده به نظر می‌رسد:

// Initial query to grab all articles
$articles = Article::all();

foreach ($articles as $article)
{
    // Get the author to print the name.
    echo $article->title . ' by ' . $article->author->name;
}

ما در دام افتاده‌ایم.

این حلقه یک پرسش اولیه برای گرفتن همه مقالات ایجاد می‌کند:

SELECT * FROM articles;

و N پرس و جو برای گرفتن نویسنده برای هر مقاله به منظور چاپ قسمت "نام". همچنین اگر نویسنده همیشه یکسان باشد.

SELECT * FROM author WHERE id = [articles.author_id]

N+1 بار پرس و جو می‌کند.

ممکن است چندان مسئله مهمی به نظر نرسد. پانزده یا بیست پرسش را می‌توان به عنوان مساله‌ای آنی تلقی کرد. مراقب باشید و قسمت اول این مقاله را مرور کنید:

  • بانک اطلاعاتی منبعی است که در همه فرایندها مشترک است.
  • ماشین پایگاه داده دارای منابع محدودی است. اگر از سرویس مدیریت شده استفاده می‌کنید، بارگذاری بیشتر پایگاه داده ممکن است هزینه‌های بیشتری داشته باشد.
  • اگر پایگاه داده شما در یک ماشین جداگانه واقع شده باشد، همه داده‌ها باید با تأخیر اضافی شبکه منتقل شوند.

از eager loading استفاده کنید

همانطور که در داکیومنت لاراول ذکر شده، به راحتی می‌توانیم درگیر مشکل پرسش N+1 بشویم، زیرا هنگام دسترسی به روابط Eloquent به عنوان خصوصیات (article-> author$)، داده‌های رابطه به صورت lazy loaded بارگذاری می‌شوند.

این بدان معناست تا زمانی که برای اولین بار به خصوصیتی دسترسی پیدا نکنید، داده‌های رابطه بارگیری نمی‌شوند.

با این وجود می‌توانیم تمام داده‌های رابطه‌ها را با یک روش ساده بارگیری کنیم. بنابراین وقتی به رابطه Eloquent به عنوان خصوصیت دسترسی پیدا می‌کنید، یک پرسش جدید اجرا نمی‌شود، زیرا داده‌ها قبلا توسط ORM بارگیری شده‌اند.

این تاکتیک "eager loading" نامیده می‌شود که توسط ORM پشتیبانی می‌شود.

// Eager load authors using "with".
$articles = Article::with('author')->get();

foreach ($articles as $article)
{
    // Author will not run a query on each iteration.
    echo $article->author->name;
}

Eloquent متد with() را برای رابطه‌های eager loading فراهم می‌کند.

در این حالت فقط دو پرس و جو انجام می‌شود.

اولین مورد برای بارگیری همه مقالات مورد نیاز است:

SELECT * FROM articles;

روش دوم با متد with() است و همه نویسندگان را بازیابی می‌کند:

SELECT * FROM authors WHERE id IN (1, 2, 3, 4, ...);

Eloquent داده‌ها را به صورت داخلی نقشه برداری می‌کند تا طبق معمول استفاده شود:

$article->author->name;

عبارات انتخاب شده را بهینه کنید

مدت‌ها فکر می‌کردم که صریحا تعریف کردن تعداد فیلدها در یک پرسش انتخابی، بهبود چشم‌گیری در عملکرد ایجاد نمی‌کند. بنابراین از سادگی فقط در به دست آوردن تمام فیلدهای کوئری خود استفاده کردم.

علاوه بر این، هارد کودینگ لیست فیلدها برای یک انتخاب خاص، دستور کد را حفظ نمی‌کند.

بزرگترین اشتباه در پشت این استدلال این است که می‌تواند از منظر پایگاه داده درست باشد.

اکنون ما از ORM استفاده می‌کنیم، بنابراین داده‌های انتخاب شده از پایگاه داده در قسمت پی اچ پی در حافظه بارگیری می‌شود تا توسط ORM مدیریت شود. هرچه فیلدهای بیشتری را به دست آوریم، فرآیند حافظه بیشتری نیز به خود اختصاص می‌دهد.

Laravel Eloquent روش "select" را برای محدود کردن درخواست محدود به ستون‌های مورد نیاز ما ارائه می‌دهد.

$articles = Article::query()
    ->select('id', 'title', 'content') // The fields you need
    ->latest()
    ->get();

به استثنای فیلدهای پی اچ پی نیازی به پردازش این داده‌ها نیست، بنابراین می‌توانید مقدار مصرف حافظه را به میزان قابل توجهی کاهش دهید.

عدم انتخاب همه چیز می‌تواند مرتب سازی، گروه بندی و عملکرد جوین شدن را بهبود بخشد زیرا پایگاه داده می‌تواند حافظه را از این طریق ذخیره کند.

از view ‌های MySQL استفاده کنید

Viewها کوئری‌های select هستند که در بالای جداول دیگر ساخته شده و در پایگاه داده ذخیره می‌شوند.

هنگامی که ما SELECT را در مقابل یک یا چند جدول انجام می‌دهیم، پایگاه داده باید دستور SQL شما را کامپایل کند، همچنین تأیید کند که شامل خطا نیست و انتخاب داده را اجرا کند.

ویوها عبارت‌های از پیش تدوین شده SELECT هستند، بنابراین MySQL می‌تواند پرسش زیرخط را بلافاصله اجرا کند.

علاوه بر این MySQL در مورد فیلتر کردن داده‌ها به طور کلی هوشمندتر از پی اچ پی است. در مقایسه با استفاده از مجموعه‌ها یا توابع آرایه در پی اچ پی، یک افزایش عملکرد بزرگ وجود دارد.

اگر می‌خواهید درباره قابلیت MySQL برای برنامه‌های پایگاه داده بیشتر بدانید، نگاهی به این لینک بیندازید.

یک مدل Eloquent را به یک view پیوست کنید

View ها "جداول مجازی" هستند. از نظر ORM به عنوان جداول عادی ظاهر می‌شوند.

به همین دلیل می‌توانیم یک مدلEloquent برای پرس و جو از داده‌ها در داخل View ایجاد کنید.

class ArticleStats extends Model
{
    /**
     * The name of the view is the table name.
     */
    protected $table = "article_stats_view";

    /**
     * If the resultset of the View include the "author_id"
     * we can use it to retrieve the author as normal relation.
     */
    public function author()
    {
        return $this->belongsTo(Author::class);
    }
}

رابطه‌ها به طور معمول کار می‌کند، همچنین کستینگ، صفحه بندی و ... دیگر هیچ تاثیری بر عملکرد ندارند.

جمع بندی

بسیار خب! امیدوارم که یک یا چند مورد از این نکات بتواند به شما کمک کند تا یک محصول نرم‌افزاری جامع‌تر و قابل مقیاس‌تر بسازید.

من از ORM Eloquent برای نوشتن نمونه‌های کد استفاده کرده‌ام، اما شما نمی‌دانید که این استراتژی‌ها برای همه ORM های اصلی خارج از کشور به همان روش کار می‌کنند.

همانطور که همیشه می‌گویم، ابزارها به ما کمک می‌کنند تا یک استراتژی کارآمد را پیاده‌سازی کنیم.

از اینکه ما را همراهی می‌کنید نهایت قدردانی را داریم. امیدواریم که این مقاله برایتان مفید واقع شده باشد. در صورت تمایل نظرات خود را در بخش زیر با ما در میان بگذارید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
5 از 1 رای

/@erfanheshmati
عرفان حشمتی
Full-Stack Web Developer

کارشناس معماری سیستم های کامپیوتری، طراح و توسعه دهنده وب سایت، تولیدکننده محتوا

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید