ক্যাশিং হেডার: Cache-Control থেকে revalidation
কোন রেসপন্স কে জমা রাখবে, কতক্ষণ টাটকা ধরবে, মেয়াদ শেষে কী করবে: প্রতিটা Cache-Control ডিরেক্টিভ, Age, ETag আর 304, CDN-এর জন্য আলাদা হেডার, Set-Cookie-র ফাঁদ, আর প্রতিটা ধরনের কন্টেন্টের জন্য তৈরি রেসিপি।
গল্পে বুঝি
চকবাজারের রুশদের বেকারি থেকে রোজ ভোরে রুটি, বনরুটি আর কেক যায় শহরের কয়েকটা দোকানে। একটা দোকান চালান হাইসাম। প্রতিটা প্যাকেটে রুশদ একটা লেবেল লাগিয়ে দেন, আর হাইসাম লেবেল পড়েই ঠিক করেন কী করবেন। পাউরুটির লেবেলে লেখা “৩ দিন ভালো থাকবে”; হাইসাম তিন দিন নিশ্চিন্তে বিক্রি করেন। বিয়ের অর্ডারের কেকের লেবেলে লেখা “শুধু অর্ডারকারীর জন্য, তাকে রাখবেন না”; হাইসাম ওটা দোকানের তাকে তোলেনই না, সরাসরি খদ্দেরের হাতে দেন। আর ক্রিম পেস্ট্রির লেবেলে লেখা “বিক্রির আগে প্রতিবার বেকারিতে ফোন করে জেনে নিন এখনো চলবে কিনা”; হাইসাম রাখেন ঠিকই, কিন্তু প্রতিবার বিক্রির আগে একটা ফোন করেন।
তিন দিন পেরোলে পাউরুটি ফেলে দেন না হাইসাম। রুশদকে ফোন করে প্যাকেটের ব্যাচ নম্বর বলেন: “ব্যাচ ৪১৭, এখনো চলবে?” রেসিপি না বদলালে রুশদ বলেন, “চলবে, আরও তিন দিন।” নতুন রুটি পাঠাতে হয় না, শুধু একটা ফোন। রেসিপি বদলালে নতুন ব্যাচ পাঠান। কখনো বেকারির ফোন বন্ধ থাকে; তখন লেবেলের নিচের ছোট লেখাটা কাজে লাগে: “মেয়াদের পর বেকারি না পেলে আরও এক দিন বিক্রি করা যাবে।” হাইসাম সেটুকু ছাড় নেন, তার বেশি নয়।
একদিন রুশদ খেয়াল করলেন, হাইসামের দোকানের জন্য লেবেলে “৩ দিন” লিখলে ক্রেতারা বাড়িতে নিয়েও তিন দিন রেখে দেন, যা তিনি চান না। তাই তিনি দুই রকম লেবেল দিতে শুরু করলেন: একটা দোকানের জন্য (“দোকানে ৩ দিন”), আরেকটা ক্রেতার জন্য (“বাড়িতে ১ দিন”)। আর একটা নিয়ম হাইসাম নিজেই বানিয়ে নিয়েছেন: কোনো প্যাকেটে যদি কারো নামের চিরকুট লাগানো থাকে, সেটা তিনি কখনো তাকে তোলেন না, লেবেলে যাই লেখা থাকুক।
মিলিয়ে নিই: রুশদের বেকারি হলো origin, হাইসামের দোকান CDN-এর শেয়ার্ড ক্যাশ, আর ক্রেতার বাড়ি ব্রাউজার ক্যাশ। লেবেলটাই Cache-Control হেডার। “৩ দিন ভালো” হলো max-age; “শুধু অর্ডারকারীর জন্য” হলো private; “প্রতিবার জেনে নিন” হলো no-cache। ব্যাচ নম্বর হলো ETag, ফোনে “এখনো চলবে?” হলো conditional request, আর “চলবে, আরও তিন দিন” হলো 304 Not Modified। “বেকারি না পেলে আরও এক দিন” হলো stale-if-error। দোকান আর বাড়ির জন্য আলাদা লেবেল হলো s-maxage বা CDN-Cache-Control, আর নামের চিরকুট দেখে তাকে না তোলা হলো Set-Cookie থাকলে ক্যাশ না করার CDN নিয়ম। বাস্তবে প্রতিটা রেসপন্সে এই লেবেলই ঠিক করে CDN কী করবে, তাই এই চ্যাপ্টারটা পুরো ট্র্যাকের সবচেয়ে গুরুত্বপূর্ণগুলোর একটা।
দুই রকম ক্যাশ, এক হেডার
HTTP ক্যাশিংয়ের নিয়ম লেখা আছে RFC 9111-এ। এটা দুই রকম ক্যাশ আলাদা করে:
- Private cache: একজন ইউজারের নিজের, যেমন ব্রাউজারের ক্যাশ। ব্যক্তিগত রেসপন্স রাখা নিরাপদ।
- Shared cache: অনেক ইউজারের মধ্যে ভাগ করা, যেমন CDN, কোম্পানির proxy, reverse proxy। এখানে রাখা জিনিস অন্য ইউজারও পেতে পারে।
একটাই Cache-Control হেডার দুই রকম ক্যাশকেই নির্দেশ দেয়, আর কিছু ডিরেক্টিভ শুধু একটার জন্য। CDN নিয়ে কাজ করতে গেলে প্রতিটা ডিরেক্টিভ পড়ার সময় প্রশ্ন করুন: এটা কি শেয়ার্ড ক্যাশের জন্যও প্রযোজ্য?
ফ্রেশনেস মডেল: টাটকা, মেয়াদোত্তীর্ণ, revalidate
প্রতিটা ক্যাশ করা রেসপন্সের একটা freshness lifetime থাকে (কতক্ষণ টাটকা) আর একটা age (কতক্ষণ আগে origin থেকে এসেছে)। নিয়মটা এক লাইনের:
fresh if age < freshness_lifetime
stale otherwise টাটকা হলে ক্যাশ origin-কে না জিজ্ঞেস করেই দেয়। stale হলে সাধারণত revalidate করে, যদি না কোনো ডিরেক্টিভ stale দেওয়ার অনুমতি দেয়।
শেয়ার্ড ক্যাশে freshness lifetime বের হয় এই ক্রমে, প্রথম যেটা পাওয়া যায়:
s-maxagemax-ageExpiresথেকেDateবাদ দিয়ে- কিছুই না থাকলে heuristic (নিচে)
ব্রাউজার (private cache) s-maxage উপেক্ষা করে, শুরু করে max-age থেকে।
প্রতিটা ডিরেক্টিভ
max-age=N
রেসপন্স N সেকেন্ড টাটকা। ব্রাউজার আর CDN দুটোর জন্যই, যদি না CDN-এর জন্য আলাদা কিছু বলা থাকে।
Cache-Control: max-age=3600 s-maxage=N
শুধু শেয়ার্ড ক্যাশের জন্য, আর সেখানে max-age-কে ছাপিয়ে যায়। এটাই “দোকানে ৩ দিন, বাড়িতে ১ দিন”:
Cache-Control: max-age=60, s-maxage=86400 ব্রাউজার ৬০ সেকেন্ড রাখবে, CDN এক দিন। কেন এমন চাইবেন? কারণ CDN-এর কপি আপনি purge করতে পারেন (চ্যাপ্টার ৬), কিন্তু ইউজারের ব্রাউজারের কপি কখনো না। তাই যে কন্টেন্ট বদলাতে পারে, তার ব্রাউজার TTL ছোট রাখুন আর CDN TTL বড়।
RFC অনুযায়ী s-maxage শেয়ার্ড ক্যাশের জন্য proxy-revalidate-ও বোঝায়: মেয়াদ শেষে যাচাই না করে stale দেওয়া যাবে না।
no-cache: নামটা বিভ্রান্তিকর
no-cache মানে “ক্যাশ করো না” নয়। মানে “রাখতে পারো, কিন্তু প্রতিবার দেওয়ার আগে origin-এর সাথে যাচাই করো।” গল্পের ক্রিম পেস্ট্রি। ETag থাকলে প্রতিটা যাচাই একটা ছোট 304, তাই এটা HTML-এর জন্য ভালো পছন্দ হতে পারে যখন আপনি চান ডিপ্লয়ের সাথে সাথে সবাই নতুনটা দেখুক।
no-store: সত্যিকারের “রেখো না”
কোনো ক্যাশ, ব্রাউজার বা CDN, এই রেসপন্স ডিস্কে বা মেমরিতে জমা রাখবে না। ব্যাংক স্টেটমেন্ট, অথ টোকেন, ব্যক্তিগত ডেটা।
| ডিরেক্টিভ | রাখা যাবে? | দেওয়ার আগে যাচাই? |
|---|---|---|
no-cache | হ্যাঁ | প্রতিবার |
no-store | না | প্রশ্নই নেই |
private আর public
private: শুধু private cache (ব্রাউজার) রাখতে পারবে, শেয়ার্ড ক্যাশ না। লগইন করা ইউজারের পেজের জন্য।
public: শেয়ার্ড ক্যাশও রাখতে পারবে, এমনকি এমন রেসপন্সও যা ডিফল্টে রাখা হতো না (যেমন Authorization হেডারসহ রিকোয়েস্টের রেসপন্স)। সাধারণ রেসপন্সে max-age থাকলেই শেয়ার্ড ক্যাশ রাখতে পারে, তাই public প্রায়ই অপ্রয়োজনীয়; কিন্তু অথেন্টিকেটেড রিকোয়েস্টে এটা ভুলে লাগালে বিপদ।
Authorization হেডারসহ রিকোয়েস্টের রেসপন্সে public লাগানো মানে আপনি CDN-কে বলছেন “এক ইউজারের অথেন্টিকেটেড রেসপন্স সবাইকে দিতে পারো।” নিশ্চিত না হলে লাগাবেন না।
must-revalidate আর proxy-revalidate
মেয়াদ শেষ হলে যাচাই ছাড়া stale দেওয়া নিষেধ, এমনকি origin ডাউন থাকলেও (তখন 504 দিতে হবে)। proxy-revalidate একই জিনিস, শুধু শেয়ার্ড ক্যাশের জন্য। ডেটা পুরনো হলে ক্ষতি হয় এমন জায়গায় ব্যবহার করুন; অন্য জায়গায় এটা stale-if-error-এর সুরক্ষা কেড়ে নেয়।
immutable (RFC 8246)
“এই URL-এর কন্টেন্ট কখনো বদলাবে না।” ব্রাউজার তখন ইউজার রিফ্রেশ চাপলেও revalidate করে না। ফাইলনেমে কন্টেন্টের hash থাকলে (app.3f9a1c.js) এটাই সঠিক:
Cache-Control: public, max-age=31536000, immutable stale-while-revalidate=N (RFC 5861)
মেয়াদ শেষের পর আরও N সেকেন্ড ক্যাশ পুরনো কপি সাথে সাথে দিতে পারে, আর পেছনে origin থেকে নতুনটা আনে। ইউজার কখনো revalidation-এর অপেক্ষা করে না।
Cache-Control: max-age=60, stale-while-revalidate=300 প্রথম ৬০ সেকেন্ড টাটকা; ৬০ থেকে ৩৬০ সেকেন্ডের মধ্যে রিকোয়েস্ট এলে পুরনো কপি দিয়ে পেছনে আপডেট; ৩৬০-এর পর সাধারণ revalidation।
stale-if-error=N (RFC 5861)
origin এরর দিলে (5xx) বা পৌঁছানো না গেলে মেয়াদের পর আরও N সেকেন্ড stale দেওয়া যাবে। গল্পের “বেকারি না পেলে আরও এক দিন”। origin ডাউন থাকলেও সাইট টিকিয়ে রাখার সবচেয়ে সস্তা উপায়:
Cache-Control: max-age=300, stale-if-error=86400 সব CDN এই দুই ডিরেক্টিভ সমানভাবে মানে না, আর কিছু CDN-এ অন্য ডিরেক্টিভের সাথে মিশে অপ্রত্যাশিত আচরণ হয়। উদাহরণ: ২০২৬ সালের অক্টোবর অনুযায়ী Cloudflare-এর ডকুমেন্টেশন বলছে s-maxage থাকলে সেখানে stale-while-revalidate কাজ করে না, কারণ s-maxage নিজেই proxy-revalidate বোঝায়। আপনার প্রোভাইডারের ডকুমেন্টেশনে যাচাই করুন, আর curl দিয়ে নিজে পরীক্ষা করুন।
Expires আর Age
Expires হলো পুরনো HTTP/1.0 উপায়: একটা নির্দিষ্ট তারিখ-সময় পর্যন্ত টাটকা।
Expires: Sun, 01 Nov 2026 12:00:00 GMT Cache-Control: max-age থাকলে Expires উপেক্ষিত হয়। এটা ঘড়ির ওপর নির্ভর করে, আর ভুল ফরম্যাটের তারিখ (বা Expires: 0) মানে “ইতিমধ্যে মেয়াদোত্তীর্ণ”। নতুন কিছুতে শুধু max-age ব্যবহার করুন।
Age হেডার শেয়ার্ড ক্যাশ যোগ করে: কপিটা কত সেকেন্ড আগে origin থেকে এসেছে বা যাচাই হয়েছে। ব্রাউজার max-age থেকে Age বাদ দিয়ে নিজের বাকি সময় হিসাব করে। তাই CDN-এ ১ ঘণ্টা থাকা max-age=3600-এর কপি ব্রাউজারে আসার সাথে সাথেই প্রায় মেয়াদোত্তীর্ণ। এটা ঠিক আছে, এভাবেই কাজ করার কথা।
$ curl -sI https://baytalhikma.org/css/site.css | grep -iE '^(cache-control|age):'
cache-control: max-age=3600
age: 3540 (উদাহরণ।) এই কপি ব্রাউজারে আর মাত্র ৬০ সেকেন্ড টাটকা থাকবে।
কোনো হেডার না থাকলে: heuristic freshness
origin যদি Cache-Control বা Expires কিছুই না পাঠায়, RFC 9111 ক্যাশকে নিজে একটা অনুমান করার অনুমতি দেয়, যদি রেসপন্সের স্ট্যাটাস কোড ডিফল্টভাবে ক্যাশেবল হয় (যেমন 200, 301, 404)। প্রচলিত অনুমান: Last-Modified থেকে এখন পর্যন্ত সময়ের ১০%। দশ দিন আগে বদলানো ফাইল মোটামুটি এক দিন টাটকা।
CDN-গুলো প্রায়ই নিজেদের একটা ডিফল্ট TTL ব্যবহার করে। উদাহরণ: ২০২৬ সালের অক্টোবর অনুযায়ী Fastly-র ডকুমেন্টেশন বলছে কোনো ফ্রেশনেস হেডার না থাকলে ডিফল্ট TTL ২ মিনিট (সার্ভিসে আলাদা fallback TTL সেট না থাকলে)। অন্য প্রোভাইডারের ডিফল্ট আলাদা; নিজেরটার ডকুমেন্টেশন দেখুন।
শিক্ষা: হেডার না পাঠানো মানে “ক্যাশ করো না” নয়; মানে “যা খুশি করো।” প্রতিটা রেসপন্সে স্পষ্ট Cache-Control দিন।
Revalidation: ETag আর Last-Modified
মেয়াদোত্তীর্ণ কপি ফেলে না দিয়ে ক্যাশ origin-কে জিজ্ঞেস করে “বদলেছে?” এর জন্য রেসপন্সে একটা validator থাকা লাগে।
ETag হলো কন্টেন্টের একটা পরিচয়, প্রায়ই hash বা ভার্সন। ক্যাশ পরেরবার If-None-Match দিয়ে পাঠায়:
$ curl -sI https://baytalhikma.org/index.html | grep -i etag
etag: "6720f1a3-2b41"
$ curl -sI -H 'If-None-Match: "6720f1a3-2b41"' https://baytalhikma.org/index.html
HTTP/2 304
etag: "6720f1a3-2b41"
cache-control: no-cache (উদাহরণ।) 304-এ বডি নেই। ক্যাশ নিজের কপির মেয়াদ নতুন করে শুরু করে।
Last-Modified একটা তারিখ; ক্যাশ পাঠায় If-Modified-Since। সেকেন্ডের নিচে নির্ভুল নয়, আর একাধিক origin সার্ভারে একই ফাইলের আলাদা mtime থাকলে ভুল ফল দেয়। দুটো থাকলে ETag-কে অগ্রাধিকার দেওয়া হয়।
W/"..." দিয়ে শুরু হলে সেটা weak ETag: “অর্থের দিক থেকে একই”, বাইট-বাই-বাইট নয় (যেমন gzip আর brotli সংস্করণ)।
একাধিক origin সার্ভার থাকলে ETag মিলিয়ে নিন। কিছু ওয়েব সার্ভার ETag বানায় ফাইলের inode বা mtime থেকে, তাই দুই সার্ভারে একই ফাইলের ETag আলাদা হয়। লোড ব্যালান্সার যদি revalidation রিকোয়েস্ট অন্য সার্ভারে পাঠায়, 304-এর বদলে প্রতিবার পুরো 200 আসে, আর আপনার ব্যান্ডউইথ নীরবে বাড়ে।
CDN-এর জন্য আলাদা হেডার
s-maxage সব শেয়ার্ড ক্যাশকে একসাথে বলে: CDN, কোম্পানির proxy, সব। কখনো আপনি শুধু নিজের CDN-কে আলাদা নির্দেশ দিতে চান, আর সেই নির্দেশ ব্রাউজারে পৌঁছাক তাও চান না। এজন্য targeted হেডার আছে।
CDN-Cache-Control (RFC 9213)
Cache-Control-এর মতোই সিনট্যাক্স, কিন্তু শুধু CDN-এর জন্য। যে CDN এটা মানে, সে এটা দেখলে সাধারণ Cache-Control-কে ক্যাশিং সিদ্ধান্তে উপেক্ষা করে:
Cache-Control: max-age=60
CDN-Cache-Control: max-age=86400 ব্রাউজার ৬০ সেকেন্ড, CDN এক দিন। RFC 9213 একটা প্যাটার্নও দেয়: ভেন্ডর-নির্দিষ্ট নাম (যেমন ProviderName-CDN-Cache-Control), যা শুধু ওই একটা CDN পড়ে, যাতে CDN-এর সামনে আরেকটা CDN থাকলে দুজনকে আলাদা নির্দেশ দেওয়া যায়।
২০২৬ সালের অক্টোবর অনুযায়ী Cloudflare-এর ডকুমেন্টেশন বলছে Cloudflare CDN-Cache-Control আর নিজস্ব Cloudflare-CDN-Cache-Control দুটোই মানে। দুটো থাকলে বেশি নির্দিষ্টটা (Cloudflare-এরটা) পড়ে। CDN-Cache-Control সে নিচের দিকে (ব্রাউজারের দিকে) পাঠিয়ে দেয়, যাতে মাঝে অন্য CDN থাকলে সেও পড়তে পারে; নিজস্বটা পাঠায় না। আপনার প্রোভাইডার এটা মানে কিনা, ডকুমেন্টেশনে নিশ্চিত হয়ে নিন।
Surrogate-Control
পুরনো, কোনো RFC-তে মানসম্মত নয়, কিন্তু কিছু CDN আর reverse proxy মানে। ২০২৬ সালের অক্টোবর অনুযায়ী Fastly-র ডকুমেন্টেশন বলছে Surrogate-Control-এর ডিরেক্টিভ Cache-Control: max-age-এর চেয়ে অগ্রাধিকার পায়, আর Fastly ইউজারের কাছে পাঠানোর আগে হেডারটা মুছে ফেলে:
Cache-Control: max-age=60
Surrogate-Control: max-age=86400 নিজের CDN যে হেডার মানে, সেটাই ব্যবহার করুন; একসাথে অনেকগুলো দিয়ে “যেটা কাজে লাগে” ভাবলে পরে ডিবাগ করা কঠিন হয়।
Set-Cookie-র ফাঁদ
origin-এর রেসপন্সে Set-Cookie থাকলে সেটা প্রায় নিশ্চিতভাবে কোনো একজন ইউজারের জন্য (সেশন আইডি, CSRF টোকেন)। সেটা ক্যাশ হলে পরের সবাই ওই ইউজারের কুকি পাবে: অন্যের সেশনে লগইন হয়ে যাওয়ার মতো ভয়ংকর বাগ। তাই অনেক CDN নিরাপত্তার জন্য ডিফল্টে Set-Cookie থাকা রেসপন্স ক্যাশ করে না, Cache-Control যাই বলুক:
- ২০২৬ সালের অক্টোবর অনুযায়ী Fastly-র ডকুমেন্টেশন:
Set-Cookieথাকলে রেসপন্স ডিফল্টে ক্যাশ হয় না;Cache-Control: privateথাকলেও না। - একই সময়ে Cloudflare-এর ডকুমেন্টেশন: Free, Pro, Business প্ল্যানে ডিফল্ট সেটিংয়ে
Set-Cookieথাকা রেসপন্স ক্যাশ হয় না।
ঠিক কোন প্ল্যানে বা কোন সেটিংয়ে কী হয়, তা বদলায়; প্রোভাইডারের ডকুমেন্টেশনে মিলিয়ে নিন।
এর উল্টো দিকটাই বেশিরভাগ “CDN কাজ করছে না” অভিযোগের কারণ: আপনার ফ্রেমওয়ার্ক বা অ্যানালিটিক্স প্লাগইন প্রতিটা রেসপন্সে একটা কুকি সেট করে, এমনকি স্ট্যাটিক পেজেও, আর CDN তাই কিছুই ক্যাশ করে না। সমাধান: স্ট্যাটিক আর পাবলিক রেসপন্স থেকে Set-Cookie সরান, কুকি শুধু সেই পাথে সেট করুন যেখানে দরকার।
$ curl -sI https://baytalhikma.org/about | grep -iE 'set-cookie|cache-control|x-cache'
cache-control: public, max-age=600
set-cookie: session=8f3c...; Path=/; HttpOnly
x-cache: MISS (উদাহরণ।) max-age=600 আছে, তবুও প্রতিবার MISS। অপরাধী set-cookie।
রেসিপি: কোন কন্টেন্টে কী হেডার
| কন্টেন্ট | হেডার | কেন |
|---|---|---|
ফাইলনেমে hash সহ অ্যাসেট (app.3f9a1c.js, logo.8b2e.svg) | Cache-Control: public, max-age=31536000, immutable | কন্টেন্ট বদলালে URL বদলায়, তাই পুরনো URL চিরকাল সঠিক |
hash ছাড়া অ্যাসেট (/favicon.ico, /og.png) | Cache-Control: max-age=86400, stale-while-revalidate=86400 | বদলাতে পারে; এক দিন ঠিক আছে, purge দিয়ে দ্রুত বদলানো যায় |
| পাবলিক HTML, ঘনঘন ডিপ্লয় | Cache-Control: max-age=0, s-maxage=300, stale-while-revalidate=60 বা CDN-Cache-Control দিয়ে CDN TTL | ব্রাউজার প্রতিবার যাচাই করে; CDN কয়েক মিনিট রাখে, ডিপ্লয়ে purge |
| পাবলিক HTML, প্রতিটা ডিপ্লয় সাথে সাথে দেখাতে হবে | Cache-Control: no-cache + ETag | প্রতিবার ছোট 304; সবসময় নতুন |
| পাবলিক API (বাজারদর, ক্যাটালগ) | Cache-Control: public, max-age=30, stale-while-revalidate=30, stale-if-error=600 | কয়েক সেকেন্ডের পুরনো ডেটা ঠিক আছে; origin ডাউন হলেও চলে |
| লগইন করা ইউজারের পেজ বা API | Cache-Control: private, no-cache | ব্রাউজার রাখতে পারে, CDN কখনো না |
| সংবেদনশীল ডেটা (টোকেন, পেমেন্ট, ব্যক্তিগত তথ্য) | Cache-Control: no-store | কোথাও জমা থাকবে না |
এরর পেজ (404, 500) | ছোট TTL, যেমন max-age=60, অথবা no-store | ভুল করে ক্যাশ হওয়া এরর দীর্ঘ সময় সাইট ভাঙা দেখায় |
hash করা ফাইলনেম হলো CDN ক্যাশিংয়ের সবচেয়ে বড় কৌশল: বছরব্যাপী TTL, কোনো purge লাগে না, আর ডিপ্লয়ের সাথে সাথেই নতুন ফাইল, কারণ নতুন HTML নতুন URL-এর দিকে নির্দেশ করে। প্রায় সব আধুনিক বিল্ড টুল (Vite, webpack, esbuild) এটা নিজে থেকেই করে।
হাতে-কলমে: Cache-Control প্লেগ্রাউন্ড
নিচের প্লেগ্রাউন্ডে ডিরেক্টিভগুলো বদলে দেখুন ব্রাউজার আর CDN প্রতিটা কম্বিনেশনে কী করে। (প্লেগ্রাউন্ডের লেখা ইংরেজিতে।) চেষ্টা করুন: max-age=60 আর s-maxage=86400 একসাথে দিলে কী হয়; private যোগ করলে CDN-এর কলাম কীভাবে বদলায়; no-cache আর no-store-এর পার্থক্য।
Request flow visualizer
Click Send Request to simulate a user request hitting the CDN. The first request goes all the way to origin (MISS). Subsequent ones are served from the edge PoP (HIT). Hit Purge to evict the cache and watch the next request go to origin again.
Cache-Control header builder
Toggle directives and watch the header string build live. The interpretation panel explains exactly what the resulting header tells every cache in the chain.
Mini CDN simulator
Three independent PoP caches. Send requests to any region. Each PoP maintains its own cache state. Watch hit rates build up, TTLs count down, and see how purge affects all PoPs.
Full CDN stack: all layers
Pick a scenario and watch the request travel through every layer. Each layer lights up as the request touches it. The packet stops where it gets served or blocked.
nginx phase pipeline
Every HTTP request passes through nginx's phases left to right. Pick a scenario and the request ball moves through each phase, stopping where it gets blocked or short-circuited. Green = pass through, yellow = modified, red = blocked.
Token bucket rate limiter
The bucket holds up to 20 tokens. Tokens refill at 3/sec. Each request costs 1. When the bucket empties, requests are rejected with 429. Watch what happens when you burst-fire requests vs space them out.
Cache object lifecycle
A single cached object moves through these states over its lifetime. The TTL bar counts down in real time. Click Step to advance manually or Auto to run the full lifecycle. Purge at any point to see immediate eviction.
হেডার যাচাই করার অভ্যাস
প্রতিটা নতুন রুট বা ডিপ্লয়ের পর তিনটা জিনিস দেখুন:
URL=https://baytalhikma.org/
# 1. What the origin sends
curl -sI --resolve baytalhikma.org:443:192.0.2.10 "$URL" | grep -iE '^(cache-control|cdn-cache-control|surrogate-control|expires|etag|last-modified|set-cookie|vary):'
# 2. What the edge returns, twice
curl -sI "$URL" | grep -iE '^(cache-control|age|x-cache|cache-status|cf-cache-status):'
curl -sI "$URL" | grep -iE '^(cache-control|age|x-cache|cache-status|cf-cache-status):'
# 3. Whether revalidation works
ETAG=$(curl -sI "$URL" | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r')
curl -so /dev/null -w '%{http_code}\n' -H "If-None-Match: $ETAG" "$URL" শেষ লাইনে 304 আশা করছেন। 200 এলে revalidation কাজ করছে না।
ডিবাগিং: এই চ্যাপ্টারের সমস্যাগুলো
| লক্ষণ | নিশ্চিত করবেন কীভাবে | সমাধান |
|---|---|---|
max-age আছে তবুও সবসময় MISS | curl -sI দিয়ে set-cookie আছে কিনা দেখুন | স্ট্যাটিক আর পাবলিক রেসপন্স থেকে Set-Cookie সরান; কুকি শুধু যেখানে দরকার সেখানে |
| ডিপ্লয়ের পর ইউজাররা পুরনো CSS/JS পাচ্ছে | ফাইলনেমে hash আছে কিনা, আর HTML-এর cache-control দেখুন | hash করা ফাইলনেম ব্যবহার করুন; HTML-এর ব্রাউজার TTL ছোট রাখুন (max-age=0 বা no-cache) |
| CDN purge করলাম, তবু কিছু ইউজার পুরনো দেখছে | ওই ইউজারের রেসপন্সের max-age কত ছিল দেখুন | ব্রাউজারের কপি purge হয় না; max-age শেষ হওয়া পর্যন্ত অপেক্ষা। ভবিষ্যতে ব্রাউজার TTL ছোট, CDN TTL বড় (s-maxage) |
no-cache দিলাম, তবুও ক্যাশে জমা থাকছে | এটা প্রত্যাশিত | no-cache মানে “যাচাই করে দাও”। একেবারে জমা রাখতে না চাইলে no-store |
| হেডার না দেওয়া পেজ কয়েক মিনিট বা ঘণ্টা পুরনো থাকে | origin রেসপন্সে cache-control আছে কিনা দেখুন | heuristic বা CDN ডিফল্ট TTL কাজ করছে। প্রতিটা রেসপন্সে স্পষ্ট Cache-Control দিন |
revalidation-এ সবসময় 200, 304 কখনো না | ওপরের ETag কমান্ড চালান; দুইবার origin-এর ETag মিলিয়ে দেখুন | ETag সার্ভারভেদে আলাদা (inode/mtime থেকে)। কন্টেন্ট-ভিত্তিক ETag ব্যবহার করুন বা Last-Modified-এ ভরসা করুন |
| origin ডাউন হলে সাইট সাথে সাথে এরর দেখায় | রেসপন্সে stale-if-error বা must-revalidate আছে কিনা দেখুন | stale-if-error যোগ করুন, আর নিশ্চিত হোন আপনার CDN এটা মানে; must-revalidate থাকলে সরান |
| ব্রাউজার ঠিক, CDN ভুল TTL মানছে | CDN-Cache-Control, Surrogate-Control, বা CDN ড্যাশবোর্ডের নিয়ম দেখুন | একাধিক উৎস থাকলে CDN কোনটাকে অগ্রাধিকার দেয় তা ডকুমেন্টেশনে মিলিয়ে একটাই উৎস রাখুন |
মূল শিক্ষা
Cache-Controlহলো লেবেল, CDN তা মেনে চলে। প্রতিটা রেসপন্সে স্পষ্ট হেডার দিন; না দিলে ক্যাশ নিজে অনুমান করে।- শেয়ার্ড ক্যাশে TTL-এর ক্রম:
s-maxage→max-age→Expires→ heuristic। no-cacheমানে “যাচাই করে দাও”,no-storeমানে “রেখো না”।privateমানে শুধু ব্রাউজার।- ব্রাউজার TTL ছোট, CDN TTL বড়। CDN-এর কপি purge করা যায়, ব্রাউজারেরটা যায় না।
s-maxageবাCDN-Cache-Controlদিয়ে আলাদা করুন। - ETag আর
304মেয়াদোত্তীর্ণ কপিকে কয়েকশো বাইটে নবায়ন করে; একাধিক origin-এ ETag মেলাতে হয়। stale-while-revalidateআরstale-if-errorইউজারকে অপেক্ষা আর আউটেজ থেকে বাঁচায়, তবে প্রোভাইডারের আচরণ যাচাই করে নিন।Set-Cookieঅনেক CDN-এ ক্যাশিং বন্ধ করে দেয়। স্ট্যাটিক রেসপন্সে অপ্রয়োজনীয় কুকি সবচেয়ে প্রচলিত “CDN কাজ করে না” কারণ।- hash করা ফাইলনেম +
immutableহলো সবচেয়ে কার্যকর ক্যাশিং কৌশল।