ক্যাশ ইনভ্যালিডেশন ও ইভিকশন পলিসি
এই পাঠে যা শিখবেন
- ইনভ্যালিডেশন ও ইভিকশন — দুটি ভিন্ন সমস্যা কেন ও কীভাবে আলাদা
- তিনটি ইনভ্যালিডেশন স্ট্র্যাটেজি — TTL, Explicit invalidation, Versioning
- তিনটি ইভিকশন পলিসি — LRU, LFU, FIFO — এবং কখন কোনটি উপযুক্ত
- Python দিয়ে
OrderedDict-ভিত্তিক একটি সত্যিকারের LRU ক্যাশ বাস্তবায়ন, ধাপে ধাপে eviction ট্রেস করে
১ · ইনভ্যালিডেশন বনাম ইভিকশন — দুটি ভিন্ন সমস্যা
L19-L20-এ আমরা ক্যাশিং কীভাবে ডেটা রাখে ও ব্যবহার করে তা দেখেছি। কিন্তু দুটি প্রশ্ন এখনও বাকি —
মূল ডেটা বদলে গেলে ক্যাশে থাকা পুরনো কপিটি কীভাবে জানব যে এটি এখন ভুল/স্টেল?
ক্যাশের সীমিত মেমরি (RAM) পূর্ণ হয়ে গেলে নতুন এন্ট্রির জন্য জায়গা করতে কোনটি সরাব?
দুটি সমস্যা স্বাধীন — একটি এন্ট্রি সঠিক (invalid নয়) থাকা সত্ত্বেও ইভিকশন পলিসি সেটি সরিয়ে দিতে পারে (জায়গার অভাবে), আবার একটি এন্ট্রি ক্যাশে জায়গা থাকা সত্ত্বেও ইনভ্যালিডেশন লজিক সেটি সরিয়ে দিতে পারে (কারণ ডেটা বদলে গেছে)।
২ · ইনভ্যালিডেশন স্ট্র্যাটেজি
প্রতিটি এন্ট্রি একটি নির্দিষ্ট সময় পর স্বয়ংক্রিয়ভাবে মেয়াদোত্তীর্ণ হয়ে যায় (যেমন ৬০ সেকেন্ড পর)। সবচেয়ে সহজ বাস্তবায়ন, কিন্তু মেয়াদ শেষ না হওয়া পর্যন্ত ডেটা বদলে গেলেও স্টেল থেকে যায়।
ডেটাবেসে পরিবর্তন হওয়ার সাথে সাথেই অ্যাপ্লিকেশন সক্রিয়ভাবে সংশ্লিষ্ট ক্যাশ এন্ট্রি মুছে দেয় বা আপডেট করে। তাৎক্ষণিক সঠিকতা দেয়, কিন্তু প্রতিটি রাইট পাথে অতিরিক্ত লজিক ও একাধিক ক্যাশ সার্ভারের ক্ষেত্রে সমন্বয় প্রয়োজন।
ক্যাশ কী-এর মধ্যে একটি ভার্সন নম্বর জুড়ে দেওয়া হয় (যেমন
user:42:v3) — ডেটা বদলালে ভার্সন বাড়িয়ে দিলেই পুরনো কী স্বয়ংক্রিয়ভাবে "অকার্যকর" হয়ে যায় (নতুন কী দিয়ে খোঁজা হয়), যদিও পুরনো এন্ট্রি মেমরিতে থেকে যায় যতক্ষণ না ইভিকশন তা সরায়।৩ · ইভিকশন পলিসি
যে এন্ট্রি সবচেয়ে বেশি সময় ধরে অ্যাক্সেস করা হয়নি সেটি সরানো হয়। ধারণা: সাম্প্রতিক অতীতে যা ব্যবহৃত হয়নি, নিকট ভবিষ্যতেও সম্ভবত হবে না। বাস্তবে সবচেয়ে বেশি ব্যবহৃত পলিসি।
যে এন্ট্রি সবচেয়ে কম সংখ্যকবার অ্যাক্সেস হয়েছে সেটি সরানো হয় — সাম্প্রতিকতার বদলে মোট ফ্রিকোয়েন্সি গণনা করে। কোনো আইটেম হঠাৎ জনপ্রিয় হয়ে আবার নিষ্ক্রিয় হয়ে গেলে LRU-এর চেয়ে ভিন্ন সিদ্ধান্ত নিতে পারে।
যে এন্ট্রি সবচেয়ে আগে ক্যাশে ঢুকেছিল সেটি সরানো হয় — অ্যাক্সেস প্যাটার্ন উপেক্ষা করে, শুধু insert-এর ক্রম দেখে। বাস্তবায়ন সবচেয়ে সহজ কিন্তু প্রায়ই সবচেয়ে কম কার্যকর, কারণ একটি সাম্প্রতিক-ব্যবহৃত পুরনো এন্ট্রিও সরিয়ে ফেলতে পারে।
OrderedDict-এ get/put হলে সেই কী move_to_end() দিয়ে পেছনে সরানো হয় — ক্যাপাসিটি ছাড়ালে popitem(last=False) দিয়ে সবচেয়ে সামনের (LRU) এন্ট্রি সরানো হয়।৪ · Python-এ সম্পূর্ণ LRU ক্যাশ বাস্তবায়ন
collections.OrderedDict এন্ট্রির insertion order মনে রাখে এবং move_to_end() দিয়ে
যেকোনো কী-কে "সবচেয়ে সাম্প্রতিক" হিসেবে চিহ্নিত করা যায়। ক্যাপাসিটি ৩ ধরে নিচে একটি ধারাবাহিক get/put সিকোয়েন্স
চালিয়ে দেখা যাক ঠিক কখন এবং কোন কী evict হয়।
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key):
if key not in self.cache:
print(f"GET {key} → মিস (cache-এ নেই, আগেই evict হয়ে গেছে)")
return None
self.cache.move_to_end(key)
print(f"GET {key} → হিট ({self.cache[key]}), নতুন order: {list(self.cache.keys())}")
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
print(f"PUT {key}={value} → order: {list(self.cache.keys())}")
if len(self.cache) > self.capacity:
evicted_key, _ = self.cache.popitem(last=False)
print(f" ⚠ ক্যাপাসিটি ({self.capacity}) অতিক্রম — evict হলো: '{evicted_key}' (least-recently-used)")
lru = LRUCache(3)
lru.put("A", 1)
lru.put("B", 2)
lru.put("C", 3)
lru.get("A")
lru.put("D", 4)
lru.get("B")
lru.put("E", 5)
lru.get("C")
print(f"\nচূড়ান্ত cache order: {list(lru.cache.keys())}")
get("A") A-কে
পেছনে সরায় → [B, C, A]। এরপর put("D") নতুন কী যোগ করে ক্যাপাসিটি ৪-এ পৌঁছায় → সবচেয়ে সামনের (least
recently used) B evict হয় → [C, A, D]। get("B") তাই মিস দেয় (আগেই evict হয়ে গেছে)। এরপর
put("E") আবার ক্যাপাসিটি ছাড়ায় → এই মুহূর্তে সামনে থাকা C evict হয় (C সবচেয়ে বেশি সময় ধরে
অ্যাক্সেস হয়নি — শেষবার শুধু insert হয়েছিল, A ও D পরে অ্যাক্সেস/insert হয়েছে) → [A, D, E]। তাই
get("C")-ও মিস দেয়। প্রতিটি eviction ঠিক সেই মুহূর্তে প্রকৃত least-recently-used কী-কেই
সরিয়েছে — কোনো র্যান্ডম বা ভুল অনুমান নয়।
ইনভ্যালিডেশন ঠিক করে "ডেটা কখন ভুল" আর ইভিকশন ঠিক করে "জায়গার অভাবে কী সরাব" — বাস্তব ক্যাশিং সিস্টেম দুটোই একসাথে ব্যবহার করে (যেমন Redis-এ একটি এন্ট্রির TTL থাকতে পারে, আবার মেমরি পূর্ণ হলে LRU/LFU দিয়ে আগেভাগেও evict হতে পারে)। মডিউল ৫ (ক্যাশিং) এখানেই শেষ — L19-এ আমরা প্যাটার্ন শিখেছি, L20-এ ভৌগোলিক বিস্তার, L21-এ জীবনচক্র ব্যবস্থাপনা। পরের মডিউলে আমরা অ্যাসিনক্রোনাস সিস্টেম (মেসেজ কিউ, ইভেন্ট-ড্রিভেন আর্কিটেকচার) দেখব।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ TTL সবচেয়ে সহজ ইনভ্যালিডেশন স্ট্র্যাটেজি হওয়া সত্ত্বেও এতে একটি "স্টেল উইন্ডো" থাকে — এটি কখন গ্রহণযোগ্য?
যখন ডেটা মাঝে মাঝে সামান্য পুরনো হলেও ব্যবসায়িকভাবে কোনো ক্ষতি হয় না — যেমন একটি আবহাওয়ার পূর্বাভাস (৫ মিনিট পুরনো হলেও সমস্যা নেই) বা একটি প্রোডাক্ট পেজের "স্টক আছে" ব্যাজ (কিছু সেকেন্ড দেরিতে আপডেট হলেও গ্রহণযোগ্য)। TTL-এর সৌন্দর্য হলো এটি explicit invalidation-এর মতো প্রতিটি ডেটা-পরিবর্তনের সাথে সমন্বয় করার জটিলতা ছাড়াই কাজ করে — শুধু "কতক্ষণ স্টেল থাকা সহনীয়" এই একটি সংখ্যা ঠিক করলেই চলে।
প্র ০২ এমন একটি বাস্তব দৃশ্যকল্প দিন যেখানে LFU, LRU-এর চেয়ে ভালো সিদ্ধান্ত নেবে।
ধরুন একটি ভাইরাল পোস্ট এক ঘণ্টায় ১০,০০০ বার অ্যাক্সেস হলো, তারপর হঠাৎ নিষ্ক্রিয় হয়ে গেল — কিন্তু ঠিক পরের মুহূর্তে অন্য একটি সাধারণ পোস্টও একবার অ্যাক্সেস হলো। LRU শুধু "সাম্প্রতিকতা" দেখে, তাই সেই একবার-অ্যাক্সেস-হওয়া সাধারণ পোস্টটিকেই "সবচেয়ে সাম্প্রতিক" ভেবে ভাইরাল পোস্টের বদলে অন্য কিছু (হয়তো ভাইরাল পোস্টের চেয়ে অনেক কম গুরুত্বপূর্ণ একটি এন্ট্রি) সরিয়ে ফেলার ঝুঁকি রাখে না ঠিকই, কিন্তু সময়ের সাথে ভাইরাল পোস্ট আর অ্যাক্সেস না হলে eventually সরে যাবে। LFU এখানে ভাইরাল পোস্টের বিশাল সঞ্চিত ফ্রিকোয়েন্সি কাউন্ট মনে রাখে এবং তাকে অনেক বেশি সময় ধরে ক্যাশে রাখবে, এমনকি সাম্প্রতিক অ্যাক্সেস না থাকলেও — যদি অনুমান করা হয় সেটি আবার জনপ্রিয় হতে পারে।
প্র ০৩ Phil Karlton-এর মতে cache invalidation কেন কম্পিউটার সায়েন্সের সবচেয়ে কঠিন সমস্যাগুলোর একটি, যখন এটি শোনায় সহজ ("যখন ডেটা বদলায়, ক্যাশ মুছে ফেলো")?
বাস্তবে "কখন ডেটা বদলে গেছে তা জানা" নিজেই কঠিন হয়ে ওঠে যখন সিস্টেমটি ডিস্ট্রিবিউটেড — একই ডেটার কপি একাধিক অ্যাপ সার্ভারের লোকাল ক্যাশে, একটি শেয়ার্ড Redis-এ, এবং হাজারো CDN এজে (L20) থাকতে পারে। একটি আপডেট সব জায়গায় একই মুহূর্তে ছড়িয়ে দেওয়া, নেটওয়ার্ক বিলম্ব ও আংশিক ব্যর্থতা সামলে, এবং একইসাথে over-invalidation (অপ্রয়োজনে বারবার ক্যাশ খালি করে পারফরম্যান্স নষ্ট করা) এড়ানো — এই ভারসাম্য বজায় রাখাই আসল চ্যালেঞ্জ, শুধু একটি লাইন কোড লেখা নয়।
অনুশীলন
-
ট্রেস করুন: ক্যাপাসিটি ২ ধরে এই সিকোয়েন্স হাতে ট্রেস করুন:
put(X), put(Y), get(X), put(Z)— প্রতিটি ধাপে order কী হবে এবং শেষ ধাপে কোন কী evict হবে তা লিখুন, তারপর কোড সেলেLRUCache(2)দিয়ে যাচাই করুন।put(X)→ [X]।put(Y)→ [X, Y] (ক্যাপাসিটি ২, এখনও ঠিক আছে)।get(X)→ X পেছনে সরে → [Y, X]।put(Z)→ নতুন কী যোগ হয়ে [Y, X, Z], ক্যাপাসিটি ছাড়ায় → সামনের (least-recently-used) Y evict হয় → চূড়ান্ত order [X, Z]। লক্ষ্য করুন Y evict হলো, X নয় — কারণ X মাঝখানেgetদিয়ে "সাম্প্রতিক" হিসেবে চিহ্নিত হয়েছিল। -
সিদ্ধান্ত নিন: একটি CDN এজ সার্ভারের (L20) থাম্বনেইল ইমেজ ক্যাশ এবং একটি সার্চ অটোকমপ্লিট সিস্টেমের (L49) সাজেশন ক্যাশ — এই দুটির জন্য LRU নাকি LFU বেশি উপযুক্ত হবে বলে মনে হয়?
থাম্বনেইল ক্যাশ: ট্রাফিক প্যাটার্ন প্রায়ই "সাম্প্রতিক ট্রেন্ডিং কন্টেন্ট" অনুসরণ করে — যা এখন জনপ্রিয় তা কালকেও জনপ্রিয় নাও হতে পারে, তাই LRU স্বাভাবিক পছন্দ (সাম্প্রতিক অ্যাক্সেসই সবচেয়ে গুরুত্বপূর্ণ সংকেত)। অটোকমপ্লিট সাজেশন: জনপ্রিয় সার্চ টার্ম (যেমন সাধারণ শব্দ) দীর্ঘমেয়াদে বারবার ফিরে আসে, সাময়িক নিষ্ক্রিয়তা সত্ত্বেও — তাই LFU এখানে বেশি উপযোগী হতে পারে, কারণ এটি দীর্ঘমেয়াদী সামগ্রিক জনপ্রিয়তা মনে রাখে, শুধু সর্বশেষ অ্যাক্সেস নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫১টি পাঠ পরবর্তী পাঠ — মেসেজ কিউ ও Pub/Sub প্যাটার্ন দিয়ে মডিউল ৬ (অ্যাসিনক্রোনাস সিস্টেম) শুরু হবে।
- L20 · CDN ও এজ ক্যাশিং পূর্ববর্তী পাঠ ভৌগোলিকভাবে বিতরণকৃত ক্যাশে ইনভ্যালিডেশন কেন আরও কঠিন হয় তা রিভাইজ করুন।
- L19 · ক্যাশিং স্ট্র্যাটেজি ও প্যাটার্ন মডিউল ৫ শুরু Cache-aside, write-through ও অন্যান্য ক্যাশিং প্যাটার্ন থেকে ক্যাশিং মডিউল শুরু করুন।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics ও System Design — সব এক জায়গায়।