آیا از کُندی اجرای صفحه ی خود رنج می برید؟ ما به شما پکیج افزایش سرعت Cache رو پیشنهاد میکنیم ;)
خیلی وقتا توی اجرای برنامه ما مجبوریم یه سری کارهای تکراری رو انجام بدیم. یا ممکنه لازم باشه کاربر یه کاری رو هی انجام بده. مثلاً مشکلی که توی سیستم خودم داشتم این بود که یه درخت از لیست حساب های شرکت رو نشون میدادم که آبشاری میومد پایین و کاربرش دونه دونه میزد تا برسه به اون نودی که میخواست و بعدش هم احتمالاً میرفت یه نود بالاتر و میومد توی نود بعدی و همینجوری این درخت من هی میرفت از دیتابیس اون نودی که کاربر میخواست رو میاورد! اولش که این فرم رو نوشته بودم سرعتش قابل قبول بود ولی به مرور چیزای بیشتری رو بهش اضافه کردم و از دید خودم کُند شده بود. مثلاً یک و نیم ثانیه طول میکشید تا هر حساب رو لود کنه! (شاید بگی یک ثانیه چیزی نیست ولی وقتی یوزر میخواد دائم ازش استفاده بکنه یک ثانیه هم براش زیاده) اینم در نظر بگیرین که کل حساب های شرکت 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 که میشناسم هم خلاصه وار اینجا بگم:
برای Cache کلی کتابخونه و روش استاندارد هم هست که توی برنامه میتونین ازش استفاده کنین ولی من ترجیح دادم خودم بنویسم. در کل خیلی چیز خوبیه ولی نباید زیاده روی کرد توش. چون همونقدر که سرعت رو بالا میبره، مصرف حافظه رو هم زیاد میکنه. امیدوارم که درست و به جا ازش استفاده کنین.