امید آرام - توسعه دهنده نرم افزار
امید آرام - توسعه دهنده نرم افزار
خواندن ۵ دقیقه·۲ روز پیش

افزایش سرعت برنامه با استفاده از Cache در کدنویسی

آیا از کُندی اجرای صفحه ی خود رنج می برید؟ ما به شما پکیج افزایش سرعت Cache رو پیشنهاد میکنیم ;)

caching in software development
caching in software development

خیلی وقتا توی اجرای برنامه ما مجبوریم یه سری کارهای تکراری رو انجام بدیم. یا ممکنه لازم باشه کاربر یه کاری رو هی انجام بده. مثلاً مشکلی که توی سیستم خودم داشتم این بود که یه درخت از لیست حساب های شرکت رو نشون میدادم که آبشاری میومد پایین و کاربرش دونه دونه میزد تا برسه به اون نودی که میخواست و بعدش هم احتمالاً میرفت یه نود بالاتر و میومد توی نود بعدی و همینجوری این درخت من هی میرفت از دیتابیس اون نودی که کاربر میخواست رو میاورد! اولش که این فرم رو نوشته بودم سرعتش قابل قبول بود ولی به مرور چیزای بیشتری رو بهش اضافه کردم و از دید خودم کُند شده بود. مثلاً یک و نیم ثانیه طول میکشید تا هر حساب رو لود کنه! (شاید بگی یک ثانیه چیزی نیست ولی وقتی یوزر میخواد دائم ازش استفاده بکنه یک ثانیه هم براش زیاده) اینم در نظر بگیرین که کل حساب های شرکت 7-8 هزار تاست که عدد بزرگی نیست ولی به خاطرش هی میرفتیم دیتابیس و برمیگشتیم.

خلاصه، اومدم براش یه کَش درست کردم که توی اولین فراخوانی دیتا میره همه حساب ها رو میاره و توی RAM نگهش میداره و توی مراجعه های بعدی از همون کش استفاده میکنه.

public static class Cache<T> { private class CacheItem { public DateTime CreateDateTime { get; set; } public int Key { get; set; } public List<T> Values { get; set; } } private static List<CacheItem> CacheItems = new List<CacheItem>(); public static bool TryGet(int key, out List<T> values) { values = default; CacheItems.RemoveAll(x => x.CreateDateTime < DateTime.Now.AddDays(-1)); var cacheItem = CacheItems.FirstOrDefault(x => x.Key == key); if (cacheItem != null) { values = cacheItem.Values; return true; } return false; } public static void Set(int key, List<T> values) { if (values == null) return; var cacheItem = CacheItems.FirstOrDefault(x => x.Key == key); if (cacheItem == null) { CacheItems.Add(new CacheItem { CreateDateTime = DateTime.Now, Key = key, Values = values, }); } else { cacheItem.Values = values; cacheItem.CreateDateTime = DateTime.Now; }; } public static void Delete(int key) { CacheItems.RemoveAll(x => x.Key == key); } public static bool TryGetObject(int key, out T obj) { obj = default; if (TryGet(key, out List<T> values)) { obj = values.FirstOrDefault(); return true; } return false; } public static void SetObject(int key, T obj) { if (obj == null) return; Set(key, new List<T> { obj }); } }

اول اینو بگم که توی پروژه های تحت وب (مثل این) با هر فراخوانی API همه اطلاعات قبلی از بین میره ولی ما اینجا اومدیم این کلاس رو static تعریف کردیم که باعث میشه بره بچسبه به اصل برنامه و دیگه تا وقتی IIS رو restart نکنیم از بین نمیره.

کلاسی که نوشتیم هم خیلی ساده ست؛ اول اینکه Generic تعریف شده تا بتونه برای هر تایپی استفاده بشه. بعدش هم یه «کلید - Key» داره که دیتا باهم قاطی نشه و یه «لیست - Values» که دقیقاً همون چیزیه که میخوایم کش کنیم و یه «زمان ایجاد - CreateDateTime» هم داره که بیشتر از یک روز چیزی رو توش نگه نداریم.

استراتژی های مختلفی برای cache داریم که ما اینجا از متداول ترینش یعنی Cache-Aside استفاده کردیم که اینجوریه که توی هر فراخوانی دیتا، برنامه اول cache رو چک میکنه اگه دیتا داشت (Cache Hit) که همون رو برمیگردونه و اگه نداشت (Cache Miss)، برنامه میره از دیتابیس میخونه و به کاربر نشون میده و توی کش هم ذخیره ش میکنه. اینجوری مثلاً:

if (!Cache<AccountDTO>.TryGet(periodId, out result)) { result = FetchAccountsFromDB(periodId); Cache<AccountDTO>.Set(periodId, result); }

به همین سادگی! ولی به قدری توی سرعت برنامه تأثیر مثبت داشت که کاربرش کلی حال کرده بود.

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


چند تا استراتژی Cache که میشناسم هم خلاصه وار اینجا بگم:

  1. استراتژی Cache-Aside:
    که همین حالت بالاست؛ یعنی کش در کنار دیتابیس هست؛ برنامه اول کش رو چک میکنه اگه نبود میره سراغ دیتابیس و بعدش کش رو آپدیت میکنه.
  2. استراتژی Read-Through:
    تو این حالت کش در مسیر دیتابیس قرار میگیره و اگه دیتا نداشته باشه میره از دیتابیس میخونه و اول خودش آپدیت میشه و بعد دیتا رو برمیگردونه.
  3. استراتژی Write-Through:
    این استراتژی میگه هر چیزی قراره بره توی دیتابیس ذخیره بشه اول باید توی کش نوشته بشه. (که به نظرم خیلی کارا نیست چون هم حجم بالایی میگیره هم خیلی به درد نمیخوره!)
  4. استراتژی Write-Around:
    این روش انگار اینجوریه که همه ی دیتا رو توی کش نمینویسه. فقط اونایی که درخواست Read براشون میاد رو مینویسه! (خودم درست نفهمیدم چجوریه!)
  5. استراتژی Write-Back / Write-Behind:
    این روش باحالیه که Cache به برنامه میگه آقا دیتابیس منم. هر چی رو بخوایم Insert کنیم در واقع توی Cache نوشته میشه و خیلی سریع از نظر کاربر انجام میشه؛ بعد خود این Cache میاد مثلاً با یکم تأخیر و جوری که کاربر اصلاً بهش کاری نداره دیتا رو میریزه توی دیتابیس.

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

cachecachingdeveloperweb developmentکدنویسی
شاید از این پست‌ها خوشتان بیاید