کش سایت چیست و کش CDN چگونه کار میکند؟ راهنمای عملی
از cache hit و cache key تا Cache-Control و TTL؛ یاد میگیرید کش CDN را طوری تنظیم کنید که سایت سریع شود و صفحههای کاربر لاگینشده لو نرود.
کش کردن ارزانترین بهینهسازی ممکن است: پاسخی که از لبه سرو میشود نه به سرور شما فشار میآورد، نه منتظر مسیر پرنوسان اینترنت بینالملل میماند. اما بیشتر سایتهایی که CDN میگیرند ماهها با نرخ hit پایین کار میکنند، چون کش را «روشن» کردهاند بدون اینکه بدانند لبه بر چه اساسی تصمیم میگیرد. اینجا همان تصمیم را باز میکنیم: کلید کش، هدرها، TTL و پاکسازی.
چرخه یک درخواست: Hit، Miss و Bypass
وقتی دامنه پشت CDN قرار میگیرد (اگر با مفهوم کلی آشنا نیستید، CDN چیست و چطور کار میکند را اول بخوانید)، درخواست ابتدا به نزدیکترین نود لبه میرسد. نود کلید کش را میسازد، حافظهاش را نگاه میکند و یکی از این چهار حالت رخ میدهد:
| وضعیت | یعنی چه | مبدأ چه میبیند |
|---|---|---|
| HIT | نسخه معتبر در لبه بود | هیچ درخواستی نمیرسد |
| MISS | چیزی در لبه نبود | یک درخواست کامل، پاسخ ذخیره میشود |
| EXPIRED | نسخه بود ولی TTL تمام شده | یک درخواست شرطی، معمولاً پاسخ HTTP 304 |
| BYPASS | قانون یا هدر اجازه کش نداده | هر بار درخواست کامل، هیچ چیز ذخیره نمیشود |
تفاوت MISS و BYPASS مهم است: MISS یک بار رخ میدهد و بعد به HIT تبدیل میشود، ولی BYPASS یعنی عملاً کش ندارید. هر HIT همزمان TTFB کاربر، بار مبدأ و ترافیک خروجی سرور را کم میکند؛ در nsin ترافیک در سه تعرفه جداگانه cached، proxied و direct حساب میشود، پس نرخ hit مستقیماً تعیین میکند بایتهای شما در کدام ردیف بیفتند و روی هزینه پهنای باند اثر میگذارد.
کلید کش: لبه از کجا میفهمد دو درخواست «یکی» هستند؟
کلید کش (cache key) رشتهای است که لبه از روی درخواست میسازد و پاسخ را زیر آن ذخیره میکند: متد (فقط GET و HEAD کش میشوند)، اسکیم، هاست، مسیر و کوئریاسترینگ. هر چیزی که در کلید نباشد ولی محتوای پاسخ را عوض کند، یک باگ بالقوه است.
کوئریاسترینگ
?page=2 و ?page=3 دو صفحه متفاوتاند، پس کوئریاسترینگ باید در کلید بماند. اما utm_source و gclid محتوا را عوض نمیکنند و اگر بمانند، یک صفحه واحد به دهها نسخه تکهتکه میشود. اگر میخواهید نادیدهشان بگیرید، حتماً از فهرست مجاز استفاده کنید؛ حذف کورکورانه همه پارامترها فاجعه است.
هدر Origin و کوکیها
اگر پاسخی هدر Access-Control-Allow-Origin دارد و با Vary: Origin جواب میدهد، کلید هم باید بر اساس Origin تفکیک شود. وگرنه اولین پرکردن کش که بدون Origin انجام شده نسخه بدون CORS را ذخیره میکند و همان به مرورگری میرسد که از دامنه دیگری fetch کرده — خطای CORS مرموزی که با ریلود «درست میشود» و بعد برمیگردد. کوکی درخواست و Set-Cookie پاسخ هم بهطور پیشفرض باید کش را دور بزنند؛ دلیلش پایینتر.
Cache-Control؛ زبان مشترک شما و لبه
مبدأ با هدر Cache-Control به لبه و مرورگر میگوید با پاسخ چه کنند. مهمترین دستورها:
public/private—privateیعنی فقط مرورگر خود کاربر حق نگهداشتن دارد، نه کش مشترک.max-age=N— عمر پاسخ به ثانیه، برای همه کشها از جمله مرورگر.s-maxage=N— همان، ولی فقط برای کش مشترک و با اولویت بالاتر. ابزار شما برای «در لبه یک ساعت، در مرورگر ۶۰ ثانیه».no-cache— برخلاف اسمش یعنی «ذخیره کن، ولی قبل از هر بار سرو اعتبارش را بپرس».no-store— هیچجا ذخیره نشود. فقط پنل کاربر، سبد خرید و احراز هویت.must-revalidate— بعد از انقضا، سرو نسخه کهنه ممنوع است.stale-while-revalidate=N— نسخه کهنه فوراً سرو میشود و بهروزرسانی در پسزمینه انجام میگیرد. بهترین دوست LCP شما.stale-if-error=N— اگر مبدأ خطا داد، نسخه کهنه سرو شود. nsin مستقل از این هدر هم میتواند وقتی مبدأ از دسترس خارج میشود صفحههای کششده را سرو کند.immutable— به مرورگر میگوید حتی با ریلود هم درخواست شرطی نفرست؛ فقط روی فایل هشدار.
هدر قدیمی Expires هر وقت Cache-Control شامل max-age یا s-maxage باشد نادیده گرفته میشود؛ روی Pragma هم اصلاً حساب نکنید.
اعتبارسنجی مجدد با ETag و Last-Modified
وقتی TTL تمام میشود لازم نیست کل فایل دوباره منتقل شود. لبه یک درخواست شرطی میفرستد: If-None-Match با ETag قبلی، یا If-Modified-Since با تاریخ Last-Modified. اگر محتوا عوض نشده باشد مبدأ یک HTTP 304 خالی برمیگرداند و همان نسخه با عمر تازه سرو میشود.
تله رایج: در مبدأهای چندسروره، ETag پیشفرض بعضی وبسرورها به inode فایل وابسته است و روی هر سرور فرق میکند، پس هیچوقت 304 نمیگیرید. یا غیرفعالش کنید، یا مقداری بر پایه هش محتوا بدهید.
کش درست تنظیمشده بار مبدأ را حذف میکند؛ کش غلط تنظیمشده حریم خصوصی کاربران را.
TTL پیشنهادی بر اساس نوع محتوا
این جدول نقطه شروع است، نه قانون؛ بر اساس نرخ تغییر محتوای خودتان تنظیمش کنید.
| نوع محتوا | TTL لبه | TTL مرورگر | نکته |
|---|---|---|---|
فایل هشدار مثل app.9f2c1b.js | یک سال | max-age=31536000, immutable | نام با هر بیلد عوض میشود، پس purge لازم نیست |
| تصویر، فونت، آیکون | چند هفته | چند روز | با ETag ترکیب کنید |
| CSS و JS بدون هش در نام | چند ساعت | کوتاه + no-cache | راهحل واقعی، هشدار کردن نام است |
| HTML صفحههای عمومی | چند دقیقه تا چند ساعت | no-cache | حتماً با stale-while-revalidate |
| صفحه محصول و قیمت | چند دقیقه | 0 | بعد از تغییر قیمت، purge هدفمند |
| خروجی GET یک API عمومی | چند ثانیه تا چند دقیقه | 0 | با s-maxage از مرورگر جدا کنید |
| API کاربر، سبد خرید، پنل | بدون کش | private, no-store | استثنا ندارد |
sitemap.xml و robots.txt | چند ساعت | چند ساعت | برای خزندهها همیشه در دسترس بماند |
سه سطر اول بیشترین سود Core Web Vitals را میدهند؛ اگر LCP سایتتان هنوز بالای 2.5 ثانیه است، راهنمای Core Web Vitals قدم بعدی شماست.
خطر کش شدن صفحه کاربر لاگینشده
سناریو را دقیق تصور کنید: کاربر A وارد حساب میشود، مبدأ صفحه پروفایل او را همراه Set-Cookie برمیگرداند، و لبه — چون قانون «همهچیز را کش کن» فعال بوده — آن پاسخ را ذخیره میکند. از این لحظه هر بازدیدکنندهای که همان URL را باز کند، صفحه و در بدترین حالت نشست کاربر A را میگیرد. کلاسیکترین حادثه امنیتی مربوط به کش همین است.
- روی هاستی که ورود کاربر دارد، تا وقتی قانون دور زدن بر اساس کوکی درخواست را نگذاشتهاید، کش سراسری را فعال نکنید.
- پاسخهای دارای
Set-Cookieیا کش نشوند، یا این هدر هنگام ذخیره حذف شود. - کوکیهای نشست را با نام دور بزنید. در وردپرس:
wordpress_logged_in_*،comment_author_*،woocommerce_items_in_cartو کل مسیرهای/wp-adminو/wp-login.php. - به
Vary: Cookieتکیه نکنید؛ فنی درست است ولی هر کوکی متفاوت یک نسخه جدا میسازد و نرخ hit را صفر میکند.
پاکسازی کش و بیاعتبارسازی
بهترین purge، purgeای است که لازم نمیشود: اگر نام فایلهای استاتیک هش محتوا داشته باشد، هر انتشار یک URL تازه میسازد. purge را برای چیزی نگه دارید که URL ثابت دارد — صفحه اصلی، صفحه محصول، فید، sitemap.
- purge هدفمند بعد از انتشار مطلب یا تغییر قیمت: فقط همان URL و صفحههای فهرست مرتبط.
- purge سراسری فقط بعد از تغییر قالب یا مهاجرت؛ پاک کردن کل کش در ساعت اوج، سیل درخواست را یکجا به مبدأ برمیگرداند.
- ترتیب انتشار: اول مبدأ را بهروزرسانی کنید، بعد purge بزنید. برعکسش یعنی لبه دوباره نسخه قدیمی را کش میکند.
- TTL کوتاه بهجای purge مکرر: اگر روزی چند بار purge دستی میزنید، TTL شما اشتباه است.
در پنل nsin کش فهرستی از قانونهاست؛ هر قانون دامنه خودش (هاست و مسیر)، TTL و رفتار مخصوص خودش را دارد و purge هم در دسترس است. لاگهای زنده هم نشان میدهند کدام مسیر hit میخورد و کدام bypass میشود. برای اتصال دامنه، ترتیب بیخطر کار در راهاندازی CDN بدون قطعی آمده و جزئیات فنی هدرها در مستندات.
پرسشهای متداول
کش سایت چیست و با کش مرورگر چه فرقی دارد؟
کش مرورگر فقط برای همان یک کاربر روی همان دستگاه کار میکند، ولی کش CDN مشترک است: یک بار پر میشود و همان نسخه به همه بازدیدکنندههای بعدی میرسد. به همین دلیل هم قدرتش بیشتر است و هم اشتباه در آن گرانتر تمام میشود.
چرا با وجود فعال بودن CDN، درخواستها همیشه MISS میشوند؟
معمولاً یکی از این چهار دلیل: مبدأ Cache-Control: no-store یا private میفرستد، پاسخها Set-Cookie دارند، کوئریاسترینگ متغیر کلید کش را تکهتکه کرده، یا مسیر داخل دامنه هیچ قانون کشی نیست. اول هدرهای مبدأ را با curl -I ببینید.
آیا کش کردن صفحات به سئو آسیب میزند؟
برعکس. Googlebot هم مثل کاربر از لبه پاسخ میگیرد و پاسخ سریعتر یعنی بودجه خزش بهتر. فقط TTL صفحههای پرتغییر را منطقی نگه دارید تا نسخه کهنه ایندکس نشود.
بعد از تغییر سایت چقدر طول میکشد کش پاک شود؟
با purge بلافاصله؛ بدون purge تا پایان TTL همان قانون. اما کش مرورگر کاربران با purge پاک نمیشود و تا انقضای max-age میماند — دلیل دیگری برای اینکه max-age فایلهای بدون هش را بلند نگذارید.
کش را با یک قانون ساده شروع کنید: TTL بلند برای مسیرهای استاتیک، TTL کوتاه بههمراه stale-while-revalidate برای HTML، و دور زدن صریح برای هر مسیری که کاربر لاگینشده دارد. بعد یکی دو روز لاگها را ببینید و TTLها را تنظیم کنید. هر دامنه جدید در پنل nsin هفت روز نسخه کامل Enterprise رایگان دارد، که برای همین آزمون و خطای اولیه کافی است.