سایت را روی ریورس پروکسی گذاشتهاید، کش را روشن کردهاید، و حالا دو شکایت همزمان دارید: کاربری که وارد حساب شده، بعد از یک رفرش دوباره صفحهٔ ورود را میبیند؛ و مشتریای که محصولی را از سبد حذف کرده، هنوز همان محصول را در سبد میبیند. هر دو یک ریشه دارند: شما صفحهٔ کامل را کش کردهاید، بدون اینکه به پروکسی بگویید کدام پاسخها اصلاً نباید کش شوند.
کش لبه وقتی درست کار میکند که سه چیز را از هم جدا کنید: پاسخهای عمومی که همه یکسان میبینند، پاسخهای خصوصی که به یک کاربر تعلق دارند، و پاسخهایی که هرگز نباید ذخیره شوند. تا وقتی این تفکیک انجام نشده، هر بار که کش را روشن میکنید، یک باگ جدید تولید کردهاید.
چرا کش لبه روی سایت داینامیک جواب نمیدهد
پاسخ کوتاه: جواب میدهد، ولی نه با تنظیم پیشفرض. مشکل این است که اکثر سایتهای داینامیک هیچ هدر کش معناداری برنمیگردانند. PHP یا فریمورک شما یک پاسخ 200 OK میفرستد و تمام. پروکسی نگاه میکند، هیچ دستوری نمیبیند، و بر اساس سیاست پیشفرض خودش تصمیم میگیرد. بعضی پروکسیها هیچچیز را کش نمیکنند و شما فکر میکنید کش خراب است؛ بعضیها همهچیز را کش میکنند و شما فکر میکنید سایت خراب است.
راهحل این است که اپلیکیشن، خودش تصمیم کش را اعلام کند. این کار با هدرهای Cache-Control انجام میشود، نه با تنظیمات پروکسی. پروکسی فقط اجراکننده است.
هدرهایی که واقعاً تصمیم میگیرند
یک صفحهٔ عمومی مثل لیست محصولات باید این را برگرداند:
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600
max-age برای مرورگر است و s-maxage برای کش مشترک مثل CDN یا ریورس پروکسی. تفاوت این دو را جدی بگیرید؛ اگر فقط max-age بگذارید، CDN از آن استفاده نمیکند و شما فکر میکنید کش لبه فعال نشده. عدد ۶۰ ثانیه برای لیست محصولات عمدی است: کوتاهتر از آن یعنی کش بیفایده، بلندتر یعنی قیمتهای بهروزشده دیر به کاربر میرسند.
و یک صفحهٔ کاربر واردشده باید این را برگرداند:
Cache-Control: private, no-store
private میگوید فقط مرورگر خود کاربر میتواند نگه دارد، و no-store میگوید حتی مرورگر هم ذخیره نکند. برای صفحهٔ سبد خرید و پنل کاربری، no-store را انتخاب کنید. هزینهاش این است که هر بازدید یک درخواست کامل به بکاند میرود و شما هیچ صرفهجویی در منابع ندارید. این هزینه را بپردازید. کش کردن صفحهٔ سبد خرید، صرفهجوییای است که با از دست دادن سفارش جبران میشود.
تنظیم ریورس پروکسی: کجا دور بزنیم
در Nginx، تصمیم کش را به اپلیکیشن واگذار کنید و فقط مسیرهایی را که مطمئنید، دستی دور بزنید. یک بلوک کاری:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=edge:100m
max_size=10g inactive=24h use_temp_path=off;
server {
listen 443 ssl http2;
location / {
proxy_cache edge;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 301 302 5m;
proxy_cache_bypass $cookie_sessionid $http_authorization;
proxy_no_cache $cookie_sessionid $http_authorization;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
location /cart/ { proxy_pass http://backend; }
location /account/ { proxy_pass http://backend; }
location /api/ { proxy_pass http://backend; }
}
دو خط proxy_cache_bypass و proxy_no_cache قلب ماجرا هستند. اولی میگوید اگر این شرط برقرار بود، از کش نخوان و مستقیم برو به بکاند؛ دومی میگوید پاسخ را ذخیره نکن. اگر فقط اولی را بگذارید، پاسخ شخصی کاربر در کش مینشیند و به کاربر بعدی سرو میشود. این دقیقاً همان باگی است که بهصورت «کاربر A اطلاعات کاربر B را میبیند» گزارش میشود.
هدر X-Cache-Status را حتماً اضافه کنید. بدون آن، شما در حال حدس زدن هستید. با یک curl -I ساده میبینید HIT گرفتهاید یا MISS یا BYPASS:
curl -sI https://example.com/products | grep -i x-cache-status
اگر همیشه MISS میبینید، احتمالاً کوکی سشن روی همهٔ درخواستها ست میشود، حتی برای بازدیدکنندهٔ ناشناس. این را در مرورگر یا با curl -I بدون کوکی بررسی کنید.
اینجا اشتباه میکنند
رایجترین اشتباهی که دیدهام: تیم کوکی سشن را روی دامنهٔ اصلی ست میکند، بدون تفکیک مسیر. نتیجه این است که هر بازدیدکنندهٔ ناشناس هم یک کوکی سشن دارد، شرط proxy_cache_bypass برای همه برقرار میشود، و نرخ HIT روی صفر میماند. علامتش این است که همهچیز درست تنظیم به نظر میرسد، سرور بیدلیل زیر بار است، و کسی نمیفهمد چرا کش کار نمیکند. راهحل: کوکی سشن را فقط بعد از ورود موفق ست کنید، نه در اولین بازدید.
کش لبه و CDN: تفاوت در محل تصمیم
ریورس پروکسی روی سرور خودتان و CDN هر دو کش لبه هستند، ولی محل تصمیم فرق دارد. اگر ترافیک شما عمدتاً از یک جغرافیا میآید و بکاند در همان منطقه است، ریورس پروکسی محلی کافی است و پیچیدگی کمتری دارد. اگر کاربران از چند قاره میآیند، CDN تأخیر شبکه را کم میکند ولی شما کنترل کمتری روی منطق دور زدن کش دارید.
| معیار | ریورس پروکسی محلی | CDN |
|---|---|---|
| کنترل روی منطق bypass | کامل، در سطح کانفیگ | محدود به قواعد پنل |
| تأخیر برای کاربر دور | بالا | پایین |
| هزینهٔ نگهداری | روی دوش شما | مدیریتشده |
| مناسب برای | ترافیک منطقهای | مخاطب پراکنده |
انتخاب من برای سایت داینامیک با کاربران واردشده: اول ریورس پروکسی محلی را درست کنید، بعد CDN را جلوی آن بگذارید. اگر برعکس عمل کنید، دیباگ کردن اینکه کدام لایه پاسخ اشتباه را کش کرده، چند برابر سختتر میشود. برای آشنایی با گزینههای توزیع محتوا، شبکهٔ توزیع محتوا نقطهٔ شروع خوبی است.
سه لایهای که باید از هم جدا بمانند
وقتی سایت داینامیک دارید، پاسخها را در سه سبد بگذارید و برای هر سبد یک سیاست ثابت تعریف کنید:
- عمومی و قابل کش: صفحهٔ اصلی، لیست محصولات، صفحات دستهبندی، فایلهای استاتیک.
publicباs-maxageمشخص. - خصوصی: پنل کاربری، سبد خرید، تاریخچهٔ سفارش.
private, no-storeو دور زدن کش در پروکسی. - حساس: مسیرهای API که توکن میگیرند، صفحات پرداخت. دور زدن کامل، بدون استثنا.
نکتهای که کمتر گفته میشود: کش کردن پاسخهای API با متد POST اشتباه است، ولی خیلیها انجامش میدهند چون «سرعت میگیریم». اگر API شما idempotent نیست، کش کردنش یعنی یک عملیات مالی ممکن است دو بار اجرا شود یا اصلاً اجرا نشود. متد را در proxy_cache_key بگذارید تا GET و POST هرگز با هم قاطی نشوند.
پاکسازی کش بعد از انتشار
بعد از هر دیپلوی، کش لبه باید پاک شود، وگرنه کاربران نسخهٔ قدیمی را میبینند. اگر فایلهای استاتیک را با هش در نام نسخهبندی میکنید (app.a3f9c1.js)، نیازی به پاکسازی ندارید و میتوانید max-age را روی یک سال بگذارید. برای HTML، پاکسازی هدفمند کافی است:
curl -X PURGE https://example.com/products
این دستور فقط وقتی کار میکند که ماژول purge را در Nginx فعال کرده باشید. اگر فعال نیست، راه سریعتر این است که نسخهٔ کش را در proxy_cache_key بگنجانید و بعد از دیپلوی مقدارش را عوض کنید. یک متغیر محیطی که در کانفیگ خوانده میشود، این کار را بدون restart انجام میدهد.
چه زمانی کش لبه را خاموش کنید
اگر سایت شما کمتر از چند صد بازدید همزمان دارد و همهٔ کاربران واردشده هستند، کش لبه فقط پیچیدگی اضافه میکند. در این حالت بهینهسازی دیتابیس و ایندکسها بازده بیشتری دارد. کش لبه وقتی ارزشش را ثابت میکند که نسبت بازدیدکنندهٔ ناشناس به کاربر واردشده بالا باشد و همان صفحات عمومی، بخش بزرگی از ترافیک را بسازند.
برای سایتهایی که ترافیکشان نوسان شدید دارد، ترکیب کش لبه با مقیاسپذیری خودکار منطقی است: کش فشار را کم میکند و مقیاسپذیری، پیکهای باقیمانده را جذب میکند. و اگر میخواهید بدانید کدام درخواستها واقعاً به بکاند میرسند، مانیتورینگ سرور را قبل از هر تغییر کش راه بیندازید، نه بعد از آن.
اگر هنوز روی یک سرور واحد کار میکنید و میخواهید کش را جدا از اپلیکیشن نگه دارید، یک زیرساخت ابری (IaaS) به شما اجازه میدهد لایهٔ پروکسی را مستقل از بکاند مدیریت کنید. برای بارهای سنگینتر و چند سرویسی، کوبرنتیز مدیریتشده گزینهٔ بعدی است، ولی تا وقتی یک سرور جواب میدهد، به سراغش نروید.
پرسشهای پرتکرار
چرا بعد از فعال کردن کش، کاربران از حساب خارج میشوند؟
چون پاسخ صفحهٔ ورود یا صفحهٔ اصلی با هدر public کش شده و مرورگر یا CDN همان پاسخ را به کاربر بعدی میدهد. کوکی سشن در پاسخ کششده وجود ندارد یا متعلق به کاربر دیگری است. راهحل این است که هر مسیری که به سشن وابسته است، با proxy_cache_bypass از کش خارج شود و پاسخش private, no-store برگرداند.
تفاوت max-age و s-maxage در کش لبه چیست؟
max-age مدت اعتبار پاسخ در مرورگر کاربر است و s-maxage مدت اعتبار آن در کشهای مشترک مثل CDN و ریورس پروکسی. اگر فقط max-age بگذارید، کش لبه از آن استفاده نمیکند و شما فکر میکنید کش فعال نشده. برای صفحات عمومی هر دو را با هم بگذارید.
چطور بفهمم کش لبه واقعاً کار میکند؟
هدر X-Cache-Status را در پاسخها اضافه کنید و با curl -sI بررسی کنید که مقدارش HIT است یا MISS. اگر همیشه MISS میبینید، احتمالاً کوکی سشن روی همهٔ درخواستها ست میشود و شرط bypass برای همه برقرار است. این را با یک درخواست بدون کوکی تست کنید.
آیا کش کردن پاسخهای API درست است؟
فقط برای درخواستهای GET و پاسخهای عمومی که به کاربر خاصی وابسته نیستند. کش کردن POST یا هر پاسخ وابسته به توکن، باعث میشود دادهٔ یک کاربر به کاربر دیگر سرو شود یا یک عملیات مالی دو بار اجرا شود. متد HTTP را در کلید کش بگنجانید تا این دو هرگز با هم قاطی نشوند.
دیدگاهها ۰
هنوز دیدگاهی ثبت نشده؛ اولین نفر باشید!