Course-এর সূচি

2.4

HTTP Caching: Cache-Control, ETag ও Validation

Freshness, revalidation, Vary, shared বনাম private cache, আর নিরাপদে পুরোনো content দেখানো।

38 মিনিট পড়া + Animation

টিপস: ডটেড-আন্ডারলাইন করা যেকোনো শব্দে ট্যাপ বা Hover করলে তার অর্থ দেখবে।

এই Lesson শেষে তুমি পারবে

  • বোঝাতে, HTTP Caching কেন তোমার হাতে থাকা সবচেয়ে সস্তা Performance আর Scaling-এর হাতিয়ার, আর একটা Fresh Hit, একটা 304 আর একটা পুরো 200-এর দাম আলাদা করে বলতে।
  • Private Cache (Browser) আর Shared Cache (Proxy, Gateway, CDN) আলাদা করতে, আর কোনটা কোন Response রাখতে পারে তা বলতে।
  • max-age, s-maxage, Expires, Age আর আন্দাজের (Heuristic) Freshness দেখে হিসাব করতে, জমা Response-টা এখনো Fresh কি না।
  • ঠিক Cache-Control Directive বাছতে, আর no-cache, no-store, private, public, must-revalidate আর immutable ঠিক কোথায় আলাদা, তা বোঝাতে।
  • ETag (Strong আর Weak) আর Last-Modified দিয়ে আবার যাচাই (Revalidate) করতে, আর একটা 304-এর প্রতিটা Header পড়তে।
  • Vary দিয়ে Cache Key সাজাতে, Vary: User-Agent-এর ফাঁদে না পড়ে; আর stale-while-revalidate ও stale-if-error দিয়ে ইচ্ছে করে পুরনো (Stale) Content দেখাতে।
  • Hash-ওয়ালা File-এর নাম দিয়ে এমন Deploy করতে, যাতে কোনো ব্যবহারকারী পুরনো JavaScript-এ আটকে না থাকে।
  • HTML, Static Asset, Public API আর ব্যক্তিগত Data-র জন্য Caching Policy লিখতে, লগইন করা ব্যবহারকারীর Response নিরাপদে Cache করতে, আর API-তে ETag আর If-Match দিয়ে হারানো Update ঠেকাতে।

#এই Lesson কোথায় বসে

Lesson ১.১-এ Cache-Control, ETag আর 304 Status এক লাইনে করে এসেছিল। Lesson ১.৮-এ DNS-এ একই ধারণা দেখেছি: TTL ঠিক করে একটা উত্তর কতক্ষণ আবার ব্যবহার করা যাবে, আর “Propagation” আসলে Cache-এর মেয়াদ ফুরোনো। Lesson ১.১২ আর Lesson ১.১৩-এ দেখেছি Proxy আর Nginx কীভাবে Response Cache করে, আর Fingerprint-ওয়ালা File-এ public, immutable Header। কোন Method Cache করা যায় তা আছে Lesson ২.২-এ, আর Header ও Compression কীভাবে কাজ করে তা Lesson ২.৩-এ।

এই Lesson-এ পুরো Protocol-টা: তোমার Browser থেকে CDN পর্যন্ত যেকোনো HTTP Cache কীভাবে ঠিক করে একটা Response জমা রাখা যাবে কি না, কতক্ষণ আবার ব্যবহার করা যাবে, সস্তায় কীভাবে আবার যাচাই করবে, আর Header কীভাবে দিলে ব্যবহারকারী গতি পাবে অথচ কখনো ভুল Data দেখবে না। CDN-এর ভেতরটা কেমন, Redis দিয়ে Cache-aside, Eviction Policy আর Stampede, এগুলো Chapter 23 আর 24-এর বিষয়; এখানে আমরা থাকব HTTP নিজে যা বলে, তার মধ্যে।

#HTTP Caching কেন

HTTP Caching মানে একটা Response-এর কপি ব্যবহারকারীর কাছাকাছি রেখে দেওয়া, Browser-এ বা কোনো Proxy বা CDN-এ, আর পরের Request-এ Origin Server-কে আবার না জিজ্ঞেস করে সেই কপিটাই ব্যবহার করা। তিন রকম ফল হতে পারে, আর তিনটার দাম একদম আলাদা:

ফলকী ঘটেNetwork-এর খরচOrigin-এর কাজ
Fresh Hitজমা কপি এখনো Fresh, তাই সরাসরি ব্যবহারকিছুই না: DNS নেই, Connection নেই, ০ Byteকিছুই না; Origin জানতেও পারে না
আবার যাচাই (304)কপি Stale, তাই Cache জিজ্ঞেস করে “বদলেছে কি?” আর উত্তর আসে, নাএক Round Trip, কয়েকশো Byteসস্তা একটা যাচাই
পুরো আনা (200)কপি নেই, বা Resource বদলে গেছেএক বা একাধিক Round Trip আর পুরো Bodyপুরো কাজ

ঢাকার একটা 4G ফোন থেকে Singapore-এর Server পর্যন্ত প্রতিটা Round Trip-এ ধরো ১৫০ ms লাগে। একটা Page-এর ৫০টা File-এর ৪০টা যদি Cache থেকে আসে, তাহলে ওই ৪০টা Request আর হয়ই না। ব্যবসার দিক থেকে লাভ দুই দিকে: ব্যবহারকারী দ্রুত Page পায়, আর প্রতিটা Hit এমন একটা Request, যা তোমার Server বা Bandwidth-এর বিলে কখনো ওঠে না। CDN যদি ৯৫% Request-এর উত্তর দেয়, Origin সামলায় মাত্র ৫%; Hit Ratio ৯০% থেকে ৯৫% করলে Origin-এর চাপ অর্ধেক হয়ে যায়।

প্রতিটা Cache যে সিদ্ধান্ত নেয়, নোটে সেটাই আঁকা আছে:

                  [ Browser / client cache ]
                              |
                      (Is the copy fresh?)
                       /              \
                    (Yes)             (No: stale)
                     /                    \
       [ Use it: memory / disk ]   [ Conditional request ]
                                   (If-None-Match: ETag, or
                                    If-Modified-Since: date)
                                            |
                                   (Server validation)
                                    /               \
                       (304 Not Modified)      (200 OK + new body)
                       reuse the stored body   replace the copy

Cache-এ রাখা একটা File-এর জীবন: জমা, Fresh, Stale, আবার যাচাই

একটা Logo, একটা Browser, একটা Origin। প্রথম ভিজিট, Network ছাড়াই আবার ব্যবহার, মেয়াদ ফুরোনো আর সস্তা 304 যাচাই, আর যখন File সত্যিই বদলেছে বা Server কোনো নিয়মই দেয়নি, সব ধাপে ধাপে দেখো।

পরিস্থিতি

এখনো কিছু জমা নেই, তাই পুরো File আসে, আর Response বলে দেয় কতক্ষণ আবার ব্যবহার করা যাবে।

Page/logo.png চায়
Browser CachePrivate Cache
Originwww.example.com
GET /logo.png
ধাপ 1 / 6
1

Page-এর প্রতিটা Request আগে Browser-এর HTTP Cache হয়ে যায়। Cache তার Key খোঁজে: Method আর পুরো URL।

#Cache কোথায় থাকে: Private আর Shared

HTTP Standard (RFC 9111) দুই রকম Cache চেনে, আর প্রায় প্রতিটা নিয়ম নির্ভর করে তুমি কোনটার কথা বলছ তার ওপর।

Cacheধরনকাকে সেবা দেয়সাধারণত কী থাকে
Browser-এর HTTP Cache (Memory আর Disk)Privateএকজন ব্যবহারকারী, একটা Browser ProfileCache করা যায় এমন সব: HTML, CSS, JavaScript, ছবি, Font, API Response
Service Worker CachePrivate, Code দিয়ে চালানোএকজন ব্যবহারকারী, একটা SiteSite-এর নিজের Script যা ঠিক করে (Offline App)
অফিস বা ISP-এর Forward ProxySharedপেছনের সবাইআজকাল সামান্যই: TLS ভেঙে না দেখলে HTTPS-এর ভেতরে দেখতে পারে না
Reverse Proxy / API Gateway (Nginx, Varnish)Sharedতোমার Site-এর সব ব্যবহারকারীApp Server-এর সামনে Public Page আর API Response
CDN Edge (Cloudflare, CloudFront, Fastly, Akamai)Sharedওই Edge-এর কাছের সবাইStatic Asset, Public Page, Video-র টুকরো, Public API Response

Private Cache একজন মানুষের জন্য বানানো Response রাখতে পারে, যেমন তোমার Profile Page। Shared Cache কখনোই তা পারবে না, কারণ তার পরের জন তোমার Page পেয়ে যাবে। এই একটা নিয়মই ব্যাখ্যা করে private, public, s-maxage আর Caching-এর বেশিরভাগ নিরাপত্তা-Bug।

Browser-এর Cache নিয়ে আরও দুটো কথা:

  • এটা Memory আর Disk দুটোই ব্যবহার করে। ছোট, সদ্য ব্যবহার করা Response চলতি Session-এর জন্য Memory-তে থাকে; DevTools এগুলোকে লেখে “(memory cache)” বা “(disk cache)”। নোট বলে File থাকে “ডিভাইসের Disk-এ”, সেটা অর্ধেক সত্যি।
  • এটা ভাগ করা (Partitioned)। ২০২০–২০২১ থেকে Chrome, Firefox আর Safari Cache-এর Key-তে URL-এর সাথে Top-level Site-ও রাখে। ফলে a.example-এ গিয়ে Cache হওয়া File b.example-এ আবার ব্যবহার হয় না। এতে কোনো Site Cache-এর সময় মেপে তোমার History আন্দাজ করতে পারে না; আর “Public CDN থেকে jQuery তো সবার Cache-এ আছেই”, এই কথাটা আর সত্যি না।

#কী কী Cache করা যায়

  • Method: বাস্তবে শুধু GET আর HEAD-এর Response Cache হয়। POST-এর Response আবার ব্যবহার করা যায় শুধু স্পষ্ট বলে দিলে, আর প্রায় কখনোই Cache হয় না (Safe আর Cacheable Method নিয়ে Lesson ২.২)।
  • Status Code: স্পষ্ট Freshness থাকলে যেকোনো Response রাখা যায়। না থাকলে রাখা যায় শুধু সেই Code-গুলো, যাদের Standard বলে আন্দাজে Cache করা যায় (Heuristically Cacheable): 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 আর 501। তাই কোনো Header ছাড়া একটা 404-ও Cache হতে পারে, আর Browser 301 খুব শক্ত করে মনে রাখে।
  • ডিফল্ট Key হলো Method আর পুরো URL (Scheme, Host, Path আর Query String)। শুধু একটা Tracking Parameter-এ আলাদা দুটো URL মানে দুটো আলাদা Entry।

#Freshness: একটা কপি কতক্ষণ আবার ব্যবহার করা যায়

জমা Response Fresh থাকে যতক্ষণ তার বয়স (Age) তার Freshness-এর মেয়াদের চেয়ে কম, তারপর সেটা Stale। Fresh কপি কাউকে না জিজ্ঞেস করেই ব্যবহার হয়; Stale কপি সাধারণত আগে আবার যাচাই হয়। Stale মানে ভুলও না, মুছে ফেলাও না: Cache সেটা রেখে দেয়, কারণ একটা সস্তা 304 তাকে আবার কাজের করে তুলতে পারে।

#max-age আর Expires

Cache-Control: max-age=3600
Expires: Mon, 02 Nov 2026 12:00:00 GMT
  • max-age=N মেয়াদ দেয় সেকেন্ডে, Response তৈরির মুহূর্ত থেকে গুনে। max-age=3600 মানে এক ঘণ্টা; max-age=31536000 (নোটের উদাহরণ) মানে এক বছর, প্রচলিত “চিরকাল”।
  • Expires দেয় একটা নির্দিষ্ট তারিখ আর সময়, সবসময় GMT-তে, আর বারের নামও ঠিক হতে হয়। এটা HTTP/1.0-র পদ্ধতি, আর Server আর Client-এর ঘড়ি মিলতে হয়। Expires: 0-এর মতো অচল মান মানে “মেয়াদ আগেই শেষ”।
  • দুটোই থাকলে সবসময় max-age জেতে, সব Cache-এ। (নোট বলে “সাধারণত”; Standard বলে সবসময়।)

দুটোই Freshness বোঝায়, কপি কতদিন রাখা হবে তা না। জায়গা লাগলে Cache Fresh Entry-ও আগে মুছে দিতে পারে, আবার Stale Entry অনেকদিন রেখে দিতে পারে, পরে আবার যাচাইয়ের আশায়।

#s-maxage: Shared Cache-এর আলাদা মেয়াদ

s-maxage=N খাটে শুধু Shared Cache-এ, আর সেখানে max-age (আর Expires)-কে ছাপিয়ে যায়। Browser এটা উপেক্ষা করে। চেনা ধরনটা হলো Browser-এ ছোট মেয়াদ, CDN-এ লম্বা:

Cache-Control: max-age=60, s-maxage=600

কেন? কিছু বদলালে CDN কয়েক সেকেন্ডে Purge করা যায়, কিন্তু ব্যবহারকারীর Browser-এ তুমি কখনো হাত দিতে পারবে না। তাই যে কপি মুছতে পারো, লম্বা মেয়াদ তারই। অনেক CDN এর জন্য নিজস্ব Header-ও নেয়, যেমন CDN-Cache-Control (২০২২ থেকে Standard) বা Surrogate-Control, যেগুলো তারা পড়ে আর সরিয়ে দেয়; Chapter 24-এ এগুলো আসবে।

#Age: অন্য Cache-এ আগেই কাটানো সময়

Shared Cache জমা Response দেওয়ার সময় একটা Age Header যোগ করে: Origin Response তৈরি বা শেষবার যাচাই করার পর কত সেকেন্ড গেছে। পরের Cache সেই সময়টাও গোনে:

বর্তমান বয়স = এই Cache-এ কপিটা যতক্ষণ আছে + যে Age নিয়ে এসেছিল

তাই max-age=60-ওয়ালা একটা Response CDN থেকে Age: 40 নিয়ে এলে Browser-এ তার Freshness বাকি থাকে মাত্র ২০ সেকেন্ড। Age: 300 নিয়ে এলে পৌঁছানোর সময়েই সেটা Stale, আর Browser প্রতিবার ব্যবহারের আগে যাচাই করবে: ছোট max-age আর লম্বা s-maxage একসাথে দেওয়ার এক অবাক করা খরচ।

#আন্দাজের Freshness: Server যখন কিছুই বলে না

Response-এ max-age, s-maxage বা Expires না থাকলে, কিন্তু Last-Modified তারিখ থাকলে, Standard Cache-কে একটা মেয়াদ আন্দাজ করার অনুমতি দেয়। প্রচলিত নিয়ম হলো শেষ পরিবর্তনের পর থেকে যত সময় গেছে তার ১০%: ১০ দিন আগে শেষ বদলানো File প্রায় এক দিনের জন্য Fresh ধরা হয়। এভাবেই একটা ভুলে যাওয়া Header হয়ে যায় “Fix-এর এক দিন পরেও ব্যবহারকারী পুরনো File দেখছে”। সবসময় স্পষ্ট একটা Cache-Control পাঠাও, শুধু no-cache হলেও।

#Cache-Control Directive

Cache-Control হলো কমা দিয়ে আলাদা করা Directive-এর তালিকা, বেশিরভাগ Response-এ পাঠানো হয়:

Directiveমানেসাধারণ ব্যবহার
max-age=Nযেকোনো Cache-এ N সেকেন্ড FreshCache করা যায় এমন বেশিরভাগ Response
s-maxage=NShared Cache-এ N সেকেন্ড Fresh; সেখানে max-age-কে ছাপিয়ে যায়Browser-এর চেয়ে লম্বা CDN মেয়াদ
no-cacheরাখা যাবে, কিন্তু প্রতিবার ব্যবহারের আগে Origin-এর সাথে যাচাই করতে হবেHTML Page, যে Data সবসময় হালনাগাদ থাকতে হবে
no-storePrivate বা Shared, কোনো Cache-এ রাখা যাবে নাBank Statement, এককালীন Token, স্বাস্থ্যের রেকর্ড
privateশুধু Private Cache (Browser) রাখতে পারবেএকজন ব্যবহারকারীর Page আর API Response
publicএমন অবস্থাতেও Shared Cache রাখতে পারবে, যেখানে সাধারণত পারে না (Authorization-ওয়ালা Request, স্পষ্ট মেয়াদ নেই)কদাচিৎ লাগে; নিচে দেখো
must-revalidateStale হলে সফল যাচাই ছাড়া কখনো ব্যবহার নয়; Origin-এ পৌঁছানো না গেলে Error (504)যেখানে Stale-এর চেয়ে Error ভালো
proxy-revalidatemust-revalidate-এর মতোই, শুধু Shared Cache-এর জন্যবিরল
immutableFresh থাকা অবস্থায় এই Body কখনো বদলাবে না, তাই Reload-এও যাচাই কোরো নাHash-ওয়ালা Static File
stale-while-revalidate=Nমেয়াদ শেষে আরও N সেকেন্ড Stale কপি দেওয়া যাবে, পেছনে Refresh চলতে চলতেযেখানে কয়েক সেকেন্ড পুরনো হলে সমস্যা নেই
stale-if-error=Nআবার যাচাই ব্যর্থ হলে (5xx বা উত্তরই নেই) N সেকেন্ড Stale কপি দেওয়া যাবেOrigin বিভ্রাটের সময় Site চালু রাখা
no-transformProxy Body বদলাতে পারবে না (ছবি আবার Compress, Minify)যে Media-কে Byte-এ Byte-এ এক থাকতে হবে

Request-এও Cache-Control থাকতে পারে। Hard Reload পুরো Download জোর করাতে Cache-Control: no-cache পাঠায়; max-age=0 আবার যাচাই চায়; only-if-cached চায় জমা কপি, নয়তো কিছুই না। পুরনো Pragma: no-cache Header HTTP/1.0-এর অবশেষ, বর্তমান Standard একে বাতিল করেছে; তবু পুরনোদের সাথে মিল রাখতে Browser এখনো Cache-Control-এর পাশাপাশি এটা পাঠায়।

Cache-Control Directive: Private বনাম Shared Cache

Browser হলো Private Cache (একজন ব্যবহারকারী)। CDN হলো Shared Cache (সবার)। প্রতিটা Directive ঠিক করে কে Response জমা রাখতে পারবে, কতক্ষণ, আর আগে জিজ্ঞেস করতে হবে কি না।

পরিস্থিতি

সব জায়গায় জমা থাকে, কিন্তু প্রতিবার ব্যবহারের আগে Origin-এর সাথে যাচাই করতে হয়।

BrowserPrivate Cacheজমা, যাচাই লাগবে
CDN EdgeShared Cacheজমা, যাচাই লাগবে
Originwww.example.com
200 · Cache-Control: no-cache · ETag: "h7"
ধাপ 1 / 5
1

নামটা বিভ্রান্তিকর: no-cache Cache করা নিষেধ করে না। দুটো Cache-ই Page জমা রাখে, কিন্তু Origin-কে জিজ্ঞেস না করে কেউই আবার ব্যবহার করতে পারে না। যে HTML সবসময় হালনাগাদ থাকতে হবে, তার জন্য এটাই চলে।

নিজেকে যাচাই করোপ্রশ্ন

একটা HTML Page সবসময় হালনাগাদ থাকতে হবে, কিন্তু কিছু না বদলালে আবার Download এড়াতে চাও। কোন Header মানায়?

#Validation: ETag আর Last-Modified

জমা Response Stale হলে Cache সেটা ফেলে দেয় না। সে একটা Conditional Request পাঠায়, যার মানে “আমার কাছে এটা আছে; বদলালে তবেই নতুন Body পাঠাও”। এটা সম্ভব করতে আসল Response-এ একটা Validator থাকতে হয়।

#ETag আর If-None-Match

ETag (Entity Tag) হলো একটা Resource-এর একটা Version-এর জন্য একটা অস্বচ্ছ (Opaque) নাম:

HTTP/1.1 200 OK
Cache-Control: max-age=3600
ETag: "hash_v123"

পরে Cache জিজ্ঞেস করে:

GET /logo.png HTTP/1.1
If-None-Match: "hash_v123"

বর্তমান ETag এখনো "hash_v123" হলে Server উত্তর দেয় 304 Not Modified, Header সহ কিন্তু Body ছাড়া। না হলে নতুন Body আর নতুন ETag সহ 200 OK। যেটাই হোক, খরচ মাত্র এক Round Trip।

ভালো ETag কেমন হয়:

  • এমন যেকোনো String, যা Body বদলালেই বদলায়। নোট যেমন বলে, Content-এর Hash খুব চলে, কিন্তু Database Row-এর Version বা Update-এর গুনতি ("v42") একই রকম কাজ করে, আর বড় Response Hash করার চেয়ে সস্তা।
  • প্রতিটা Server-এ একই। Load Balancer-এর পেছনে তিনটা Server-কে একই File-এর জন্য একই ETag বানাতে হবে। পুরনো Apache-র ডিফল্ট File-এর Inode নম্বর মিশিয়ে দিত, যা প্রতিটা মেশিনে আলাদা; ফলে অন্য Server-এ পড়া প্রতিটা Request 304-এর বদলে পুরো 200 পেত।
  • Strong না Weak। সাধারণ "abc" হলো Strong Validator: Byte-এ Byte-এ এক। W/"abc" হলো Weak: “অর্থে এক”, যেমন একই Page, শুধু একটা Comment-এ সময় আলাদা। If-None-Match Weak তুলনা করে, তাই Caching-এ দুটোই চলে; কিন্তু Range Request আর If-Match-এর জন্য Strong ETag লাগে। Nginx চলতে চলতে Compress করলে Strong ETag-কে Weak বানিয়ে দেয়, কারণ Byte-গুলো আর এক থাকে না।

#Last-Modified আর If-Modified-Since

পুরনো, সময়ভিত্তিক Validator:

Last-Modified: Wed, 21 Oct 2026 07:28:00 GMT
...
If-Modified-Since: Wed, 21 Oct 2026 07:28:00 GMT

ওই মুহূর্তের পর Resource না বদলালে Server 304 দেয়। এর জন্য বাড়তি কিছু রাখতে হয় না, কিন্তু এর মাপ এক সেকেন্ড পর্যন্ত: একই সেকেন্ডে দুবার বদলানো, Deploy-এ File-এর পুরনো সময় ফিরে আসা, বা Server-গুলোতে File-এর সময় আলাদা হওয়া, সবকটাতেই এটা ভুল বলে। Request-এ If-None-Match আর If-Modified-Since দুটোই থাকলে Server-কে ETag-টাই দেখতে হবে। ETag পাঠাও; Last-Modified রাখো বিকল্প হিসেবে (আন্দাজের Freshness-এও এটা লাগে, আর Crawler-দের কাজে আসে)।

#একটা 304 আসলে কী বলে

304 মানে “তোমার কপি Fresh ছিল” না: Fresh কপি তো যাচাই-ই হতো না। মানে হলো “তোমার Stale কপিটা এখনো ঠিক আছে”। 304-এ Body নেই, কিন্তু Header থাকে (ETag, Cache-Control, Expires, Vary, Date), আর Cache সেগুলো দিয়ে জমা Header আপডেট করে আর Freshness-এর ঘড়ি নতুন করে চালু করে। এভাবেই File আবার না পাঠিয়েও Cache-এ থাকা File-এর মেয়াদ বদলানো যায়।

সব Conditional Header এক জায়গায়:

Request Headerকার সাথে জোড়াকী কাজেশর্ত ব্যর্থ হলে
If-None-Match: "e"ETagCache আবার যাচাই (GET)304 Not Modified
If-Modified-Since: dateLast-ModifiedCache আবার যাচাই (GET)304 Not Modified
If-Match: "e"ETag (Strong)নিরাপদ Update (PUT, PATCH, DELETE)412 Precondition Failed
If-Unmodified-Since: dateLast-Modifiedনিরাপদ Update, পুরনো ধরন412 Precondition Failed
If-Range: "e"ETag বা তারিখFile না বদলালে তবেই অর্ধেক Download আবার শুরু206 Range-এর বদলে পুরো 200
If-None-Match: *—কিছু না থাকলে তবেই তৈরি (PUT)412 Precondition Failed

#Cache Key আর Vary

Cache প্রতিটা Response একটা Key-এর নিচে রাখে, ডিফল্টে Method আর পুরো URL। কিন্তু এক URL-এর একাধিক সঠিক উত্তর থাকতে পারে: Brotli-তে Compress করা আর না-করা app.js, ইংরেজি আর বাংলা Page। Response-এর Vary Header বলে দেয়, জমা Response আবার ব্যবহারের আগে Request-এর আর কোন কোন Header মিলতে হবে:

Vary: Accept-Encoding

এবার Cache প্রতিটা আলাদা Accept-Encoding মানের জন্য একটা করে Entry রাখে, আর প্রত্যেক Client-কে সেই Body দেয়, যা সে পড়তে পারে।

  • Vary: Accept-Encoding প্রায় প্রতিটা Compressed Response-এর লাগে (gzip, Brotli আর zstd নিয়ে Lesson ২.৩)। এটা ভুলে গেলে Shared Cache এমন Client-কেও Brotli Byte দিয়ে দেয়, যে কখনো চায়ইনি। CDN এই Header-কে অল্প কয়েকটা মানে নামিয়ে আনে, যাতে অনেক Variant না হয়।
  • Vary: User-Agent একটা ফাঁদ। হাজার হাজার আলাদা User-Agent String আছে, তাই Cache হাজারটা ছোট্ট Entry-তে ভেঙে যায় আর Hit Ratio ধসে পড়ে। ফোনের জন্য আলাদা HTML দিতেই হলে CDN-কে User-Agent থেকে Device-এর ধরন বের করতে দাও (বা Sec-CH-UA-Mobile Client Hint ব্যবহার করো) আর তার ওপর Vary দাও; আরও ভালো, একটাই Responsive Page বানাও।
  • Vary: Cookie Cache-কে প্রতিটা Cookie-মানের জন্য একটা কপি রাখায়, যা লগইন করা ব্যবহারকারীদের ক্ষেত্রে মানে প্রত্যেকের জন্য একটা কপি: সঠিক, কিন্তু Shared Cache-এ প্রায় অকেজো। এমন Response-কে বরং private দাও।
  • Vary: Accept-Language ঠিক আছে, যদি CDN একে তোমার সমর্থিত ভাষাগুলোতে নামিয়ে আনে; না হলে প্রতিটা Browser-এর একটু আলাদা ভাষার তালিকা নতুন একটা Variant।
  • Vary: * মানে “কখনো আবার ব্যবহার কোরো না”, no-store লেখার একটা আনাড়ি উপায়।

বেশিরভাগ CDN সরাসরি Key সাজাতেও দেয়: Tracking Parameter (utm_*, fbclid) বাদ দেওয়া, Query String সাজানো, বা কোনো Header বা Cookie Key-তে যোগ করা। এগুলো CDN-এর Configuration, Chapter 24-এ শেখানো হবে; HTTP-র নিজের হাতিয়ার হলো Vary।

Cache Key আর Vary: এক URL, কয়েকটা জমা উত্তর

Cache প্রতিটা Response একটা Key-এর নিচে রাখে: ডিফল্টে Method আর URL। Vary বলে দেয় Request-এর আর কোন কোন Header মিলতে হবে। Stage-এর নিচে CDN-এর টেবিলটা খেয়াল করো।

পরিস্থিতি

একই File-এর Compressed আর Uncompressed কপি, ঠিকঠাক আলাদা রাখা।

User Aনতুন Browser
User Bপুরনো Client
CDN EdgeShared Cache
Originwww.example.com
Accept-Encoding: br, gzipGET /app.js
ধাপ 1 / 4
1

User A-র Browser Brotli নিতে পারে। CDN-এর কাছে এখনো কিছু নেই, তাই Request সামনে পাঠায়।

#ইচ্ছে করে Stale দেখানো

দুটো Directive (RFC 5861) Cache-কে জেনেবুঝে Stale কপি ব্যবহার করতে দেয়, একটু Freshness-এর বদলে গতি বা সবসময় চালু থাকা কিনে নিতে:

Cache-Control: max-age=60, stale-while-revalidate=300, stale-if-error=86400
  • stale-while-revalidate=300: কপি Stale হওয়ার পর ৩০০ সেকেন্ড ধরে Cache সাথে সাথে Stale কপি দিয়ে উত্তর দিতে পারে, আর পেছনে সেটা Refresh করে। ব্যবহারকারীকে কখনো Origin-এর Render-এর সময় অপেক্ষা করতে হয় না; দামটা Cache একবার দেয়। Chromium Browser আর Firefox এটা মানে, বড় CDN-গুলোও।
  • stale-if-error=86400: আবার যাচাই ব্যর্থ হলে (500, 502, 503 বা 504, বা উত্তরই নেই) Cache এক দিন পর্যন্ত Stale কপি দিয়ে যেতে পারে। Database বন্ধ থাকলেও তোমার Home Page চালু থাকে। Cloudflare, Fastly, CloudFront-এর মতো CDN এটা মানে; বেশিরভাগ Browser মানে না, তাই এটাকে Shared Cache-এর হাতিয়ার ধরো।
  • must-revalidate উল্টোটা করে: Origin-এ পৌঁছানো না গেলেও Stale দেখাতে নিষেধ করে। এটা দাও যেখানে ভুল উত্তর Error-এর চেয়েও খারাপ (যে Stock-এর হিসাব দেখে Payment নেওয়া হয়, যে Feature Flag একটা বিপজ্জনক পথ বন্ধ রাখে)।

এমন Response-এর Age Header জানায় সেটা আসলে কত পুরনো। stale-while-revalidate Cache Stampede-কেও নরম করে, যেখানে একটা মেয়াদোত্তীর্ণ Key-এর জন্য অনেক Request একসাথে Origin-এ আছড়ে পড়ে; Lock আর Request Coalescing সহ পুরো আলোচনা Chapter 23-এ।

ইচ্ছে করে Stale দেখানো: stale-while-revalidate আর stale-if-error

Response: Cache-Control: max-age=60, stale-while-revalidate=300, stale-if-error=86400। ১ মিনিট Fresh, তারপর আরও ৫ মিনিট Refresh চলাকালীন ব্যবহারযোগ্য, আর Origin বিপদে থাকলে এক দিন পর্যন্ত ব্যবহারযোগ্য।

পরিস্থিতি

৯০ সেকেন্ড বয়সে কপিটা Stale, কিন্তু ৩০০ সেকেন্ডের জানালার ভেতরে: ব্যবহারকারী সাথে সাথে পায়, Refresh হয় পেছনে।

UserBrowser
CDN EdgeShared Cachehome "v1", বয়স ৯০ সেকেন্ড
Origin২ সেকেন্ডে Render
GET /home
ধাপ 1 / 5
1

Cache-এ থাকা Home Page-এর বয়স ৯০ সেকেন্ড: Stale, কারণ max-age ৬০।

#Hash-ওয়ালা File-এর নাম দিয়ে Cache Busting

লম্বা মেয়াদ আর ঠিকঠাক Update যেন একে অপরের শত্রু: File এক বছর Cache করলে ব্যবহারকারী নতুন Version পায় না; এক মিনিট করলে লাভের বেশিরভাগই হারাও। উত্তর হলো, একই URL-এর নিচে কখনো File বদলাবে না:

১. Build Tool প্রতিটা Static File-এর নাম দেয় তার Content-এর Hash দিয়ে: app.a83f92.js, style.72bc1.css। এক লাইন বদলালেই নাম বদলে যায়।

২. এই File-গুলো যায় Cache-Control: public, max-age=31536000, immutable দিয়ে: এক বছর Cache, কখনো আবার যাচাই নয়, কারণ একটা নামের Content কখনো আলাদা হতে পারে না।

৩. HTML স্বল্পমেয়াদি: no-cache (ETag সহ), বা CDN-এ কয়েক সেকেন্ড থেকে কয়েক মিনিট, Deploy-এ Purge সহ। বদলায় শুধু HTML, আর সেটাই নতুন File-এর নাম দেখিয়ে দেয়।

৪. Deploy-এর পর Browser HTML আবার যাচাই করে, নতুন নাম দেখে, আর শুধু সত্যিই বদলানো File-গুলো আনে। না-বদলানো File নাম রাখে, Cache-এই থাকে।

৫. Deploy-এর পরও কয়েক দিন পুরনো File রেখে দাও। যাদের Tab খোলা, আর কোথাও এখনো Cache-এ থাকা HTML, তারা এখনো সেগুলো চায়।

/app.js-এর মতো স্থির নাম আর লম্বা মেয়াদ হলে Deploy-এর পর ফিরে আসা ব্যবহারকারীর Browser-এ নতুন HTML চলে পুরনো JavaScript-এর সাথে, আর তাদের কপির মেয়াদ না ফুরোনো পর্যন্ত Server থেকে তুমি কিছুই করতে পারবে না। Query String-এ Version (app.js?v=42) Browser-এ চলে, কিন্তু কিছু Proxy Query String-ওয়ালা URL Cache করে না, আর নম্বর বাড়াতে ভুলে যাওয়াও সহজ; File-এর নামে Content Hash-ই মজবুত পথ। immutable মূলত Firefox আর Safari-তে কাজে লাগে: ২০১৭ থেকে Chrome সাধারণ Reload-এ আর Subresource যাচাই করে না, তাই সে Directive-টা উপেক্ষা করে।

Cache-এ থাকা Asset নিয়ে Deploy: File-এর নামে Hash কেন

Deploy-এর আগে Browser-এর কাছে গতকালের File। Deploy HTML আর JavaScript একসাথে বদলায়। একটা স্থির নাম আর Content-এর Hash-ওয়ালা নাম তুলনা করো।

পরিস্থিতি

/app.js-এ max-age=86400 (এক দিন), আর HTML-এ no-cache।

Browserপুরনো File আছেindex.html (পুরনো), app.js (পুরনো, ১ দিন Fresh)
CDN Edge
Originনতুন Release
Deploy: নতুন HTML renderCart() ডাকে
ধাপ 1 / 5
1

Release HTML-এ একটা Cart বোতাম যোগ করে, আর /app.js-এ renderCart Function।

#কী Cache করবে: প্রতিটা ধরনের Response-এর জন্য Policy

একটাই সঠিক Header বলে কিছু নেই; প্রতিটা ধরনের Response-এর জন্য একটা সঠিক Header আছে। ভালো একটা শুরুর সেট:

ResponseCache-Controlকেন
Hash-ওয়ালা Static File (JS, CSS, Font, Hash-ওয়ালা ছবি)public, max-age=31536000, immutableContent বদলালে নামও বদলায়
স্থির URL-এর Static File (/favicon.ico, /logo.png, /robots.txt)max-age=3600 থেকে এক দিন, ETag সহনাম বদলানো যায় না, তাই মেয়াদ মাঝারি রাখো
Public HTML PageETag সহ no-cache; CDN-এ s-maxage=60, stale-while-revalidate=300, সাথে Deploy-এ Purgeনতুন Asset-এর নাম দ্রুত ধরতে হবে
Public API পড়া (Product List, মুদ্রার দর)ETag সহ max-age=30, s-maxage=60, stale-while-revalidate=30সবার জন্য এক; কয়েক সেকেন্ড পুরনো হলে সমস্যা নেই
ব্যক্তিগত Page আর API Response (/account, /api/me)ETag সহ private, no-cache (বা private, max-age=60)Browser আবার ব্যবহার করতে পারে; কোনো Shared Cache রাখতে পারবে না
স্পর্শকাতর Data (Bank Statement, Token, চিকিৎসার রেকর্ড)no-storeDisk-এ বা কোনো Cache-এ কিছুই থাকবে না
লেখা (POST, PUT, PATCH, DELETE)সাধারণত কিছু না, বা no-storeআবার ব্যবহার হয় না; সফল একটা লেখা ওই URL-এর Browser-এ জমা কপিকেও বাতিল করে
Error (404, 5xx)ছোট স্পষ্ট মেয়াদ, যেমন max-age=10, বা no-storeনা হলে 404 আন্দাজে Cache হতে পারে, আর অল্প সময়ের বিভ্রাট Cache-এ জমে যায়
স্থায়ী Redirect (301, 308)পরীক্ষার সময় পরিমিত max-ageBrowser এগুলো শক্ত করে মনে রাখে; ভুল একটা 301 বহুদিন আটকে থাকতে পারে

আগে ভাবো Response কে দেখতে পারবে (এটা ঠিক করে private, public, no-store), তারপর কতটা পুরনো হলে চলে (মেয়াদ আর stale-* ঠিক করে), তারপর কীভাবে Update হবে (স্থির URL আর আবার যাচাই, নাকি Hash-ওয়ালা নাম)।

#লগইন করা আর ব্যক্তিগত Response নিরাপদে Cache করা

সবচেয়ে খারাপ Caching Bug ধীর Page না; ভুল মানুষের কাছে পৌঁছানো Page। নিয়মগুলো:

১. কে চাইছে তার ওপর যে Response নির্ভর করে, সেটা private (বা no-store)। কোনো Page ব্যক্তিগত কি না, CDN আন্দাজ করবে, এই ভরসা কখনো কোরো না।

২. Authorization Header-ওয়ালা Request-এর Response Shared Cache রাখতে পারে না, যদি না Response স্পষ্টভাবে public, s-maxage বা must-revalidate দিয়ে অনুমতি দেয়। ব্যক্তিগত Response-এ এগুলো যোগ কোরো না।

৩. Set-Cookie-ওয়ালা Response কখনো Shared Cache-কে রাখতে দিয়ো না। HTTP Standard এটা নিষেধ করে না, কিন্তু Cache-এ থাকা Response যদি একজনের Session Cookie বয়ে আনে, তাহলে যে-ই সেই কপি পাবে, সে ওই ব্যবহারকারী হিসেবে লগইন হয়ে যাবে। Nginx আর বেশিরভাগ CDN ডিফল্টে এমন Response Cache করে না; “সব Cache করো” নিয়ম আর নিজের বানানো Proxy প্রায়ই করে।

৪. শুধু File-এর Extension দেখে Cache কোরো না। Web Cache Deception CDN-কে ধোঁকা দিয়ে /account/x.css রাখিয়ে নেয়, যা একটা ঢিলেঢালা Router Account Page হিসেবে Render করে।

৫. Page ভাগ করো। Public কাঠামো (Layout, Product-এর Data) CDN-এ Cache করো, আর ব্যক্তিগত অংশ (নাম, Cart-এর সংখ্যা) আনো আলাদা একটা Private API Call দিয়ে। ঝুঁকি ছাড়াই Hit Ratio উঁচু থাকে।

৬. Logout-এর সময় Clear-Site-Data: "cache" Browser-কে ওই Site-এর Cache করা Data মুছে ফেলতে বলে, আর স্পর্শকাতর Page-এ no-store থাকলে শেয়ার করা Computer-এ পরে Back বোতাম সেগুলো দেখাতে পারে না।

যখন Shared Cache একজনের Page জমা রেখে দেয়

সবচেয়ে খারাপ Caching Bug ধীর Page না, ভুল মানুষকে দেখানো Page। রহিম Login করেছে; এক মিনিট পরে করিম একই CDN দিয়ে ঢুকল।

পরিস্থিতি

কেউ CDN-এ একটা নিয়ম দিয়েছে, যা Origin-এর Header না মেনে সব HTML ৫ মিনিট Cache করে।

রহিমLogin করা
করিমআরেকজন
CDN EdgeShared Cache
Origin
GET /account · Cookie: sid=R1
ধাপ 1 / 6
1

রহিম নিজের Account Page খুলল। তার Session Cookie বলে দেয় সে কে।

#Caching-এর পরিচিত Bug

লক্ষণকারণসমাধান
একজন ব্যবহারকারী আরেকজনের নাম বা Account দেখছে“সব Cache করো” নিয়ম বা ভুল একটা public-এর কারণে Shared Cache ব্যক্তিগত Page রেখেছে (প্রায়ই Set-Cookie সহ)ব্যক্তিগত Response-এ private / no-store; Session Cookie আর Set-Cookie থাকলে CDN Cache এড়িয়ে যাবে
কিছু Client পড়া যায় না এমন Script বা Page পাচ্ছেVary: Accept-Encoding ছাড়া Compressed Body Cache হয়েছেCompression দর-কষাকষি করলে সবসময় Vary: Accept-Encoding পাঠাও
Deploy-এর পর ফিরে আসা ব্যবহারকারীর Page ভাঙাস্থির নামের JS বা CSS, লম্বা মেয়াদেHash-ওয়ালা File-এর নাম; স্বল্পমেয়াদি HTML
Deploy-এর ঠিক পরে নতুন ভিজিটর ফাঁকা Page দেখছেপুরনো HTML এখনো Cache-এ, পুরনো Asset মুছে ফেলাপুরনো Asset কয়েক দিন রাখো; CDN-এ HTML Purge করো বা মেয়াদ কমাও
Fix-এর অনেক পরেও পুরনো Version, কেউ মেয়াদ দেয়নিLast-Modified থেকে আন্দাজের Freshnessসব জায়গায় স্পষ্ট Cache-Control
CDN-এর Hit Ratio এক অঙ্কেVary: User-Agent বা Vary: Cookie, বা Key-তে Tracking Parameterগুছানো মানে Vary দাও; অব্যবহৃত Query Parameter বাদ দাও
আবার যাচাই সবসময় 200 দেয়, কখনো 304 নাServer-ভেদে ETag আলাদা, বা App প্রতি Request-এ নতুন ETag বানায়Content বা Version থেকে ETag, প্রতিটা Node-এ এক
সরিয়ে ফেলার পরও ভুল একটা Redirect ব্যবহারকারীদের কাছে থেকে যাচ্ছেBrowser-এ Cache হওয়া 301আগে 302/307 দিয়ে পরীক্ষা, তারপর পরিমিত max-age সহ 301/308
একটা বিভ্রাট Cache থেকে ঘণ্টার পর ঘণ্টা Error Page হয়ে দেখা দিচ্ছেError Response লম্বা মেয়াদে Cache হয়েছেError-এর জন্য ছোট মেয়াদ বা Cache-ই না; ভালো কপি রাখতে stale-if-error

#Caching-এর বাইরেও ETag: If-Match আর হারানো Update

যে ETag 304 সম্ভব করে, সেটাই API-র একটা পুরনো সমস্যাও মেটায়। দুজন একই Record খোলে, দুজনেই Edit করে, দুজনেই Save করে: দ্বিতীয় Save চুপচাপ প্রথমটা মুছে লেখে। এর নাম Lost Update।

Optimistic Concurrency-তে Client খোলার সময়ের ETag-টা ফেরত পাঠায়:

PUT /products/7 HTTP/1.1
If-Match: "7"
Content-Type: application/json

{"price": 120, "title": "Mug"}
  • বর্তমান Version এখনো "7" হলে Server পরিবর্তনটা বসায় আর নতুন ETag ফেরত দেয়।
  • এর মধ্যে অন্য কেউ বদলে থাকলে Server ফেরত দেয় 412 Precondition Failed, কিছুই বদলায় না। Client আবার Load করে, ব্যবহারকারীকে নতুন অবস্থা দেখায়, আবার চেষ্টা করে।
  • যে Server অন্ধভাবে মুছে লেখা নিষেধ করতে চায়, সে If-Match ছাড়া লেখায় দেয় 428 Precondition Required।
  • PUT-এ If-None-Match: * মানে “না থাকলে তবেই তৈরি করো”, একটা নাম দাবি করার বা একটা Object একবারই লেখার নিরাপদ উপায়; Amazon S3 দুই রকম শর্তযুক্ত লেখাই সমর্থন করে।

Server-এ যাচাই আর লেখা একটাই অবিভাজ্য ধাপ হতে হবে, সাধারণত UPDATE … WHERE id = 7 AND version = 7, তারপর দেখা যে ঠিক একটা Row বদলেছে। ব্যবহারকারী যতক্ষণ ভাবছে ততক্ষণ কোনো Lock ধরা থাকে না, এজন্যই একে বলে Optimistic। এই Header দিয়ে API Design চলবে Chapter 26-এ, আর Transaction Isolation Database-এর Chapter-গুলোতে।

Caching-এর বাইরেও ETag: If-Match হারানো Update ঠেকায়

দুজন Admin একই Product Edit করছে। যে ETag 304 সম্ভব করেছিল, সেটাই Server-কে বলে দেয় কোন Version দেখে Edit করা হয়েছে।

পরিস্থিতি

কোনো শর্ত ছাড়া সাধারণ PUT।

Aliceদাম বদলায়
Product API/products/7Version ৭: ১০০, Mug
Bobশিরোনাম বদলায়
200 {price: 100, title: "Mug"} · ETag: "7"
ধাপ 1 / 5
1

Alice Product খুলল: Version ৭।

#Browser-এর অন্য Cache, সংক্ষেপে

  • Back/Forward Cache (bfcache)। তুমি Back চাপলে Browser আগের Page-টা Memory থেকে, তার JavaScript-এর অবস্থাসহ চোখের পলকে ফিরিয়ে আনতে পারে। এটা HTTP Cache থেকে আলাদা। unload Handler ব্যবহার করা বা WebSocket খোলা রাখা Page বাদ পড়তে পারে, আর Cache-Control: no-store দিয়ে পাঠানো Page বহুদিন ধরেই বাদ পড়ত (Chrome এর কিছুকে অনুমতি দেওয়া শুরু করেছে)। Back-এ যাওয়া ধীর লাগলে Chrome DevTools-এর Application অংশে bfcache-এর একটা পরীক্ষা আছে।
  • Service Worker Cache। Site-এর নিজের Script Request মাঝপথে ধরে Cache API থেকে উত্তর দিতে পারে; Offline Web App আর “আগে Network, না পেলে Cache” কৌশল এভাবেই চলে। Script যা বলে এটা তা-ই করে, তাই এর Bug তোমারই Bug।
  • Memory Cache আর Preload। <link rel="preload"> দিয়ে আনা বা চলতি Page-এ আগেই ব্যবহার হওয়া Resource Memory থেকে আবার ব্যবহার হয়, সাধারণ নিয়মে মেয়াদ ফুরোনোর পরেও কিছুক্ষণ।

#System Design-এ HTTP Caching

  • আগে Edge-এ Cache করো। Browser বা CDN যে Request-এর উত্তর দেয়, সেটা কখনো তোমার Load Balancer, App Server বা Database পর্যন্ত পৌঁছায় না। ৯৯% Hit Ratio-র Static Asset হলো সবচেয়ে সস্তা Scaling-এর জয়।
  • মাপকাঠি হলো Hit Ratio। Origin-এর চাপ = মোট Request × (১ − Hit Ratio)। প্রতিটা Path-এর জন্য আলাদা দেখো; কম হলে সাধারণত দোষী Vary, Cookie আর Query String।
  • Browser-এ ছোট, CDN-এ লম্বা, বদলালে Purge। Browser Purge করা যায় না, CDN যায়। s-maxage আর CDN-এর নিজস্ব Header এজন্যই।
  • API এমনভাবে বানাও যাতে Cache করা যায়। Public পড়া আর ব্যক্তিগত পড়া আলাদা রাখো (/products/7 বনাম /me/cart), ETag দাও, আর পড়ার জন্য GET ব্যবহার করো, যাতে Cache সাহায্য করতে পারে (Lesson ২.২)।
  • চালু থাকা বনাম সঠিকতা, জেনেবুঝে বাছো। stale-while-revalidate ধীর Render ঢাকে, stale-if-error বিভ্রাট ঢাকে, must-revalidate দুটোই মানা করে। প্রতিটা Endpoint-এর জন্য আলাদা ঠিক করো।
  • Caching একটা নিরাপত্তার সীমানাও। ব্যক্তিগত Data-র সামনে থাকা যেকোনো Shared Cache একটা Access Control তালিকার মতোই সাবধানে সাজাতে হয়।
  • Interview-এর প্রশ্ন: “Deploy-এর পর ব্যবহারকারী নতুন JavaScript পাচ্ছে, কীভাবে নিশ্চিত করবে?” (Hash-ওয়ালা নাম, স্বল্পমেয়াদি HTML)। “no-cache বনাম no-store?” “আমাদের CDN-এর Hit Ratio কম কেন?” (Vary, Cookie, Query String)। “দুজন Admin একে অপরের লেখা মুছে না দিক, কীভাবে?” (ETag + If-Match, 412)। Cache-aside, Write-through আর Eviction-এর প্রশ্ন Chapter 23-এ।

#নিজে করে দেখো

# See the caching headers of any URL (HEAD request)
curl -sI https://www.example.com/ | grep -iE 'cache-control|etag|last-modified|expires|age|vary'

# Revalidate by hand: copy the ETag from above and send it back
curl -sI https://www.example.com/ -H 'If-None-Match: "84238dfc8092e5d9c0dac8ef93371a07"'
#   HTTP/2 304 means your copy is still good

# Same with a date
curl -sI https://www.example.com/ -H 'If-Modified-Since: Mon, 02 Nov 2026 00:00:00 GMT'

# Is a CDN serving from cache? Look for age, x-cache or cf-cache-status (hit / miss / expired / revalidated)
curl -sI https://www.cloudflare.com/ | grep -iE 'cf-cache-status|age|cache-control'

# Ask for Brotli, then for nothing, and compare content-encoding and vary
curl -sI https://www.example.com/ -H 'Accept-Encoding: br'
curl -sI https://www.example.com/

# Optimistic concurrency against your own API
curl -i -X PUT https://api.example.com/products/7 -H 'If-Match: "7"' \
  -H 'Content-Type: application/json' -d '{"price":120}'
#   412 Precondition Failed if someone changed it first

Nginx-এ Policy বসানো (পুরো Config আছে Lesson ১.১৩-এ):

location /assets/ {                       # hashed files
    add_header Cache-Control "public, max-age=31536000, immutable";
}
location = /index.html {                  # the page that names them
    add_header Cache-Control "no-cache";
    etag on;
}
location /api/me {                        # personal
    add_header Cache-Control "private, no-cache";
    proxy_pass http://app;
}

Browser-এ DevTools → Network খোলো, Reload দাও, আর Size কলামটা দেখো: “(memory cache)” আর “(disk cache)” হলো Fresh Hit, আর 304 Status সহ কয়েকশো Byte মানে আবার যাচাই। Disable cache টিক দিলে Page-টা প্রথমবারের ভিজিটরের চোখে দেখবে; সাধারণ Reload আর Hard Reload (Ctrl+Shift+R) তুলনা করো। Application → Back/forward cache দেখায় একটা Page bfcache-এর যোগ্য কি না।

#সারসংক্ষেপ

মূল শিক্ষা

  • HTTP Caching ব্যবহারকারীর কাছেই Response আবার ব্যবহার করে। Fresh Hit-এর দাম শূন্য, 304-এর এক ছোট Round Trip, 200-এর পুরো Body। CDN-এর Hit Ratio ৯০% থেকে ৯৫% করলে Origin-এর চাপ অর্ধেক।
  • Private Cache (Browser) একজনের Data রাখতে পারে; Shared Cache (Proxy, Gateway, CDN) কখনোই না। Browser-এর Cache Memory আর Disk দুটোই ব্যবহার করে, আর Site ধরে ভাগ করা।
  • কপি Fresh যতক্ষণ বয়স মেয়াদের চেয়ে কম: s-maxage (শুধু Shared) হারায় max-age-কে, আর max-age সবসময় হারায় Expires-কে; Age আগের ধাপে কাটানো সময় গোনে; মেয়াদ না থাকলে Cache Last-Modified-এর পর থেকে যাওয়া সময়ের ১০% আন্দাজ করতে পারে।
  • no-cache = রাখো কিন্তু প্রতিবার যাচাই; no-store = কখনো রেখো না; private = শুধু Browser; public = কদাচিৎ লাগে; must-revalidate = কখনো Stale না, বদলে Error; immutable = কখনো আবার যাচাই না।
  • Validation: ETag + If-None-Match (পছন্দের; Strong বা W/ Weak; প্রতিটা Server-এ এক) বা Last-Modified + If-Modified-Since (এক সেকেন্ডের মাপ) → 304 Not Modified, আপডেট হওয়া Header সহ, Body ছাড়া।
  • Vary Request-এর Header-কে Cache Key-তে যোগ করে: Compressed Response-এ সবসময় Accept-Encoding; Shared Cache-এ কখনো কাঁচা User-Agent বা Cookie না।
  • stale-while-revalidate সাথে সাথে দেয়, পেছনে Refresh করে; stale-if-error বিভ্রাটের মধ্যেও Site চালু রাখে (মূলত CDN-এ)।
  • max-age=31536000, immutable-ওয়ালা Hash-ওয়ালা File-এর নাম, সাথে স্বল্পমেয়াদি HTML, Deploy নিরাপদ করে; পুরনো Asset কয়েক দিন রেখে দাও।
  • ব্যক্তিগত Response: private বা no-store, Shared Cache-এ কখনো Set-Cookie না, শুধু Extension দেখে কখনো Cache না।
  • ETag + If-Match হারানো Update-কে বানায় 412 Precondition Failed; 428 শর্ত দাবি করে; If-None-Match: * একবারই তৈরি করে।