পাঠ ৩৪ · ৫৭-এর মধ্যে · মডিউল ৮
Home / Courses / Mobile App Development / লোকাল স্টোরেজ

ফাইল স্টোরেজ ও ক্যাশিং স্ট্র্যাটেজি

File storage & caching strategies
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • কী-ভ্যালু, SQL ও ফাইল স্টোরেজ — কোনটা কখন ব্যবহার করবেন
  • ক্যাশ এভিকশন কেন দরকার (সীমিত ডিস্ক/মেমোরি জায়গা)
  • LRU কৌশলের পেছনের যুক্তি — "সাম্প্রতিক ব্যবহার = ভবিষ্যতে আবার লাগার সম্ভাবনা বেশি"
  • একটি সত্যিকারের LRU ক্যাশ বাস্তবায়ন, যেখানে অ্যাক্সেস (শুধু ইনসার্ট নয়) সত্যিই এভিকশনের সিদ্ধান্ত বদলে দেয়

১ · তিন ধরনের লোকাল স্টোরেজ

L32 আর L33-এ আমরা কী-ভ্যালু আর SQL-স্টাইল স্টোরেজ দেখেছি — দুটোই ছোট, স্ট্রাকচার্ড ডেটার জন্য। কিন্তু একটি ছবি, ডাউনলোড করা ভিডিও, বা ক্যাশ করা নেটওয়ার্ক রেসপন্স — এগুলো বড় বাইনারি ব্লব, আর এগুলো ডিকশনারি বা টেবিলের একটি কলামে রাখা অদক্ষ। এর বদলে এগুলো সরাসরি ডিভাইসের ফাইল স্টোরেজেFile Storageঅ্যাপের নিজস্ব স্যান্ডবক্সড ডিরেক্টরিতে সরাসরি ফাইল হিসেবে রাখা বড় বাইনারি ডেটা — ছবি, ভিডিও, ডাউনলোড করা নথি। রাখা হয়।

কী-ভ্যালু
ছোট, সমতল সেটিংস — থিম, ফ্ল্যাগ, একক মান। (L32)
SQL
স্ট্রাকচার্ড, সম্পর্কযুক্ত রেকর্ড — নোট, মেসেজ, কন্টাক্ট। (L33)
ফাইল স্টোরেজ
বড় বাইনারি ব্লব — ছবি, ভিডিও, PDF, ক্যাশ করা API রেসপন্স। iOS-এ Documents/Caches ডিরেক্টরি, Android-এ ইন্টারনাল/এক্সটার্নাল স্টোরেজ।

২ · ক্যাশিং ও LRU এভিকশন

ডিভাইসের স্টোরেজ সসীম, তাই ফাইল ক্যাশ অসীমভাবে বাড়তে দেওয়া যায় না। একটি ফিক্সড ক্যাপাসিটির ক্যাশ পূর্ণ হয়ে গেলে নতুন কিছু ঢোকাতে পুরনো কিছু সরাতেই হয় — প্রশ্ন হলো কোনটা সরাবেন। LRU কৌশলের যুক্তি সহজ: যে এন্ট্রি সবচেয়ে বেশিদিন ধরে অ্যাক্সেস হয়নি, সেটিই ভবিষ্যতে সবচেয়ে কম দরকার হওয়ার সম্ভাবনা — তাই সেটিকেই আগে সরানো হয়। লক্ষ্য করুন এটি "সবচেয়ে পুরনো ইনসার্ট করা" নয় — একটি পুরনো এন্ট্রি যদি সদ্য আবার অ্যাক্সেস হয়, সেটি আর LRU থাকে না।

Python
# একটি ফিক্সড-ক্যাপাসিটি LRU ক্যাশ -- dict (মান) + list (অ্যাক্সেস-অর্ডার)
# self._order[0]  = সবচেয়ে কম সম্প্রতি ব্যবহৃত (LRU, এভিকশনের প্রথম শিকার)
# self._order[-1] = সবচেয়ে সম্প্রতি ব্যবহৃত (MRU)

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self._data = {}
        self._order = []

    def _touch(self, key):
        if key in self._order:
            self._order.remove(key)
        self._order.append(key)   # এখন সবচেয়ে সম্প্রতি ব্যবহৃত

    def get(self, key):
        if key not in self._data:
            print(f"GET  {key!r:28s} -> মিস (ক্যাশে নেই)")
            return None
        self._touch(key)
        print(f"GET  {key!r:28s} -> হিট   | অ্যাক্সেস-অর্ডার এখন: {self._order}")
        return self._data[key]

    def put(self, key, value):
        if key in self._data:
            self._data[key] = value
            self._touch(key)
            print(f"PUT  {key!r:28s} -> আপডেট | অ্যাক্সেস-অর্ডার এখন: {self._order}")
            return
        if len(self._data) >= self.capacity:
            evicted = self._order.pop(0)      # সবচেয়ে কম সম্প্রতি ব্যবহৃত-কে সরানো হলো
            del self._data[evicted]
            print(f"PUT  {key!r:28s} -> ক্যাশ পূর্ণ! LRU এন্ট্রি evict হলো: {evicted!r}")
        self._data[key] = value
        self._order.append(key)
        print(f"PUT  {key!r:28s} -> যোগ হলো | অ্যাক্সেস-অর্ডার এখন: {self._order}")

# ---------- ধারণক্ষমতা ৩ -- ইমেজ ক্যাশ সিমুলেশন ----------
cache = LRUCache(capacity=3)

cache.put("home_banner.jpg", "(2.1 MB ছবি ডেটা)")
cache.put("product_42.jpg", "(1.4 MB ছবি ডেটা)")
cache.put("avatar_user7.jpg", "(0.3 MB ছবি ডেটা)")   # ক্যাশ এখন পূর্ণ (৩/৩)

cache.get("home_banner.jpg")                          # home_banner-কে আবার অ্যাক্সেস -- এটি এখন MRU

cache.put("banner_promo.jpg", "(1.8 MB ছবি ডেটা)")    # নতুন এন্ট্রি -- কাউকে evict করতে হবে

cache.get("avatar_user7.jpg")                          # avatar-কে আবার অ্যাক্সেস -- এটি এখন MRU

cache.put("cover_photo.jpg", "(2.5 MB ছবি ডেটা)")     # আবার নতুন এন্ট্রি -- আবার evict

print(f"\nবর্তমান ক্যাশে থাকা কী-গুলো (dict-এর নিজস্ব ইনসার্শন-অর্ডারে): {list(cache._data.keys())}")
print(f"প্রকৃত অ্যাক্সেস-অর্ডার (LRU -> MRU): {cache._order}")

    
হাতে-কলমে ট্রেস করলে দেখা যায় কেন এটি সত্যিকারের LRU, শুধু "সবচেয়ে পুরনো" নয়। প্রথম তিনটি put-এর পর অ্যাক্সেস-অর্ডার ছিল [home_banner, product_42, avatar_user7] (ইনসার্শন-অর্ডার অনুযায়ী)। কিন্তু তারপর cache.get("home_banner.jpg") কল হওয়ায় home_banner-কে অর্ডার থেকে সরিয়ে শেষে বসানো হয় — অর্ডার হয়ে যায় [product_42, avatar_user7, home_banner]। তাই যখন banner_promo.jpg ঢোকাতে একটি এন্ট্রি evict করতে হয়, তখন সরানো হয় product_42.jpg — home_banner.jpg নয়, যদিও home_banner.jpg-ই সবার আগে ইনসার্ট হয়েছিল। এটাই প্রমাণ করে ক্যাশটি প্রকৃত অ্যাক্সেস-অর্ডার ট্র্যাক করছে, ইনসার্শন-অর্ডার নয়। একই যুক্তিতে দ্বিতীয় এভিকশনে (cover_photo.jpg ঢোকানোর সময়) সরানো হয় home_banner.jpg-কেই — কারণ তখন সেটিই সবচেয়ে বেশিদিন ধরে অ্যাক্সেস হয়নি (avatar_user7.jpg এর মধ্যেই আবার অ্যাক্সেস হয়ে গেছে)। শেষের দুটি প্রিন্ট লাইনও লক্ষ্য করুন — cache._data.keys() Python dict-এর নিজস্ব ইনসার্শন-অর্ডার মেনে চলে, যা আমাদের হাতে-রাখা cache._order লিস্টের (প্রকৃত অ্যাক্সেস-অর্ডার) থেকে আলাদা — এই দুটো গুলিয়ে ফেলা একটি সাধারণ ভুল।
মূল কথা · Key takeaway

একটি সঠিক LRU ক্যাশে "কোনটা সরাতে হবে" এই সিদ্ধান্তটা ইনসার্শনের সময় নয়, শেষবার অ্যাক্সেসের সময় দিয়ে নেওয়া হয় — তাই প্রতিটি get-কেও (শুধু put নয়) অর্ডার আপডেট করতে হয়। মোবাইলে এই কৌশলটিই ইমেজ-লোডিং লাইব্রেরি (যেমন ছবি ক্যাশিং) ব্যবহার করে সীমিত ডিস্ক জায়গায় সবচেয়ে প্রাসঙ্গিক কনটেন্ট রেখে দেয়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ _touch মেথডে প্রথমে self._order.remove(key) তারপর self._order.append(key) — এই দুই ধাপ কেন দরকার, শুধু append করলে হতো না?

যদি শুধু append করা হতো, একই কী অর্ডার লিস্টে দুইবার (পুরনো অবস্থানে ও নতুন অবস্থানে) থেকে যেত — তখন এভিকশনের সময় pop(0) ভুল (বা ইতিমধ্যে ক্যাশ থেকে সরানো হয়ে যাওয়া) একটি কী ফেরত দিতে পারত। আগে remove(key) করে পুরনো অবস্থান মুছে তারপর শেষে append করলে প্রতিটি কী অর্ডার লিস্টে ঠিক একবারই থাকে, সবসময় তার সবশেষ অ্যাক্সেসের অবস্থানে।

প্র ০২ যদি get মেথডে self._touch(key) কলটি বাদ দেওয়া হতো, তাহলে ক্যাশটি আসলে কোন কৌশলে পরিণত হতো?

এটি তখন আসলে একটি FIFO (First-In-First-Out) ক্যাশে পরিণত হতো — শুধু ইনসার্শনের ক্রম অনুযায়ী সবচেয়ে পুরনো এন্ট্রি সরানো হতো, অ্যাক্সেস প্যাটার্ন যাই হোক না কেন। কোড সেলের উদাহরণেই তখন home_banner.jpg-কে get করা সত্ত্বেও সেটিই প্রথমে evict হতো, যা "সাম্প্রতিক ব্যবহারকে গুরুত্ব দেওয়া" LRU-এর মূল উদ্দেশ্যকে নস্যাৎ করে দেয়।

প্র ০৩ কোড সেলে cache.get("home_banner.jpg") কল করার সময় সেই কী ইতিমধ্যে evict হয়ে গেলে কী হবে?

get মেথডে প্রথমেই if key not in self._data চেক হয় — evict হয়ে যাওয়া একটি কী self._data-তে আর নেই, তাই সরাসরি "GET ... -> মিস (ক্যাশে নেই)" প্রিন্ট হয়ে None ফেরত আসবে, এবং _touch কলই হবে না — একটি evict হওয়া কী পুনরায় "সম্প্রতি ব্যবহৃত" হিসেবে গণ্য হয় না।

অনুশীলন

  1. চিন্তা করুন: একটি নিউজ-অ্যাপে সর্বশেষ ২০টি খবরের ছবি ক্যাশ করা থাকে। ইউজার একটি পুরনো খবরে ফিরে গিয়ে সেটির ছবি আবার দেখলে LRU কৌশল অনুযায়ী কী ঘটবে বলে মনে করেন?

    সেই পুরনো ছবিটি অ্যাক্সেস হওয়ার সাথে সাথে ক্যাশের অর্ডার লিস্টে সেটি "সবচেয়ে সম্প্রতি ব্যবহৃত" (MRU) অবস্থানে চলে যাবে — অর্থাৎ পরবর্তী কোনো এভিকশনে সেটিই সবার শেষে সরানো হবে, যদিও এটি মূলত পুরনো একটি এন্ট্রি ছিল। এটাই LRU-এর মূল সুবিধা — "সম্প্রতি প্রাসঙ্গিক" ডেটা ক্যাশে টিকে থাকে, শুধু "নতুন যোগ করা" ডেটা নয়।

  2. পরীক্ষা করুন: কোড সেলে cache = LRUCache(capacity=3)-কে capacity=2-এ বদলে দিন, এবং cache.get("avatar_user7.jpg") লাইনটি মুছে দিন — তারপর ভাবুন (এবং রান করে যাচাই করুন) শেষের cache.put("cover_photo.jpg", ...) কল এবার কোন এন্ট্রি evict করবে।

    ধারণক্ষমতা ২-এ প্রথম দুটি put (home_banner, product_42)-এর পরই ক্যাশ পূর্ণ হয়ে যাবে, তাই avatar_user7.jpg ইনসার্ট করার সময়ই প্রথম এভিকশন ঘটবে (LRU তখন home_banner, কারণ get কল হয়নি এখনো) — অর্ডার পরিবর্তিত হয়ে যাবে, এবং এরপর banner_promo.jpg, তারপর cover_photo.jpg প্রতিটিতেই নতুন এভিকশন ঘটবে। কম ধারণক্ষমতা মানে বেশি ঘন ঘন এভিকশন — এটাই মূল ট্রেড-অফ।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • পরের পাঠ L35 মোবাইলে ডেটা মাইগ্রেশন — সময়ের সাথে লোকাল স্কিমা কীভাবে নিরাপদে বদলাবেন।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।
আগের পাঠ
মোবাইলে লোকাল SQL ডেটাবেস