محمد حسینی راد
محمد حسینی راد
خواندن ۵ دقیقه·۴ سال پیش

چطور یک سایت داینامیک رو کش (Cache) کنیم؟

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

سایت ایی مثل Twitter که هر بار Refresh میکنید اش یک جواب جدید به ما میده چطور کش رو انجام میده؟ اصلا چطور میشه کش داینامیک داشت؟ Best Practice های کش برای این کار چطور هستن؟



اول ببینیم چه نیازی داریم

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

نکته بعدی اینه که میخوایم سایتمون سریع باشه. نمیخوایم کش چند ساعتی داشته باشیم! طوری که اگه تعداد فالوئینگ های من منطقی بود هر بار رفرش کنم سایت رو احتمالا مطلب جدیدی برام لود بشه.

ساده ترین روش کش رو در نظر بگیریم. من بیام تمام پیام خروجی API رو برای هر کاربر کش کنم! یعنی من وقتی خواستم تایم لاین رو بگیرم یک کوئری بزنه توی MySQL و برام تایم لاینم رو بسازه. بعد که رفرش کردم دوباره اون رو نشون بده. قاعدتا لود سایت کمتر میشه ولی این مخالف حرفیه که توی توضیحات هدف سایت گفته بودیم.

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

کش ها رو بشکونیم!

بیاید ببینیم تایم لاین من از چی تشکیل شده؟ یک لیست از پست ها. درسته؟ خب میتونیم این رو بشکونیم! جای اینکه کل تایم لاین رو کش کنیم پست ها رو کش میکنیم. چه اتفاقی میوفته؟ کوئری ایی که به دیتابیس میزنیم فقط id پست های جدید رو میگیره و از طریق اون آی دی میتونیم کل پست رو از کش بخونیم. لود دیتابیس خیلی کمتر میشه.

اما شاید خود پست رو بقیه بتونن لایک کنن. یا بتونن کامنت بذارن. اگه همشون با هم کش کنیم هر بار کسی کامنت میفرسته باید کلی صبر کنه تا کش دوباره ساخته شه تا کامنتش نمایش داده بشه. پس بهتره پست رو هم بشکونیم. یک کش میذاریم برای توضیحات مثل Text و تصویر و سازنده پست و ... که معمولا تغییر نمیکنن. اگر هم تغییر کنن مثلا Text رو آپدیت میکنیم.

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

مرتب تر ترتیب کش ها رو بگم. ( جلوی هر مورد نمونه کش ایی که میخوایم توی ردیس ذخیره کنیم نوشته شده )

  • یک لیست که آی دی تک تک پست های تایم لاینمون رو نگه میداره. timeline:$userid
  • محتویات static پست ها ( مثل description و image و author و .. ) پست ها براساس آی دیشون. postinfo:$postid
  • لیست آی دی کامنت های یک پست. postcomments:$postid
  • لیست کسایی که یک پست رو لایک کردن براساس پست آی دی. postlikes:$postid
  • محتویات یک پست ( میتونید این رو هم بشکونید ولی طبق نیاز آموزش انجام نمیدم ) comment:$commentid
  • لیست فالوئر های یک شخص followers:$userid
  • لیست فالوئینگ های یک شخص following:$userid
  • و در نهایت اطلاعات یک کاربر ( که اینم میتونید بشکونید ) user:$userid

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

دیتاتایپ های هرکدوم چی هستن؟

ما میخوایم از Redis استفاده کنیم. چون خیلی خفنه. توی همین مثال خودش رو نشون میده که چرا خفنه. ما نمیایم لیست آی دی پست های تایم لاین یک نفر رو به صورت json ذخیره کنیم! میایم از data type های ردیس استفاده میکنیم. سریع تره. تست هاش انجام شده و خودش رو ثابت کرده.

برای لیست ها همون طوری که از اسم اش میاد از دیتاتایپ لیست استفاده میکنیم. یعنی هر شخص به عنوان لیست یک تایم لاین توی ردیس داره. حالا پست جدید میاد. چیکار میکنیم؟ درست حدس زدید. به آخر لیست آی دی پست جدید اضافه میکنیم. به همین راحتی!

حالا دیتاهایی مثل postinfo رو چطور ذخیره کنیم؟ دو تا راه حل داریم. استفاده از hash ردیس یا اینکه تبدیل به بایت کنیم اطلاعات postinfo رو و توی ردیس ذخیره کنیم.

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

برای postlikes باید یک فکری هم بکنیم که یه نفر دوبار پست رو لایک نکنه. یعنی اگه پست رو لایک کرده بهش نشون بدیم. برای این کار از لیست استفاده نمیکنیم. چرا؟ چون هر بار باید چک کنیم آی دیمون توی لیست هست یا نه و این یعنی o(n) که زیاده. ما میتونیم از Sorted Set استفاده میکنیم که cost اضافه کردن و دریافت o(log n) هست. یا اگه فضای اضافه داشته باشیم کنارش یک Hash هم میذاریم که بررسی آیا لایک کرده یا نه o)1( باشه.

یک سناریو رو در نظر بگیریم.

میخوایم یک پست جدید بسازیم و انتظار داریم توی تایم لاین تک تک کسایی که ما رو فالو کردن اضافه شه. چه اتفاقی میوفته؟

  • پست جدید ما در دیتابیس ثبت میشه
  • اطلاعات استاتیک پست جدید ما توی ردیس ثبت میشه. ( postinfo )
  • لیست کسایی که مارو فالو کردن از followers میگیریم.
  • آی دی پست ایی که ساختیم به آخر تایم لاین اون افراد اضافه میکنیم.

حالا خودتون میتونید حدس بزنید وقتی یک کامنت جدید اضافه میشه به پست چه اتفاقی میوفته؟



این چیزی که ما درموردش صحبت کردیم تقریبا کاریه که توییتر انجام میده. البته خیلی باید Tune بشه و چالش های دیگه ایی بخصوص توی Scale کردن بوجود میاد. مثلا وقتی لیدی گاگا با 5 میلیون فالوئر پست بزنه چطور آی دی پست رو به آخر تایم لاین 5 میلیون نفر اضافه کنیم طوری که سرورمون نخوابه =))

ردیسکشgoسایت داینامیکredis
یک مهندس نرم‌افزار در دیوار.
شاید از این پست‌ها خوشتان بیاید