لینک کوتاه مطلب : https://hsgar.com/?p=2907

چه زمانی و چگونه کش را باطل کنیم

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

مثال

فرض کنید یک سیستم دارای اجزای زیر است (تنظیم برنامه وب بسیار معمولی):

  • بسیاری از مشتریان بدون تابعیت (سرورهای وب)
  • یک سرور کش (مثلاً Memcache)
  • یک پایگاه داده (مثلاً Postgres)

در پایگاه داده یک جدول (مجموعه ای از روابط) به نام ذخیره می کنیم friends. و طرح آن چیزی شبیه به این است:

+---------------------+------------------+------+-----+---------+-------+
| Field               | Type             | Null | Key | Default | Extra |
+---------------------+------------------+------+-----+---------+-------+
| user_id             | bigint unsigned  | NO   | PRI | 0       |       |
| friend_id           | bigint unsigned  | NO   | PRI | 0       |       |
+---------------------+------------------+------+-----+---------+-------+

اکنون در کش می خواهیم مجموعه ای از روابط را ذخیره کنیم friends-of-friends زیرا در برنامه ما کدهایی مانند داریم getFriendsofFriends($bob). بسیار معمول/طبیعی است که فهرستی از دوستان دوستان را که با شناسه کاربر در سرویس‌هایی مانند Redis یا Memcache کلید شده‌اند، در حافظه پنهان ذخیره کنید. بگویید کلید در Memcache شناسه کاربر است (مثلاً شناسه کاربر باب)، و مقدار آن لیستی از شناسه های کاربری است که دوستان دوستان باب هستند. خودشه.

تعریف

  • من “کش” را در اینجا به عنوان تعریف می کنم هر دیدگاه مادی از آنچه در منبع حقیقت در این پست وجود دارد، و این تعریف را در این پست فرض کنید، مگر اینکه طور دیگری مشخص شده باشد.
  • منظور من از “بی اعتبار کردن”، به‌روزرسانی/ حذف ورودی‌های حافظه پنهان مربوطه زمانی است که منبع حقیقت جهش می‌یابد، بنابراین هیچ داده قدیمی به طور نامحدود در حافظه پنهان ذخیره نمی‌شود.

الزامات

هنگامی که از کش استفاده می کنید، طبق تعریف، با یک سیستم توزیع شده سر و کار دارید – حافظه پنهان و منبع حقیقت وجود دارد. از آنجایی که شما با یک سیستم توزیع شده سر و کار دارید، باید در مورد توزیع وضعیت/دانش مورد انتظار شفاف باشیم. بسیاری از سیستم‌ها به شیوه‌ای موقت به حافظه نهان نزدیک می‌شوند، بدون اینکه متوجه شوند با یک سیستم توزیع‌شده سروکار دارند، که منجر به این ایده می‌شود که تشخیص زمان باطل کردن حافظه پنهان به‌طور منحصربه‌فردی سخت است. اگر کسی یک پرس و جو را علیه Postgres اجرا کند و داده ها را به صورت محلی در کش مشتری خود ذخیره کند، بدون اینکه پایگاه داده بداند، بدیهی است که باطل کردن حافظه پنهان مشتری محلی همیشه نمی تواند به درستی انجام شود. به عنوان مثال، مشتری دیگری می تواند بیاید و Postgres را بدون اینکه مشتری اول بداند، جهش دهد. Postgres نمی داند اولین کلاینت هیچ داده ای را با پرس و جوی قبلی خود ذخیره کرده است و غیره.

برای برخورد صحیح با یک سیستم توزیع شده، حافظه پنهان و پایگاه داده باید از یکدیگر آگاه باشند. به این معنا که پایگاه داده باید بداند که برخی از کوئری ها در حال ذخیره شدن هستند. و حافظه نهان باید بداند که داده ها از کجا می آیند (و به ویژه نحوه ترتیب جهش ها – اگر یک کلمه کلیدی در سیستم های توزیع شده وجود داشته باشد، این خواهد بود سفارش).

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

جهش / نوشتن تراکنش ها

مثلا یک نفر می آید و INSERTدوستی جدید بین باب و آلیس:

BEGIN;

INSERT INTO friends (user_id, friend_id)
VALUES
  ($bob, $alice), ($alice, $bob);

COMMIT;

هدف ما در اینجا این است که بفهمیم چه زمانی و چگونه ورودی‌های حافظه پنهان (دوستان دوستان) را باطل کنیم. بطرزی قابل اعتماد. Postgres این ویژگی را پشتیبانی می کند برگرداندن داده ها از ردیف های اصلاح شده. در این مورد مفید است، زیرا می‌خواهیم شناسه‌های کاربری را بدانیم که لیست دوستان دوستان ممکن است تغییر کرده باشد. اکنون تراکنش نوشتن چیزی شبیه به این خواهد بود:

BEGIN;

INSERT INTO friends (user_id, friend_id)
VALUES
  ($bob, $alice), ($alice, $bob) RETURNING user_id;

COMMIT;

در این مثال ساده، تشخیص user_id هایی که جهش یافته اند بسیار آسان است (شما می توانید با داشتن یک SELECT پس از INSERT). اما در پرس و جوهای پیچیده تر، RETURNING ویژگی از Postgres بسیار مفید می شود.

چه می‌شود اگر مجموعه‌ای از روابط را در حافظه پنهان ذخیره کنیم که به نتایج بهم پیوسته بین دو جدول بستگی دارد، و یکی از آنها friends? سپس پس از بازگشت به لیست user_ids، می توانید همان join/filter (یا سایر عملیات جبر رابطه ای) را در لیست اجرا کنید user_ids، که سپس به شما می گوید کدام مجموعه از کلیدهای کش باید به روز شوند. توضیحات بیشتر در انتهای پست

اکنون وقتی کلاینت db این تراکنش را اجرا می کند، لیستی از آنها را دریافت می کند user_ids، که دوستی آنها تغییر کرده است. اکنون نه تنها لیست دوستان آلیس و باب باید در حافظه پنهان به روز شود، بلکه دوستان آلیس و دوستان باب نیز باید به روز شوند. بنابراین هنگامی که ما داریم user_ids، باید a را اجرا کنیم select پرس و جو برای دریافت همه کاربران تحت تأثیر این جهش:

SELECT friend_id
FROM friends
WHERE user_id in $user_ids;

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

  • مشتری یک معامله را شروع می کند
  • مشتری هر گونه جهش را در صورت نیاز اجرا می کند
  • مشتری جمع آوری می کند user_idکسانی که ورودی های حافظه پنهان آنها باید باطل شود
  • مشتری کش را باطل می کند
  • مشتری معامله را انجام می دهد

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

آنچه خوب است این است که مراحل “جمع آوری user_ids و باطل کردن حافظه نهان” را می توان در سرویس گیرنده DB شما کپسوله کرد، و بنابراین لازم نیست نگران فراموش کردن نامعتبر حافظه پنهان باشید. متوجه می شوید که سرویس گیرنده DB در این مورد از طرح کش آگاه است (بنابراین می داند که user_ids و غیره را جمع آوری کند). اینجاست که نیاز قبلی شروع می شود،

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

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

شاید متوجه شده باشید که کاری که ما در اینجا انجام می دهیم اساساً از طریق DB و حافظه پنهان “تراکنش” است. این یک تراکنش ACID نیست، اما اساساً یک تراکنش همیشه متعهد در سمت کش است. دلیل اینکه می‌تواند راحت‌تر از تراکنش db بین سیستمی/متقاطع باشد، این است که آنچه در حافظه پنهان است صرفاً یک نمای واقعی از آنچه در پایگاه داده است است. بنابراین نمی‌تواند تداخلی داشته باشد به این معنا که DB یک نوشتن را انجام می‌دهد، و کش می‌گوید تضاد وجود دارد و من نمی‌توانم کش را باطل کنم (که می‌تواند در مورد تراکنش‌های x-system/x-shard db رخ دهد).

تعمیم

اکنون اجازه دهید تنظیمات قبلی را تعمیم دهیم – حافظه نهان واحد، پایگاه داده واحد.

اگر طرح واره کش پیچیده باشد چه؟

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

بیایید فقط روی ردیف های تغییر یافته تمرکز کنیم، همانطور که فقط تغییر کرده است friends روابط و/یا تغییر کرده است home روابط می توانند روی حافظه پنهان ما تأثیر بگذارند. اگر friends روابط تغییر می کند، همان تابع “تبدیل” که قبلا در پست صحبت کردیم بدون تغییر اعمال می شود. ما فقط باید منطق را برای جمع آوری کلیدهای باطل اضافه کنیم home روابط تغییر می کند می تواند چیزی شبیه به این باشد:

BEGIN;

UPDATE home SET country = 'US' WHERE user_id = $bob RETURNING user_id;

COMMIT;

در حال حاضر در home به روز رسانی رابطه، ما یک لیست از user_idکشورهایی که کشور اصلی آنها تغییر کرده است، که می تواند بر روی حافظه پنهان “دوستان دوستانی که در ایالات متحده زندگی می کنند” تأثیر بگذارد. سپس کوئری مجموعه می تواند چیزی شبیه به:

SELECT user_id
FROM friends
WHERE friend_id in $user_ids;

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

اگر تعداد زیادی کپی کش و طرحواره های کش و داده های زیادی داشته باشم چه می شود؟

راه حل فوق الذکر اساساً بی اعتباری کش را بطور همزمان مدیریت می کند. این تقویت نوشتن (به حافظه پنهان) وجود دارد که به صورت همزمان اتفاق می افتد. اگر تعداد زیادی کپی کش دارید (غیر معمول نیست، زیرا کش ها معمولاً برای مقیاس بندی ترافیک خواندن معرفی می شوند)، تقویت نوشتن همزمان بسیار وحشتناک است. این تراکنش نوشتن را کند می کند و مستعد خطا (نرخ خطای بالاتر) است. این نباید تعجب آور باشد زیرا کش یک نمای غیرعادی شده را ذخیره می کند و ویژگی های مشابهی را به عنوان شاخص های جدول در مسیر به روز رسانی به اشتراک می گذارد. بنابراین هزینه تقویت نوشتن را نیز به طور مشابه به اشتراک بگذارید.

یکی از معاوضه هایی که می توانید انجام دهید این است که نامعتبر کردن کش را ناهمزمان کنید – user_ids را به عنوان مثال در یک گزارش ثبت کنید و ابطال کش را به صورت ناهمزمان پردازش کنید. با انجام نامعتبر کش به صورت ناهمزمان، گاهی اوقات کاربران می توانند داده های قدیمی را از حافظه پنهان مشاهده کنند.

اگر یک ماکت db فقط خواندنی داشته باشم چه می شود؟

اینجا اژدها باشید پس از باطل شدن یک کش (بعد از جهش db)، اگر یک ماکت db دارید (همیشه تا حدودی پشت db اولیه)، همیشه این احتمال وجود دارد که کش بتواند داده های خود را از یک ماکت قدیمی db پر کند. یک مثال ملموس به این صورت خواهد بود:

  • DB دارای داده A است
  • کلاینت A -> B را به روز می کند و کش را باطل می کند
  • ماکت DB هنوز دارای داده A است
  • حافظه نهان از DB replica پر می شود و A را دوباره داخل می کند
  • ماکت DB A -> B را می گیرد و به روز می کند
  • اکنون کش به طور نامحدود دارای A (داده قدیمی) خواهد بود

من وارد جزئیات نمی شوم اما چند راه برای حل این مشکل وجود دارد. به عنوان مثال

  • ارسال نامعتبر کش با داده های نسخه
  • داده‌های نسخه را در حافظه پنهان ردیابی کنید، بنابراین هرگز به گذشته برنمی‌گردد (مثلاً پس از پردازش بی‌اعتبار B، هرگز نباید دوباره به A برگردد)

اگر چند قطعه DB داشته باشم و حافظه پنهانی داشته باشم که داده ها را از چندین خرده DB جمع آوری کند، چه؟

راه حل اکثراً ثابت می ماند، تنها مشکلی که باید حل کنید سفارش مجدد است (که مهم ترین کلمه کلیدی در سیستم های توزیع شده است). بسته به سیستم خود، ممکن است بتوانید از ساعت های برداری استفاده کنید، یا اگر Spanner دارید (خوش شانس هستید)، می توانید فقط از TrueTime برای حل مشکلات سفارش استفاده کنید تا حالت های حافظه پنهان هرگز به گذشته برنگردد.

اگر داده ها را در Postgres ذخیره نکنم چه می شود؟

هنگام طراحی کش، باید از ویژگی هایی که پایگاه داده شما پشتیبانی می کند آگاه باشید. اینجاست که نیاز قبلی شروع می شود:

برای برخورد صحیح با یک سیستم توزیع شده، حافظه پنهان و پایگاه داده باید از یکدیگر آگاه باشند.

هنگامی که شما با کش ها سروکار دارید، با یک سیستم توزیع شده سر و کار دارید. با آن به عنوان یک سیستم توزیع شده رفتار کنید. اگر پایگاه داده شما از برگرداندن داده های اصلاح شده به صورت همزمان پشتیبانی نمی کند، اما از binlog (مثلا MySQL) پشتیبانی می کند، کاری که می توانید انجام دهید این است

  • باینلوگ را روی جهش ها دنبال کنید (جایی که می توانید user_id های جهش یافته را جمع آوری کنید)
  • ارسال باطل از خیاط

اینجا اژدها باشید توجه داشته باشید که قبلاً، ما منطق انتخاب را اجرا می‌کردیم، به عنوان مثال، وقتی کسی آدرس خود را تغییر می‌دهد، کلیدهای عدم اعتبار را پیدا می‌کردیم. که نیاز به یک عکس لحظه به لحظه در زمان اجرای تراکنش نوشتن دارد. تعداد کمی از پایگاه‌های داده این قابلیت‌ها را ارائه می‌دهند (اگر پایگاه داده فقط به نگه داشتن تاریخچه برای چند دقیقه نیاز داشته باشد، زمانی که این عکس‌های فوری در این مورد نیاز است، خیلی گران نیست). بنابراین اگر می‌خواهید منطق جمع‌آوری کلیدهای بی‌اعتبار را به حالت ناهمزمان (خارج از تراکنش db) منتقل کنید، پایگاه داده باید به طور کلی از خواندن عکس‌های فوری نقطه در زمان پشتیبانی کند. بسته به طرح کش، نسخه‌های اولیه‌ای که دارید، و موارد استفاده مشخص، کاملاً ممکن است که بتوانید حافظه پنهان خود را بدون پایگاه داده‌ای که از عکس‌های لحظه‌ای در زمان پشتیبانی می‌کند به‌روز نگه دارید. اما اگر در مورد موارد کلی صحبت می کنیم، یک DB با خواندن لحظه به لحظه (اخیرا) مورد نیاز است.

تقویت نوشتن چطور؟

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



لینک منبع

ارسال یک پاسخ

آدرس ایمیل شما منتشر نخواهد شد.