Course-এর সূচি

24.2

প্রতিটি স্তরে Caching

edge, API, অ্যাপ্লিকেশন, ডেটাবেস আর session-এ caching, আর প্রতিটার লাভ-ক্ষতি।

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

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

এই লেসন শেষে তুমি পারবে

  • একটা সাধারণ AWS application-এর পুরো caching-এর পথ এঁকে ফেলতে (client → CloudFront → API Gateway → Lambda বা EC2 → ElastiCache বা DAX → database, আর CloudFront-এর পেছনে S3), আর বলতে কোন স্তর কী cache করে।
  • প্রতিটা স্তরের পেছনের একটাই লাভ-ক্ষতির হিসাব বুঝিয়ে বলতে: user-এর যত কাছে cache, প্রতিটা request তত দ্রুত আর সস্তা, কিন্তু কপিটা মুছে নতুন করা তত কঠিন, আর সেটা তত বেশি পুরোনো (stale) হতে পারে।
  • প্রতিটা স্তরের TTL আর invalidation সেট করতে ও তা নিয়ে যুক্তি দিতে: CloudFront-এর TTL আর invalidation, API Gateway-র stage cache (শুধু REST, TTL, cache key, Cache-Control: max-age=0 দিয়ে client-এর bypass), ElastiCache-এর TTL আর লেখার সময় key মুছে দেওয়া, DAX-এর item TTL।
  • কখন একটা cache টাকা বাঁচায় আর কখন শুধু একটা নতুন bill যোগ করে, তা ঠিক করতে।
  • এই পথে lazy loading, write-through নাকি দুটোই, তা বাছতে, আর কোন ডেটা কখনো cache করা যাবে না, তার নাম বলতে।
  • DAX নাকি ElastiCache বাছতে, আর session state-কে web tier-এর বাইরে রাখতে।
  • পরীক্ষার scenario-র keyword পড়ে বলতে, কোন caching স্তর চাওয়া হচ্ছে।

#সব স্তরেই cache কেন

আগের অধ্যায়গুলোতে একটা একটা করে cache শিখেছ: লেসন ১১.১-এ CloudFront, লেসন ৭.৫-এ ElastiCache, লেসন ১৫.৫-এ DAX, লেসন ১৫.৬-এ API Gateway-র stage cache। এই লেসনে সেগুলো আবার শেখানো হবে না। এখানে সবগুলোকে একটাই request-এর পথে বসিয়ে architect-এর প্রশ্নগুলো করা হবে: কোন request-এর উত্তর কোন স্তর দেবে, প্রতিটা কপি কতক্ষণ বাঁচবে, চারটা cache পেরোতে গিয়ে একটা দামের পরিবর্তনের কী হয়, আর কোন ডেটা কোথাও cache করাই চলবে না।

AWS-এ নানা স্তরে caching করা হয় তিনটা কারণে, আর পরীক্ষায় তিনটা শব্দই আসে: application-কে দ্রুত করা (latency কমানো), database আর তার পেছনের compute-এর চাপ কমানো, আর খরচ বাঁচানো।

#একটা flash sale-এর request কোন পথে যায়

ধরো Daraz-ধাঁচের একটা shop, ১১.১১ flash sale-এর রাত। ঢাকা, চট্টগ্রাম আর সিলেট থেকে shopper-রা app খুলছে; shop চলছে Singapore Region-এ।

স্তরServiceকী cache করতে পারেসাধারণ TTL
EdgeCloudFrontছবি, CSS/JS, পুরো page, এমনকি API response, shopper-এর কাছের একটা PoP-একয়েক সেকেন্ড থেকে এক বছর; default ২৪ ঘণ্টা
APIAPI Gateway stage cacheREST API method-এর response (default-এ শুধু GET)default ৩০০ s, সর্বোচ্চ ৩,৬০০ s
ApplicationElastiCache (Valkey, Redis OSS, Memcached)তোমার code যা রাখে: query-র ফল, বানানো page, sessionপ্রতিটা key-তে তুমি ঠিক করো
DatabaseDynamoDB-র জন্য DAX; RDS-এর জন্য read replicaDynamoDB-র item আর query-র ফল; RDS database-এর পড়ার কপিDAX item: default ৫ মিনিট
StorageCloudFront-এর পেছনে S3Edge যে static file cache করে, তার origin(origin, cache নয়)

#একটাই হিসাব: কাছে মানে দ্রুত, দূরে মানে টাটকা

পুরো ছবিটা ছয়টা শব্দে: caching, TTL, network, computation, cost, latency। প্রতিটা স্তর এগুলোকে একই দিকে কাটাকাটি করে:

  • User-এর যত কাছে (edge): request-টাকে Region পর্যন্ত network-এর যাত্রা, পেছনের সব computation আর database query, কিছুই করতে হয় না। Latency সবচেয়ে কম, প্রতি request-এর খরচও সবচেয়ে কম। কিন্তু কপিটা ছড়িয়ে থাকে শত শত জায়গায়, তাই সেটা মুছে নতুন করা সবচেয়ে কঠিন, আর সেটাই হতে পারে সবচেয়ে পুরোনো।
  • Database-এর যত কাছে (ElastiCache, DAX): প্রতিটা request-কে সামনের network আর compute-এর দাম দিতেই হয়, কিন্তু cache থাকে একটা জায়গায়, তোমার code-এর হাতে, তাই একটা key মুছতে একটা command-ই যথেষ্ট, আর ডেটা প্রায় টাটকা রাখা যায়।
  • Stale-এর সময় যোগ হয়। API cache যদি একটা response ৬০ s রাখে, আর যে Valkey key থেকে সেটা বানানো তার TTL-ও ৬০ s হয়, তাহলে একজন shopper পুরোনো দাম প্রায় দুই মিনিট দেখতে পারে। সবচেয়ে খারাপ ক্ষেত্রে সময়টা হলো যত স্তর কপি রাখে, তাদের সবার TTL-এর যোগফল।

তাই প্রতিটা রকমের ডেটার জন্য design-এর প্রশ্ন একটাই: পথের কত ওপর পর্যন্ত এটা যেতে পারে, যতক্ষণ না এর পুরোনো হওয়াটা ক্ষতি করে? Product-এর ছবি edge পর্যন্ত যেতে পারে; product list API-তে এক মিনিট বসে থাকতে পারে; দাম Valkey-তে থাকতে পারে, যদি বদলালেই key মুছে দাও; আর শেষ ৪০টা phone-এর stock-এর সংখ্যা কোথাও cache করা যায় না।

একটাই request-এর পথ, cache রাখার চারটা জায়গা

একটা flash-sale shop: shopper-রা ঢাকায়, application Singapore Region-এ। একটা product-এর ছবি, একটা product list, পুরো miss, দাম বদলানো আর stock-এর সংখ্যা, একে একে দেখো কোন স্তর কোনটার উত্তর দেয়।

পরিস্থিতি

Static file shopper-এর কয়েক millisecond দূর থেকেই উত্তর পায়, Region পর্যন্ত যায়ই না।

Shopperঢাকাx-cache: Miss
CloudFrontedge
S3 bucketছবি
GET p881-v3.webpmiss
ধাপ 1 / 3
1

সকালের প্রথম shopper একটা product-এর ছবি চাইল। কাছের PoP-এ এখনো ছবিটা নেই (miss), তাই CloudFront সেটা Regional Edge Cache হয়ে S3 bucket থেকে আনে; OAC থাকায় bucket private-ই থাকে।

#প্রথম স্তর: edge (CloudFront)

আগে যা শিখেছ (লেসন ১১.১ আর ১১.২): CloudFront হলো AWS-এর global CDN। User-এর কাছাকাছি ৭৫০-এর বেশি edge location-এ (PoP) সে কপি রাখে, আর তাদের পেছনে আছে বড় বড় Regional Edge Cache। Request-এর উত্তর দেয় সবচেয়ে কাছের PoP; শুধু miss হলে request origin-এ ফেরে (S3, একটা ALB, EC2, API Gateway বা যেকোনো HTTP server)।

পুরো পথের হিসাবে যেগুলো জরুরি:

  • TTL। একটা কপি কতক্ষণ টাটকা ধরা হবে, তারপর CloudFront আবার origin-কে জিজ্ঞেস করবে। Origin-এর Cache-Control: max-age এটা ঠিক করে, cache policy-র minimum আর maximum TTL-এর ভেতরে; কোনো header না থাকলে default TTL ২৪ ঘণ্টা (৮৬,৪০০ s)। TTL শেষ হলে CloudFront revalidate করে বা নতুন version নিয়ে আসে।
  • Cache key। Default-এ domain আর URL path। Cache policy দিয়ে এতে যোগ করো ঠিক সেই query string, header আর cookie, যেগুলো response বদলায়, এর বেশি একটাও না: দরকারি একটা বাদ পড়লে ভুল কপি যায়, বাড়তি একটা ঢুকলে hit ratio ধসে পড়ে।
  • Path ধরে behaviour। /img/*-এর static file-এ লম্বা TTL-এর policy, যেমন CachingOptimized; dynamic /api/*-এ সাধারণত CachingDisabled, তাই edge request সামনে পাঠিয়ে দেয়। তখনো edge কাজে লাগে: TLS শেষ হয় user-এর কাছেই, আর request চলে AWS-এর network ধরে।
  • Invalidation। create-invalidation TTL শেষ হওয়ার আগেই সব edge থেকে path মুছে দেয়। মাসে প্রথম ১,০০০টা path বিনা পয়সায়, তারপর প্রতি path ০.০০৫ USD (wildcard-ওয়ালা path একটাই গোনা হয়)। যে file বদলায়, তার জন্য ভালো অভ্যাস হলো নামে version রাখা (banner-v2.jpg, app.3f9a1c.js): নতুন নাম মানে নতুন cache key, মোছার কিছু নেই, আর browser-এর cache-কেও হারিয়ে দেয়, যেখানে invalidation পৌঁছাতেই পারে না।
  • CloudFront-এর পেছনে S3। Bucket private রাখো, আর শুধু distribution-কে পড়তে দাও Origin Access Control (OAC) দিয়ে। AWS-এর origin থেকে CloudFront-এ ডেটা যাওয়া বিনা পয়সায়, তাই S3-কে CloudFront দিয়ে serve করা সাধারণত দ্রুত তো বটেই, সস্তাও।

#দ্বিতীয় স্তর: API (API Gateway-র stage cache)

আগে যা শিখেছ (লেসন ১৫.৬): একটা REST API stage-এ cache রাখা যায়। Request-এর key cache-এ থাকলে API Gateway backend-কে না ডেকেই উত্তর দেয়। পুরো পথে এই স্তর edge-এর পেছনে আর তোমার সব compute-এর সামনে বসে: এখানে একটা hit Lambda invocation (বা EC2-এর কাজ) আর তার পেছনের সবকিছু বাঁচিয়ে দেয়।

পরীক্ষা আর production, দুই জায়গাতেই যে তথ্যগুলো লাগে:

  • শুধু REST API। HTTP API আর WebSocket API-তে নিজস্ব cache নেই; দরকার হলে সামনে caching policy-সহ CloudFront বসাও।
  • প্রতি stage-এ একটা, আকার 0.5 GB থেকে 237 GB। বানাতে বা আকার বদলাতে কয়েক মিনিট লাগে, আর আকার বদলালে cache খালি হয়ে যায়।
  • TTL: default ৩০০ s, ০ (বন্ধ) থেকে ৩,৬০০ s পর্যন্ত। প্রতিটা method-এ আলাদা TTL দেওয়া যায়, বা caching বন্ধ রাখা যায়।
  • Default-এ শুধু GET। Stage cache চালু করলে কেবল GET method cache হয়; অন্য method-এর জন্য আলাদা override লাগে (আর সেগুলো cache করা কদাচিৎ ঠিক)।
  • Cache key: method আর path, সাথে তুমি যে cache key parameter বেছে নাও (query string, header, path parameter)। Response বদলায় এমন একটা parameter ভুলে গেলে একজন shopper আরেকজনের উত্তর পায়।
  • 1 MB-এর বড় response cache হয় না। Cache-এর ডেটা encrypt করা যায়। CloudWatch-এ CacheHitCount আর CacheMissCount metric দেখো।
  • পুরো stage cache flush করা যায় console থেকে বা flush-stage-cache দিয়ে; তারপর cache আবার না ভরা পর্যন্ত পুরো চাপ backend নেয়।

#Client-এর bypass: Cache-Control: max-age=0

যে client-এর টাটকা উত্তর দরকার, সে Cache-Control: max-age=0 পাঠাতে পারে। তখন API Gateway backend-কে ডাকে আর রাখা entry-টার জায়গায় নতুন response বসিয়ে দেয়। কে এটা পারবে, তা ঠিক হয় stage বা method-এর দুটো setting দিয়ে:

  1. Require authorization (requireAuthorizationForCacheControl)। এটা চালু থাকলে caller-এর ওই method-এর resource ARN-এ execute-api:InvalidateCache action-এর IAM permission লাগে। বন্ধ থাকলে যেকোনো client cache bypass করে নতুন করে দিতে পারে, আর AWS সাবধান করে যে এতে সবার latency বাড়তে পারে।
  2. যাদের permission নেই, তাদের জন্য unauthorized request handling (unauthorizedCacheControlHeaderStrategy):
    • FAIL_WITH_403: request ফিরিয়ে দেওয়া হয় 403 দিয়ে।
    • SUCCEED_WITH_RESPONSE_HEADER: header উপেক্ষা করে cache থেকেই উত্তর, সাথে একটা warning header।
    • SUCCEED_WITHOUT_RESPONSE_HEADER: header চুপচাপ উপেক্ষা করা হয়।

#API cache কি সত্যিই টাকা বাঁচায়?

Notes-এর পরীক্ষার কৌশল বলে, API caching “backend call কমিয়ে API Gateway-র খরচ কমায়”। একটু খুঁটিয়ে পড়ো:

  • Cache-এর bill ঘণ্টা ধরে, আকার অনুযায়ী, ব্যবহার হোক বা না হোক, আর এটা free tier-এ নেই: us-east-1-এ 0.5 GB-র জন্য ঘণ্টায় প্রায় ০.০২ USD (মাসে প্রায় ১৪.৬০ USD) থেকে 237 GB-র জন্য ঘণ্টায় ৩.৮০ USD (মাসে প্রায় ২,৭৭০ USD)।
  • প্রতিটা request তবু API Gateway-র request। Cache hit-এর bill miss-এর মতোই (প্রথম ধাপে প্রতি দশ লাখ REST request-এ ৩.৫০ USD)। Bill-এর API Gateway-র লাইনটা কখনো কমে না।
  • যেটা কমে, সেটা backend: Lambda-র invocation আর duration, EC2-এর capacity, database-এর চাপ।

একটা হিসাব করে দেখি। একটা catalogue API-তে মাসে ২,০০,০০০ GET request আসে, পেছনে 128 MB-র একটা Lambda function, প্রতিবার ১০০ ms চলে। Lambda-র দাম প্রতি দশ লাখ request-এ ০.২০ USD, আর প্রতি GB-second-এ ০.০০০০১৬৬৬৬৭ USD: ০.০৪ USD + ২,০০,০০০ × ০.১ × ০.১২৫ × ০.০০০০১৬৬৬৬৭ ≈ ০.০৪ + ০.০৪ = মাসে ০.০৮ USD। 0.5 GB-র একটা cache-এর দাম প্রায় ১৪.৬০ USD। এখানে caching API-কে দ্রুত করে, কিন্তু bill ১৮২ গুণ করে দেয়।

এবার flash sale: মাসে ৫ কোটি request, hit ratio ৯০%। Cache সাড়ে চার কোটি invocation বাঁচায়: request-এর ৯ USD + ৪,৫০,০০,০০০ × ০.১ × ০.১২৫ × ০.০০০০১৬৬৬৬৭ ≈ ৯.৩৮ USD duration, মোট প্রায় ১৮ USD, সাথে লক্ষ লক্ষ database query, যেগুলোর জন্য নইলে বড় database লাগত। এখানে ১৪.৬০ USD-র cache নিজের খরচ তুলে আনে। Backend-এর সাশ্রয় cache-এর fee-কে ছাড়ালে তবেই caching খরচ কমায়।

API Gateway-এর stage cache

একটা REST API stage, তাতে 0.5 GB cache আর ৬০ সেকেন্ডের TTL, পেছনে একটা Lambda function। দেখো একটা hit, একটা cache key যেটা একটা parameter ভুলে গেছে, আর কিছু client যারা cache এড়িয়ে যেতে চায়।

পরিস্থিতি

Shopper app
অন্য client
API Gatewaystage cache
Lambda
GET /products?cat=phones
ধাপ 1 / 5
1

Deploy-এর পর প্রথম request। Stage cache চালু করলে default-এ শুধু GET method cache হয়; POST, PUT আর DELETE সবসময় backend-এ পৌঁছায়।

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

Lambda-র সামনে একটা REST API-তে মাসে ৩,০০,০০০ GET request আসে, প্রায় সবগুলো আলাদা। “খরচ কমাতে” team 0.5 GB-র একটা stage cache চালু করল। Bill-এর কী হবে?

#তৃতীয় স্তর: application cache (ElastiCache)

আগে যা শিখেছ (লেসন ৭.৫): ElastiCache হলো একটা in-memory স্তর, যেটা তোমার code database-এর সামনে বসায় (RDS, Aurora, DynamoDB, যেকোনো কিছু), যাতে database-কে একই query বারবার চালাতে না হয়। ওপরের স্তরগুলোর মতো না, এটা cache করে তোমার code যা ঠিক করে: একটা query-র ফল, বানানো একটা product page, একটা session, একটা leaderboard।

#কোন engine

Notes দুটো version-এর কথা বলে, Redis আর Memcached। ২০২৪ থেকে ElastiCache-এ তিনটা engine:

  • Valkey: Redis-এর open-source fork, Linux Foundation চালায়, একই command আর client। AWS-এর সুপারিশ করা default, অন্যদের চেয়ে সস্তাও।
  • Redis OSS: open-source Redis engine (পরীক্ষা যাকে এখনো “Redis” বলে)।
  • Memcached: সাদামাটা, পুরোনো cache।

সাথে ElastiCache Serverless (তিনটা engine-এর জন্যই): node-এর আকার ঠিক করতে হয় না, ডেটা কয়েকটা AZ-এ replicate হয়, bill হয় রাখা ডেটা আর processing unit ধরে। পরীক্ষা “Redis” নিয়ে যা বলে, সবই Valkey-র বেলায়ও খাটে।

Valkey / Redis OSSMemcached
High availabilityReplica, Multi-AZ automatic failoverনেই: replication নেই, failover নেই (node আলাদা AZ-এ রাখা যায়, কিন্তু হারানো node-এর ডেটা চলে যায়)
PersistenceBackup আর restore (snapshot)Node-based cluster-এ নেই
ডেটার ধরনString, hash, list, set, sorted set (leaderboard), geospatial, stream, pub/subসাধারণ key-value
ScalingRead-এর জন্য replica, write-এর জন্য shardNode যোগ করো; client key ছড়িয়ে দেয় (pure horizontal)
ThreadCommand চলে এক thread-এMulti-threaded
অন্য RegionGlobal Datastoreনেই

#এই পথে lazy loading আর write-through

লেসন ৭.৫ দুটো strategy ধাপে ধাপে শিখিয়েছে। এখানে দেখি, cache যখন চার স্তরের একটা পথের মাঝখানে বসে, তখন এদের মানে কী দাঁড়ায়।

  • Lazy loading (cache-aside)। App আগে cache পড়ে; hit হলে উত্তর দেয়, miss হলে database পড়ে, উত্তর দেয়, আর ফলটা cache-এ লিখে রাখে। কেবল যা কেউ চায়, তা-ই cache হয়, তাই memory বাঁচে; আর একটা node নষ্ট বা খালি হলেও বিপদ নেই (read সোজা database-এ যায়)। খরচ: miss penalty (তিন যাত্রা: cache get, database query, cache set, তাই প্রথম read cache না থাকার চেয়েও ধীর), আর stale data, কারণ database update হলে cache-এ হাতই পড়ে না।
  • Write-through। Database-এ প্রতিটা write-এর পরে cache-এও write। লেখা ডেটার read সবসময় hit, কখনো stale না। খরচ: write penalty (প্রতি update-এ দুবার লেখা), cache churn (যে ডেটা কেউ পড়বে না, সেটাও memory ভরায়), আর নতুন node-এ ডেটা নেই: কোনো key আবার লেখা না হওয়া পর্যন্ত নতুন node-এ সেটা খালিই থাকে।

বাস্তবে উত্তর হলো দুটো মিলিয়ে চালানো: lazy loading, যাতে যেকোনো miss cache আবার ভরে দেয় (নতুন node, পুরোনো ডেটা); write-through বা লেখার সময় key মুছে দেওয়া, যাতে বদলানো ডেটা টাটকা থাকে; আর প্রতিটা key-তে TTL, যাতে কেউ না পড়া ডেটা বিদায় নেয়, আর কোনো update বাদ পড়লেও সেটার সময় সীমিত থাকে। Memory ভরে গেলে eviction policy key সরায় (default volatile-lru TTL থাকা key-গুলোর মধ্যে সবচেয়ে কম সম্প্রতি ব্যবহৃত key-টা সরায়), প্রতিটা key-তে TTL রাখার এটা আরেকটা কারণ।

Valkey-তে দাম: lazy loading, write-through, আর যা কখনো ঢোকে না

Flash-sale shop product-এর দাম Aurora-র সামনে ElastiCache for Valkey-তে রাখে। দেখো একটা stale read, লেখার সময় key মুছে দেওয়ার সমাধান, write-through-এ খালি নতুন node, আর stock-এর সংখ্যা যা cache এড়িয়ে যায়।

পরিস্থিতি

Seller
App
Valkey১,৪৯৯ · ৬০ s
Aurora১,৪৯৯
GET price:881
ধাপ 1 / 4
1

একজন shopper phone-এর page খুলল। Lazy loading: app আগে Valkey-কে জিজ্ঞেস করে, আর ১,৪৯৯ দামটা সেখানে আছে (TTL ৬০ s)।

#যা কখনো cache-এ ঢোকে না

কিছু ডেটা যে মুহূর্তে ব্যবহার হয়, সেই মুহূর্তে নিখুঁত হতেই হবে। এদের কোথাও, যেকোনো TTL-এ cache করলে গতির সমস্যা পাল্টে ভুলের সমস্যা হয়ে যায়:

  • Sale-এর সময় stock-এর সংখ্যা আর seat: database থেকে পড়ো, আর কেনাটাকে বানাও একটা শর্তসাপেক্ষ write (stock > 0 হলে তবেই কমাও)।
  • Payment-এর মুহূর্তে account balance, যেমন bKash-ধাঁচের একটা wallet-এ: home screen-এর জন্য balance cache করা যায়, টাকা কাটার সিদ্ধান্তের জন্য কখনো না।
  • Shared key-তে user-ভিত্তিক ডেটা: user ছাড়া একটা key-তে API স্তরে cache করা cart বা profile পরের user-কে দেখানো হয়।
  • একবারের token, OTP আর নিরাপত্তা-সংক্রান্ত যেকোনো কিছু।

#DynamoDB-র তৃতীয় স্তর: DAX

আগে যা শিখেছ (লেসন ১৫.৫): DynamoDB উত্তর দেয় এক অঙ্কের millisecond-এ। DynamoDB Accelerator (DAX) হলো শুধু DynamoDB-র জন্য বানানো একটা in-memory cache cluster, যেটা cache করা read-এর উত্তর দেয় microsecond-এ, আর table-এর ওপর থেকে read-এর চাপ সরায়।

  • API-compatible। DAX DynamoDB-র API-তেই কথা বলে। DynamoDB client-এর জায়গায় DAX client বসাও আর cluster-এর endpoint দেখিয়ে দাও; GetItem, Query আর PutItem একই থাকে। Notes-এর “code নতুন করে লিখতে হয় না” কথাটা ঠিক, শুধু একটু নিখুঁত করে বললে client আর endpoint বদলায় (ছোট পরিবর্তন, শূন্য না)।
  • Read-through আর write-through: miss হলে table থেকে পড়ে cache করে; DAX-এর ভেতর দিয়ে write আগে table-এ যায়, তারপর item cache নতুন করে।
  • কেবল eventually consistent read। Strongly consistent read (ConsistentRead=true) বা transactional read সোজা table-এ চলে যায়, cache হয় না।
  • Item cache আর query cache, দুটোরই default TTL ৫ মিনিট। Query cache (Query আর Scan-এর ফল) write-এ নতুন হয় না।
  • যে write DAX এড়িয়ে যায় (অন্য application, একটা import job, global table-এর replica), সেগুলো TTL শেষ না হওয়া পর্যন্ত cache-কে stale রেখে দেয়।
  • চলে তোমার VPC-র ভেতরে একটা cluster হিসেবে (একটা primary আর সর্বোচ্চ ১০টা read replica; production-এ তিনটা AZ-এ তিনটা node), bill node-hour ধরে। এটা read-কে সাহায্য করে, write-কে না।
প্রশ্নে যা থাকেবাছো
DynamoDB + microsecond latency + application নতুন করে না লিখেDAX
Read-heavy DynamoDB table, একটা hot key read throttle করছেDAX
অনেক item জুড়ে একটা হিসাব বা aggregation-এর ফল cache করোElastiCache
RDS / Aurora বা DynamoDB ছাড়া অন্য কিছুর জন্য cacheElastiCache
Session store, leaderboard, pub/sub, rate-limit counterElastiCache
Strongly consistent read দ্রুত করতে হবেকোনোটাই কাজে আসে না; DAX এগুলো সোজা পার করে দেয়

DAX: DynamoDB read-এ microsecond

Shop-এর product catalogue একটা DynamoDB table, সামনে তিন node-এর একটা DAX cluster। App একই GetItem call দিয়ে DAX client ব্যবহার করে। দেখো DAX কোন read দিতে পারে, আর কোনটা পারে না।

পরিস্থিতি

AppDAX client
DAX৩টা node
DynamoDB
Import job
GetItem p881
ধাপ 1 / 4
1

Code আগের মতোই GetItem call করে; বদলেছে শুধু client আর endpoint। DAX-এর ক্ষেত্রে “application নতুন করে লিখতে হয় না” মানে এটাই।

#Session state: web tier-কে stateless রাখো

আগে যা শিখেছ (লেসন ৯.১, ৯.২ আর ৬.৪): session-এর ডেটা, যেমন কে login করে আছে আর cart-এ কী আছে, শুধু একটা EC2 instance-এ থাকা চলবে না। এটা রাখো একটা বাইরের store-এ, আর browser-কে দাও শুধু একটা session ID। Notes একে বলে session caching।

  • ElastiCache for Valkey বা Redis OSS: millisecond-এরও কম সময়ে read, প্রতিটা session-এ session timeout-এর সমান TTL; replica আর Multi-AZ রাখো, কারণ store হারালে সবাই logout হয়ে যায়।
  • DynamoDB: টেকসই আর serverless, সাথে একটা TTL attribute, যাতে মেয়াদ ফুরানো session নিজে থেকেই মুছে যায়। Session-কে যেকোনো অবস্থায় টিকে থাকতে হলে এটা ভালো।
  • Node নষ্ট হলে session হারানো যদি মেনে নেওয়া যায়, তাহলে Memcached-ও চলে।

Sticky session (ALB-র stickiness cookie) হলো শর্টকাট, সমাধান না। এটা একজন user-কে একটা instance-এ আটকে রাখে, তাই memory-তে রাখা cart ততক্ষণই বাঁচে, যতক্ষণ সেই instance বাঁচে: একটা scale in, একটা ব্যর্থ health check বা একটা deployment, আর cart শেষ। চাপও ছড়ায় অসমানভাবে: scale out-এ আসা নতুন instance পায় শুধু নতুন user-দের, আর ব্যস্ত instance-টা তার আটকে থাকা user-দের নিয়েই থাকে। Shared store-এ session রাখলে দুটো সমস্যাই চলে যায়; web tier হয়ে যায় stateless, আর যেকোনো instance যেকোনো request-এর উত্তর দিতে পারে।

Session state: instance-এ, নাকি সবার জন্য একটা store-এ

Flash sale-এর সময় একটা ALB আর Auto Scaling group-এর পেছনে cart। প্রথমে cart থাকে instance-এর memory-তে, সাথে sticky session; তারপর একটা DynamoDB table-এ, যেটা সব instance পড়তে পারে।

পরিস্থিতি

Sticky session একজন shopper-কে একটা instance-এ আটকে রাখে; সেই instance গেলে cart-ও যায়।

ASG
Shopper
ALB
EC2 Acart: ২টা
EC2 B
cart-এ রাখোsticky
ধাপ 1 / 3
1

রহিম cart-এ দুটো phone রাখল। EC2 A cart-টা নিজের memory-তে রাখে, আর ALB একটা stickiness cookie বসায়, যাতে ওর পরের request-গুলোও A-তেই যায়।

#Cache-এর মতো আরও দুটো বিকল্প

  • RDS read replica (লেসন ৭.১) কোনো cache code ছাড়াই primary-র ওপর থেকে read query সরিয়ে নেয়: report আর শুধু-পড়ার page replica endpoint-এ পাঠাও। এগুলো পুরো database-এর কপি, asynchronous ভাবে হালনাগাদ হয় (কয়েক সেকেন্ডের lag), আর প্রতিটা query তবু চালায়; cache কিছু না চালিয়েই রাখা উত্তর দিয়ে দেয়। Read নানা রকম হলে (report, search page) আর একটু lag চললে replica বাছো; একই কয়েকটা ফল বারবার পড়া হলে ElastiCache।
  • OAC-সহ CloudFront-এর পেছনে S3: static file-এর জন্য “cache স্তর” শুধু edge-ই, আর S3 হলো private origin। ছবি, script বা download-এর জন্য ElastiCache লাগে না।

#কোথায় কী cache করবে

ডেটাEdge (CloudFront)API cacheValkey / DAXকখনো cache নয়কেন
Product-এর ছবি, CSS/JS (নামে version)হ্যাঁ, ১ বছরএকই নামে কখনো বদলায় না
Anonymous user-দের home page-এর HTMLহ্যাঁ, ৩০–৬০ sসবার জন্য একই, একটু পুরোনো হলে চলে
Category ধরে product listছোট TTL-এ সম্ভবহ্যাঁ, ৬০ sহ্যাঁ, ব্যাকআপ হিসেবেসবার জন্য একই, হাজার হাজার বার চাওয়া হয়
একটা product-এর দামশুধু ছোট TTL-এহ্যাঁ, লেখায় মুছে দাওCheckout-এটাকার ব্যাপারে টাটকা হতেই হবে
Shopper-এর cart আর loginনাSession: Valkey বা DynamoDBUser-ভিত্তিক; instance হারালেও টিকতে হবে
DynamoDB item read (catalogue)DAXMicrosecond, একই code
Sale-এর সেরা ক্রেতাদের leaderboardValkey sorted setহিসাব করা ডেটা, ঘনঘন বদলায়
Stock-এর সংখ্যা, wallet balance, OTPহ্যাঁপ্রতি মুহূর্তে নিখুঁত হতে হবে

#শুরু থেকে শেষ: একটা ১১.১১ flash sale

Daraz-ধাঁচের shop-এর ১১.১১-এর design, স্তর ধরে ধরে:

  1. Edge: product-এর ছবি আর script

    সব ছবি আর script থাকে CloudFront-এর পেছনে একটা private S3 bucket-এ, OAC দিয়ে, নামে version আর এক বছরের TTL-সহ। প্রতিটা edge-এ প্রথম request-এর পরে এগুলো আর Region-এ ফেরে না।

  2. API: product list ৬০ সেকেন্ডের জন্য

    GET /api/products?cat=… REST API stage-এ ৬০ s-এর জন্য cache হয়, cat আর page হলো cache key parameter। Sale-এর সময় এই একটা setting মিনিটে হাজার হাজার একই request-কে প্রতি category-তে মিনিটে একবার Lambda চলায় নামিয়ে আনে। Cache control-এর জন্য authorization চালু রাখো, যাতে শুধু admin app জোর করে নতুন করাতে পারে।

  3. Application: দাম Valkey-তে

    Lambda function-গুলো ElastiCache for Valkey থেকে দাম পড়ে, lazy loading আর ছোট TTL দিয়ে, আর seller দাম বদলালে key মুছে যায়। Valkey cluster-এর replica আছে অন্য AZ-এ, তাই node নষ্ট হলে সেটা failover হয়, খালি cache আর database-এ হুড়োহুড়ি না।

  4. Session: cart DynamoDB-তে

    Cart আর login থাকে একটা DynamoDB table-এ, session ID দিয়ে, সাথে TTL attribute। যেকোনো function বা instance যেকোনো shopper-কে serve করতে পারে।

  5. কখনো cache নয়: stock-এর সংখ্যা

    Sale-এর ৪০টা phone-এর stock পড়া হয় database থেকে, আর প্রতিটা কেনা একটা শর্তসাপেক্ষ write। কয়েক millisecond ধীর, কিন্তু কখনো বাড়তি বিক্রি হয় না।

নিচের tracer-এ design-টা চালিয়ে দেখো: প্রতিটা স্তর চালু-বন্ধ করো, TTL আর seller-এর শেষ পরিবর্তনের পর কত সময় গেছে বদলাও, আর দেখো প্রতিটা request-এর উত্তর কোথা থেকে আসে, কত সময় লাগে, আর উত্তর ভুল হতে পারে কিনা।

Request tracer: উত্তর আসে কোথা থেকে?

Shopper ঢাকায়, shop Singapore Region-এ। একটা request আর cache-এর স্তরগুলো বেছে নাও, seller কত আগে ডেটা বদলেছে বলো, তারপর request পাঠাও।

Request

CloudFront ছবি cache করে

API Gateway cache-এর TTL

App-এ Valkey

Seller ডেটা বদলেছে … আগে

Trace দেখতে “Request পাঠাও” চাপো।

#কোথায় গোলমাল হয়

  • জমে থাকা TTL। প্রতিটা স্তরের TTL আলাদাভাবে নিরীহ মনে হয়েছিল; সব মিলিয়ে একটা দামের পরিবর্তন shopper-এর কাছে পৌঁছাতে কয়েক মিনিট লাগে। একই ডেটা যত স্তর রাখে, তাদের TTL যোগ করো, আর নিচ থেকে ওপরে invalidate করো (database, Valkey key, API stage, edge)।
  • User ছাড়া cache key। User-ভিত্তিক response shared key-তে cache হলে একজনের cart বা profile আরেকজনকে দেখায়। User-ভিত্তিক method-এ caching বন্ধ রাখো, বা key-তে user রাখো।
  • Parameter ছাড়া cache key, যে parameter response বদলায়: laptop-এর page-এ phone দেখায়।
  • খোলা cache bypass। API stage-এ “require authorization” বন্ধ: যেকোনো client প্রতিটা request-এ max-age=0 পাঠাতে পারে, আর পুরো চাপ backend-এর ঘাড়ে।
  • Default TTL-এর চমক। Cache-Control ছাড়া object CloudFront ২৪ ঘণ্টা রাখে; দুপুরে বদলানো দামের তালিকা কাল পর্যন্ত পুরোনো থাকে।
  • Node নষ্টের পর ঠান্ডা cache। Replica ছাড়া lazy-loaded cache খালি হয়ে ফেরে, সব read miss করে, আর database-এর ওপর হুড়োহুড়ি (stampede) নামে। Replica আর Multi-AZ রাখো, TTL-এ jitter দাও।
  • অলস cache-এর জন্য টাকা দেওয়া। কম পুনরাবৃত্ত request-এর API-তে stage cache যতটা বাঁচায়, তার চেয়ে বেশি খরচ করায়।
  • ভুল read-এর জন্য DAX। Strongly consistent read, write-heavy table আর cluster এড়িয়ে যাওয়া write, এদের কেউই DAX থেকে কিছু পায় না।
  • Session store হিসেবে sticky session। Instance-এর সাথে cart-ও উধাও।

#System design-এ caching

  • শুরু করো ডেটা থেকে, service থেকে না: প্রতিটা রকমের ডেটা কতটা পুরোনো হতে পারে ঠিক করো, আর সেই পুরোনো হওয়া যতদূর সয়, পথের ততটা ওপরে সেটা cache করো।
  • Static থাকে edge-এ, সবার জন্য একই জিনিস API-তে, হিসাব করা জিনিস app cache-এ, নিখুঁত জিনিস database-এ।
  • প্রতিটা cache-কে ফেলে দেওয়ার যোগ্য রাখো: যেকোনো cache খালি হলেও system ঠিক থাকবে (শুধু ধীর হবে)। মানে কোনো কিছুর একমাত্র কপি cache-এ রাখা চলবে না, আর এই কারণেই আসল সত্য থাকে database-এ।
  • প্রতিটা স্তরের hit ratio দেখো (CloudFront-এ CacheHitRate, API Gateway-তে CacheHitCount / CacheMissCount, ElastiCache-এ CacheHits / CacheMisses আর Evictions)। কম hit ratio-র cache টাকা খরচ করায় আর একটা বাড়তি ধাপ যোগ করে।
  • এই একই পথ আবার আসে event-এর জন্য লেসন ২৪.১-এ, আর খারাপ client আটকানোর জন্য লেসন ২৪.৩-এ: অনাকাঙ্ক্ষিত traffic থামানোর সবচেয়ে সস্তা জায়গাও edge।
পরীক্ষার ভাষাস্তর আর service
Global user, latency, origin offload, static আর dynamic contentEdge: CloudFront
REST API-র পেছনের Lambda / EC2-এর চাপ কমাও, একই request বারবারAPI: API Gateway caching
Client জোর করে টাটকা ডেটা নেবেCache-Control: max-age=0 + execute-api:InvalidateCache
HA, Multi-AZ, failover, backup, pub/sub, leaderboardElastiCache for Redis (Valkey / Redis OSS)
সাধারণ key-value, purely horizontal, multi-threadedElastiCache for Memcached
Stale data ঠেকাওWrite-through
Memory বাঁচাও, শুধু চাওয়া ডেটা cache করোTTL-সহ lazy loading
DynamoDB, microsecond, code নতুন করে লেখা নয়DAX
Stateless web tier, terminate হলেও টিকে থাকা, sticky session এড়ানোSession ElastiCache বা DynamoDB-তে

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

# 1. Edge: see whether CloudFront answered from cache, then invalidate one path
curl -sI https://d111111abcdef8.cloudfront.net/img/p881-v3.webp | grep -iE 'x-cache|age'
# x-cache: Hit from cloudfront
# age: 5312
aws cloudfront create-invalidation --distribution-id E1ABCDEXAMPLE \
  --paths "/api-docs/index.html"
# { "Invalidation": { "Id": "I2J0I21PCUYOIK", "Status": "InProgress", ... } }

# 2. API: provision a 0.5 GB stage cache and cache every GET for 60 s
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
  --patch-operations op=replace,path=/cacheClusterEnabled,value=true \
                     op=replace,path=/cacheClusterSize,value=0.5 \
                     op=replace,path=/*/*/caching/enabled,value=true \
                     op=replace,path=/*/*/caching/ttlInSeconds,value=60
# { "stageName": "prod", "cacheClusterEnabled": true, "cacheClusterSize": "0.5",
#   "cacheClusterStatus": "CREATE_IN_PROGRESS", ... }

# 3. API: only callers with execute-api:InvalidateCache may bypass; others get 403
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
  --patch-operations op=replace,path=/*/*/caching/requireAuthorizationForCacheControl,value=true \
                     'op=replace,path=/*/*/caching/unauthorizedCacheControlHeaderStrategy,value=FAIL_WITH_403'

# 4. API: turn caching off for the per-user cart method (path /cart is written ~1cart)
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
  --patch-operations op=replace,path=/~1cart/GET/caching/enabled,value=false

# 5. API: empty the whole stage cache after an urgent price correction
aws apigateway flush-stage-cache --rest-api-id a1b2c3d4e5 --stage-name prod

# 6. Application: a serverless Valkey cache for prices
aws elasticache create-serverless-cache --serverless-cache-name prices \
  --engine valkey --subnet-ids subnet-0a1b2c3d subnet-0e5f6a7b \
  --security-group-ids sg-0123456789abcdef0
# { "ServerlessCache": { "ServerlessCacheName": "prices", "Status": "creating", "Engine": "valkey", ... } }

# 7. Lazy loading by hand with valkey-cli: miss, set with a TTL, hit, delete on write
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls GET price:881
# (nil)
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls SET price:881 1499 EX 60
# OK
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls TTL price:881
# (integer) 57
valkey-cli -h prices-abc123.serverless.apse1.cache.amazonaws.com --tls DEL price:881
# (integer) 1

# 8. DynamoDB: a three-node DAX cluster in front of the catalogue table
aws dax create-cluster --cluster-name catalogue-dax --node-type dax.r5.large \
  --replication-factor 3 --iam-role-arn arn:aws:iam::111122223333:role/DAXServiceRole \
  --subnet-group-name dax-private --security-group-ids sg-0123456789abcdef0
# { "Cluster": { "ClusterName": "catalogue-dax", "TotalNodes": 3, "Status": "creating", ... } }

# 9. Sessions: let DynamoDB delete expired sessions on its own
aws dynamodb update-time-to-live --table-name sessions \
  --time-to-live-specification Enabled=true,AttributeName=expiresAt
# { "TimeToLiveSpecification": { "Enabled": true, "AttributeName": "expiresAt" } }

# 10. Clean up the paid caches
aws apigateway update-stage --rest-api-id a1b2c3d4e5 --stage-name prod \
  --patch-operations op=replace,path=/cacheClusterEnabled,value=false
aws dax delete-cluster --cluster-name catalogue-dax
aws elasticache delete-serverless-cache --serverless-cache-name prices

তারপর hit-এর হিসাব দেখো: একই request আর একটা আলাদা request বারবার পাঠাতে পাঠাতে CloudWatch-এ API stage-এর CacheHitCount আর CacheMissCount, আর Valkey cache-এর CacheHits, CacheMisses আর Evictions লক্ষ করো।

#সারসংক্ষেপ

মূল কথাগুলো

  • একটা পথ, চারটা cache: CloudFront (edge) → API Gateway-র stage cache → ElastiCache বা DAX (application/database) → database, আর CloudFront-এর পেছনে S3।
  • লাভ-ক্ষতির হিসাব: user-এর যত কাছে, প্রতি request-এ latency আর খরচ তত কম, কিন্তু invalidate করা তত কঠিন, ডেটা তত পুরোনো। স্তরে স্তরে stale-এর সময় যোগ হয়: সবচেয়ে খারাপ ক্ষেত্রে TTL-গুলোর যোগফল।
  • CloudFront: default TTL ২৪ ঘণ্টা, behaviour ধরে cache key আর cache policy, invalidation (মাসে ১,০০০ path বিনা পয়সায়, তারপর প্রতি path ০.০০৫ USD) বা আরও ভালো নামে version; S3 origin private, OAC দিয়ে।
  • API Gateway cache: শুধু REST API, প্রতি stage-এ, 0.5–237 GB, TTL default ৩০০ s, সর্বোচ্চ ৩,৬০০ s, default-এ শুধু GET, cache key parameter, flush। Cache-Control: max-age=0 দিয়ে bypass-এ execute-api:InvalidateCache লাগে কেবল require authorization চালু থাকলে। Bill ঘণ্টা ধরে, আর request-এর bill তবু হয়: backend-এর সাশ্রয় fee-কে ছাড়ালে তবেই টাকা বাঁচে।
  • ElastiCache: Valkey (সুপারিশ করা), Redis OSS, Memcached, সাথে Serverless। HA, Multi-AZ, backup, pub/sub, leaderboard-এ Redis; সাধারণ, multi-threaded, purely horizontal key-value-তে Memcached।
  • Lazy loading: শুধু চাওয়া ডেটা, কিন্তু miss penalty আর stale read। Write-through: কখনো stale না, কিন্তু write penalty, churn আর খালি নতুন node। দুটো মিলিয়ে চালাও, TTL জোরালোভাবে উচিত (বাধ্যতামূলক না), লেখায় key মুছে দাও।
  • DAX: শুধু DynamoDB, microsecond, API-compatible (client বদলাও), কেবল eventually consistent read, default TTL ৫ মিনিট, তোমার VPC-তে।
  • ElastiCache বা DynamoDB-তে session রাখলে web tier stateless হয়; sticky session state হারায় আর চাপ অসমানভাবে ছড়ায়।
  • Stock-এর সংখ্যা, payment-এর মুহূর্তের balance, OTP, বা shared key-তে user-ভিত্তিক ডেটা কখনো cache কোরো না।