سایت را روی CDN گذاشتهاید و حالا کاربری که لاگین کرده، صفحه را در حالت مهمان میبیند. یا بدتر: سبد خرید یک نفر، برای نفر بعدی نمایش داده میشود. اگر لاگ سرور را باز کنید، میبینید درخواستهای /account با کد وضعیت 200 (HIT) پاسخ گرفتهاند، در حالی که باید از مبدأ بیایند. این دقیقاً همان جایی است که پیکربندی CDN از «فعال کردن CDN» جدا میشود.
مشکل از خود CDN نیست. از این است که به لبه گفتهاید چه چیزی را کش کند، و آن دستور با واقعیت اپلیکیشن داینامیک شما نمیخواند.
پیشفرض خطرناک: کش کردن همهچیز بر اساس پسوند فایل
بیشتر پنلهای CDN یک قانون آماده دارند که میگوید «فایلهای استاتیک را کش کن». تعریف «استاتیک» در آن قانون معمولاً بر اساس پسوند است: .css، .js، .jpg، .woff2. تا اینجا درست است. فاجعه وقتی شروع میشود که این قانون را روی مسیرهایی اعمال کنید که پسوند ندارند اما پشتشان داینامیک است.
یک URL مثل /api/v1/cart هیچ پسوندی ندارد، پس در نگاه اول استاتیک به نظر نمیرسد. اما اگر قانون کش شما بر اساس «هر چیزی زیر /assets/» باشد و کسی یک endpoint را اشتباهی زیر همان مسیر بگذارد، از آن لحظه پاسخهای شخصیسازیشده کش میشوند.
قانون درست این است: کش را بر اساس هدر پاسخ مبدأ تصمیم بگیرید، نه بر اساس مسیر. اگر اپلیکیشن شما Cache-Control: private, no-store برمیگرداند، CDN باید همان را محترم بشمارد.
کدام هدرها تعیین میکنند چه چیزی کش شود
سه هدر اصلی وجود دارد و ترتیب اهمیتشان ثابت است.
Cache-Control: مهمترین. مقدارpublic, max-age=31536000, immutableبرای فایلهای نسخهدار، وprivate, no-storeبرای هر چیزی که به نشست کاربر وابسته است.Vary: اگر پاسخ بر اساسAccept-EncodingیاAccept-Languageفرق میکند، باید اعلام شود. بدون آن، نسخه gzip به مرورگری میرسد که gzip نمیفهمد.Set-Cookie: اگر در پاسخ باشد، CDNهای محتاط آن پاسخ را کش نمیکنند. اگر CDN شما این را نادیده میگیرد، همانجا مشکل سبد خرید متولد میشود.
یک نکته عملی: در Nginx مبدأ، برای مسیرهای شخصی این خط را بگذارید:
location /account/ {
add_header Cache-Control "private, no-store, max-age=0";
add_header Vary "Cookie, Accept-Encoding";
}
و برای فایلهای استاتیک نسخهدار:
location ~* \.(css|js|woff2|webp)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
عدد 31536000 یعنی یک سال. این عدد فقط وقتی امن است که نام فایل شامل هش محتوا باشد، مثل app.4f9c2a.js. اگر نام فایل ثابت است، یک سال کش یعنی کاربر تا یک سال نسخه قدیمی را میبیند.
کوئریاسترینگ: تصمیمی که بیشتر سایتها اشتباه میگیرند
پیشفرض اکثر CDNها این است که کل کوئریاسترینگ را در کلید کش لحاظ کنند. یعنی /style.css?v=1 و /style.css?v=2 دو شیء جدا در لبه هستند. این رفتار امن است اما نرخ کش را پایین میآورد.
حالت دوم این است که کوئریاسترینگ را کامل نادیده بگیرید. این کار نرخ کش را بالا میبرد و در عوض یک باگ ظریف میسازد: اگر پارامترهای کوئری روی محتوا اثر دارند، پاسخ اشتباه سرو میشود.
حالت سوم، که من پیشفرض انتخاب میکنم، لیست سفید است. فقط پارامترهایی که واقعاً محتوا را تغییر میدهند در کلید کش بمانند:
# فقط این پارامترها در cache key بمانند
v, ver, version, lang, currency
پارامترهایی مثل utm_source، gclid و fbclid باید حذف شوند. اگر حذف نشوند، هر کمپین تبلیغاتی یک نسخه تازه از همان صفحه در لبه میسازد و نرخ کش شما بیدلیل سقوط میکند. در یکی از پروژهها همین اتفاق افتاد: نرخ کش از ۹۱ درصد به ۳۴ درصد رسید چون لینکهای ایمیلی همه utm_ داشتند.
کوکیها و نشست کاربر
اگر CDN شما روی «کش کن مگر کوکی وجود داشته باشد» تنظیم شده باشد، دو حالت پیش میآید. کاربر مهمان کش میشود، کاربر لاگینشده نه. این منطقی به نظر میرسد و در عمل برای سایتهای خبری و فروشگاهی کار میکند.
اما مشکل اینجاست که بسیاری از سایتها کوکیهای غیرضروری مثل کوکی آنالیتیکس یا کوکی بنر کوکیها را روی همه بازدیدکنندهها میگذارند. نتیجه: هیچکس کش نمیشود و CDN شما فقط یک پروکسی گرانقیمت است.
راهحل این است که کوکی نشست را از بقیه جدا کنید. در PHP مثلاً نام نشست را به چیزی مثل APPSESSID تغییر دهید و در CDN فقط همین نام را ملاک تصمیم بگیرید. کوکی آنالیتیکس دیگر روی کش اثر ندارد.
اینجا اشتباه میکنند: تیمها کوکی را از پاسخ حذف میکنند تا کش شود، بدون اینکه بدانند آن کوکی برای CSRF لازم است. علامتش این است که فرمها بعد از چند ساعت کار میکنند و بعد ناگهان 419 Page Expired میدهند. اگر این خطا را دیدید، اول به تنظیمات کش CDN نگاه کنید، نه به کد فرم.
باطلسازی کش بعد از انتشار
دو مکتب وجود دارد و انتخاب بینشان به نحوه انتشار شما بستگی دارد.
| روش | هزینه | کجا جواب میدهد |
|---|---|---|
| Purge بر اساس URL | کند، نیاز به لیست دقیق مسیرها | سایتهای کوچک با انتشار دستی |
| نسخهدار کردن نام فایل | نیاز به تغییر pipeline بیلد | هر سایتی که CI/CD دارد |
| Purge کامل (purge all) | موقتاً همه ترافیک به مبدأ میرود | فقط در اضطراب |
من همیشه نسخهدار کردن را انتخاب میکنم. Purge کامل را فقط وقتی میزنم که یک پاسخ اشتباه در حال سرو شدن به همه کاربران است و باید فوراً متوقف شود. آن لحظه، مبدأ شما باید بتواند بار کامل را تحمل کند؛ اگر نمیتواند، purge کامل خودش یک حادثه دوم میسازد.
اگر زیرساخت شما روی Kubernetes مدیریتشده اجرا میشود، نسخهدار کردن نام فایل را میتوانید داخل همان pipeline بیلد بگذارید و دیگر هیچوقت دستی purge نزنید. برای سایتهایی که ترافیک استاتیک سنگین دارند، جدا کردن فایلها به فضای ذخیرهسازی آبجکت و سرو کردنشان از لبه، فشار را از مبدأ کاملاً برمیدارد.
چه چیزی را هرگز کش نکنید
- هر مسیری که با نشست کاربر تغییر میکند: سبد خرید، پنل کاربری، صفحه پرداخت.
- پاسخهای
POST،PUTوDELETE. اینها بهطور پیشفرض کش نمیشوند، اما بعضی پیکربندیهای اشتباه این کار را میکنند. - هر پاسخی که هدر
Set-Cookieدارد. - صفحات خطا با کد
5xx. اگر CDN یک502را پنج دقیقه کش کند، پنج دقیقه خطا به همه نشان داده میشود.
مورد چهارم را جدی بگیرید. پیشفرض بعضی CDNها این است که پاسخهای خطا را کوتاه کش میکنند تا مبدأ نفس بکشد. این رفتار در حمله DDoS کمک میکند اما در خطای گذرای دیتابیس، دامنه خرابی را بزرگتر میکند.
ترتیب کارها در عمل
اول هدرهای مبدأ را درست کنید، بعد CDN را روشن کنید. اگر برعکس عمل کنید، مجبور میشوید همزمان با دو متغیر بجنگید و نمیفهمید کدام لایه پاسخ اشتباه میسازد.
بعد از روشن کردن، با curl -I چند مسیر کلیدی را چک کنید:
curl -I https://example.com/account/
curl -I https://example.com/assets/app.4f9c2a.js
curl -I "https://example.com/product?utm_source=news"
در خروجی دنبال X-Cache: HIT یا CF-Cache-Status بگردید. مسیر اول باید MISS یا BYPASS باشد. مسیر دوم باید HIT باشد. مسیر سوم باید همان کلید کش مسیر بدون پارامتر را برگرداند.
اگر سایت شما ترافیک متغیر دارد و میخواهید بدانید لبه چقدر تحمل میکند، قبل از هر تغییری در پیکربندی CDN یک خط پایه از زمان پاسخ بگیرید. راهنمای مانیتورینگ سرور و هشداردهی دقیقاً همین کار را پوشش میدهد و بدون آن، هر تغییری حدس است.
برای سایتهایی که ترافیک ورودیشان غیرقابل پیشبینی است، ترکیب CDN با سرور ابری سرورنت این امکان را میدهد که ظرفیت مبدأ را مستقل از لبه تنظیم کنید. اگر لازم شد، زیرساخت ابری (IaaS) هم کنترل کامل روی لایه مبدأ میدهد.
پرسشهای پرتکرار
چرا بعد از فعال کردن CDN کاربران لاگینشده را مهمان میبیند؟
چون پاسخ صفحهای که به نشست کاربر وابسته است، در لبه کش شده و به کاربر بعدی هم همان پاسخ داده میشود. علت معمولاً نبود هدر Cache-Control: private, no-store در پاسخ مبدأ است. تا وقتی این هدر را اضافه نکنید، هر تنظیمی در پنل CDN فقط بخشی از مشکل را میپوشاند.
راهحل دائمی این است که تصمیم کش را به مبدأ بسپارید و در CDN فقط بگویید «به هدر مبدأ احترام بگذار».
آیا باید کوئریاسترینگ را در کلید کش CDN نگه دارم؟
فقط پارامترهایی که واقعاً محتوا را تغییر میدهند. پارامترهای ردیابی مثل utm_source و gclid باید حذف شوند، وگرنه هر لینک کمپین یک نسخه تازه در لبه میسازد و نرخ کش سقوط میکند.
پارامترهایی مثل lang یا currency باید بمانند، چون پاسخ واقعاً فرق میکند.
بعد از انتشار نسخه جدید، چطور کش را باطل کنم؟
بهترین روش این است که اصلاً لازم نشود: نام فایلهای استاتیک را با هش محتوا نسخهدار کنید تا URL جدید، کلید کش جدید بسازد. Purge کامل را برای مواقع اضطراری نگه دارید، چون آن لحظه همه ترافیک به مبدأ میرود.
اگر مجبورید purge بزنید، اول مطمئن شوید مبدأ ظرفیت تحمل بار کامل را دارد.
آیا کش کردن صفحات HTML داینامیک اصلاً ارزش دارد؟
بله، برای صفحاتی که برای همه کاربران یکساناند: صفحه اصلی، لیست محصولات، مقالات. اینها معمولاً بیشترین ترافیک را دارند و کش کردنشان فشار دیتابیس را بهشدت کم میکند.
اما برای هر صفحهای که با کوکی کاربر تغییر میکند، پاسخ روشن است: نه. مرز را بر اساس هدر Vary و وجود Set-Cookie بکشید، نه بر اساس حدس.
قبل از هر چیز دیگری، همین امروز با curl -I سه مسیر کلیدی سایتتان را چک کنید. اگر مسیر پنل کاربری HIT برگرداند، بقیه بحثها بیفایده است.
دیدگاهها ۰
هنوز دیدگاهی ثبت نشده؛ اولین نفر باشید!