Data & Infrastructure১৩ মিনিট পাঠ

অবজেক্ট স্টোরেজ ডিজাইন

অসীম সংখ্যক অবজেক্ট স্টোর করুন ১১ নাইনস (১১ nines) ডিউরাবিলিটির সাথে — ঠিক AWS S3 এর মতো
scope:রিয়েল-ওয়ার্ল্ড সিস্টেম (Real-World System)difficulty:অ্যাডভান্সড (Advanced)

সমস্যাটি বোঝা (Understanding the Problem)

AWS S3-এর মতো অবজেক্ট স্টোরেজ (Object Storage) সিস্টেমগুলো আপনাকে অসীম সংখ্যক অবজেক্ট — ফাইল, ইমেজ, ব্যাকআপ, লগ — চরম ডিউরাবিলিটির (Durability) সাথে স্টোর করার সুবিধা দেয়। সাধারণ ফাইল সিস্টেম (যেখানে হায়ারার্কি বা স্তর থাকে) কিংবা ব্লক স্টোরেজের (যেখানে ফিক্সড-সাইজ ব্লক থাকে) মতো না হয়ে, অবজেক্ট স্টোরেজ একটি সমতল বা ফ্ল্যাট নেমস্পেস (Flat namespace) ব্যবহার করে: প্রতিটি অবজেক্ট একটি বাকেটে (bucket) থাকে এবং একটি ইউনিক কী (key) দিয়ে তাকে চেনা যায়।

এর মূল লক্ষ্য হলো: ১১ নাইনস ডিউরাবিলিটি (11 nines of durability) (৯৯.৯৯৯৯৯৯৯৯৯%)। এর মানে হলো, আপনি যদি ১০ মিলিয়ন অবজেক্ট স্টোর করেন এবং ১০,০০০ বছর অপেক্ষা করেন, তবে পরিসংখ্যানগতভাবে কেবল ১টি অবজেক্ট হারানোর সম্ভাবনা থাকে।

ফাংশনাল প্রয়োজনীয়তা (Functional Requirements):

  • PUT /bucket/key — একটি অবজেক্ট আপলোড করা (১ KB থেকে ৫ TB পর্যন্ত)।
  • GET /bucket/key — কী (key) ব্যবহার করে একটি অবজেক্ট রিট্রিভ (Retrieve) করা।
  • DELETE /bucket/key — একটি অবজেক্ট ডিলিট বা মুছে ফেলা।
  • বাকেট তৈরি করা, লিস্ট করা এবং ম্যানেজ করা।
  • অবজেক্ট মেটাডেটা (কন্টেন্ট-টাইপ, কাস্টম হেডার, টাইমস্ট্যাম্প)।
  • ভার্সনিং (Versioning) — ওভাররাইট করা অবজেক্টের আগের ভার্সনগুলো সেভ রাখা।
  • অ্যাক্সেস কন্ট্রোল লিস্ট (ACLs) — প্রতিটি বাকেট এবং অবজেক্টের জন্য পারমিশন (Permissions)।

নন-ফাংশনাল প্রয়োজনীয়তা (Non-Functional Requirements):

  • ডিউরাবিলিটি (Durability): ৯৯.৯৯৯৯৯৯৯৯৯% (১১ নাইনস)। ডেটা কোনোভাবেই হারানো যাবে না।
  • অ্যাভেইলেবিলিটি (Availability): ৯৯.৯৯% — সার্ভিসটি প্রায় সবসময়ই সচল থাকতে হবে।
  • অসীম স্কেল (Unlimited scale): পেটাবাইট (Petabytes) পরিমাণ ডেটা এবং বিলিয়ন বিলিয়ন অবজেক্ট সামলানোর সক্ষমতা।
  • স্ট্রং রিড-আফটার-রাইট কনসিস্টেন্সি (Strong read-after-write consistency): একটি PUT রিকোয়েস্টের পরপরই একটি GET রিকোয়েস্ট অবশ্যই সর্বশেষ ভার্সনটি রিটার্ন করবে।
মূল ধারণা: বাকেট, অবজেক্ট এবং মেটাডেটা

অ্যাস্টিমেশন (Estimation)

চলুন একটি বড় আকারের ডিপ্লয়মেন্টের (Deployment) জন্য সিস্টেমটির পরিমাপ করি:

  • মোট স্টোরেজ: সব কাস্টমার মিলিয়ে ১০০ পেটাবাইট (PB)।
  • রাইট থ্রুপুট (Write throughput): চরম ব্যস্ত সময়ে (Peak) প্রতি সেকেন্ডে প্রায় ১ মিলিয়ন PUT রিকোয়েস্ট।
  • রিড থ্রুপুট (Read throughput): পিকে প্রতি সেকেন্ডে প্রায় ১০ মিলিয়ন GET রিকোয়েস্ট (১০:১ রিড-টু-রাইট রেশিও)।
  • অবজেক্ট সাইজ: ১ KB থেকে ৫ TB পর্যন্ত, গড়ে প্রায় ১ MB।
  • অবজেক্টের সংখ্যা: ১০০ PB / ১ MB (গড়) = প্রায় ১০০ বিলিয়ন অবজেক্ট।
  • প্রতি অবজেক্টের মেটাডেটা: প্রায় ১ KB (কী, বাকেট, টাইমস্ট্যাম্প, ACLs, ভার্সন ইনফো) = প্রায় ১০০ TB মেটাডেটা।

মূল বিষয়: মেটাডেটা সার্ভিসকে অবশ্যই ১১ মিলিয়ন QPS (PUT + GET) সামলাতে হবে এবং ডেটা লেয়ারকে বিপুল পরিমাণ বাইট আদান-প্রদান করতে হবে। এই দুটি স্কেলিং চ্যালেঞ্জ একেবারেই আলাদা।

এপিআই ডিজাইন (API Design)

অবজেক্ট স্টোরেজের API-গুলো চমৎকারভাবে সহজ — এটি মূলত HTTP সিম্যান্টিকস ব্যবহার করা একটি কী-ভ্যালু স্টোর:

আপলোড অবজেক্ট (Upload Object)

EndpointPUT /{bucket}/{key}
HeadersContent-Type, Content-Length, x-amz-meta-* (custom metadata)
Bodyঅবজেক্ট ডেটা (বাইনারি)
Response200 OK সাথে ETag (অবজেক্টের MD5 হ্যাশ)

ডাউনলোড অবজেক্ট (Download Object)

EndpointGET /{bucket}/{key}
HeadersRange (আংশিক বা আংশিক ডাউনলোডের জন্য)
Response200 OK অবজেক্ট ডেটাসহ

ডিলিট অবজেক্ট (Delete Object)

EndpointDELETE /{bucket}/{key}
Response204 No Content

হেড অবজেক্ট (Head Object - শুধুমাত্র মেটাডেটা)

EndpointHEAD /{bucket}/{key}
Response200 OK শুধুমাত্র হেডারসহ (বডি ছাড়া) — content-type, size, ETag, last-modified

বড় অবজেক্টগুলোর (৫ GB এর বেশি) জন্য, মাল্টিপার্ট আপলোড (multipart upload) ব্যবহার করুন: আপলোড শুরু করা (initiate), বিভিন্ন অংশ একসাথে আপলোড করা (parallel), এবং আপলোড সম্পূর্ণ করা (complete)। এটি আপলোড পজ করে আবার পুনরায় শুরু করতে এবং প্যারালাল ট্রান্সফার সক্ষম করে।

ডেটা পাথ: কীভাবে অবজেক্ট স্টোর করা হয়
Click chart to zoom
PUT ফ্লো: প্লেসমেন্ট সার্ভিস ৩টি ডেটা নোড বেছে নেয়, প্যারালালে রাইট করে, কোরামের (২/৩) জন্য অপেক্ষা করে এবং তারপর মেটাডেটা রেকর্ড করে

ইরেজার কোডিং এর মাধ্যমে ডিউরাবিলিটি (Durability via Erasure Coding)

ডিউরাবিলিটি অর্জনের একটি সহজ উপায় হলো ৩x রিপ্লিকেশন (3x replication): প্রতিটি অবজেক্টের পুরোপুরি তিনটি কপি আলাদা আলাদা নোডে সেভ রাখা। এটি সহজ ও কার্যকর হলেও, এর খরচ অনেক বেশি — ৩ গুণ স্টোরেজ ওভারহেড। ১০০ PB অবজেক্টের জন্য র (Raw) স্টোরেজ প্রয়োজন হবে ৩০০ PB।

ইরেজার কোডিং (Erasure coding) (বিশেষত Reed-Solomon কোডিং) অনেক বেশি ইফিশিয়েন্ট। এটি যেভাবে কাজ করে:

  1. একটি অবজেক্টকে k টি ডেটা চাঙ্কে ভাগ করা হয় (যেমন, k=4)।
  2. গাণিতিক এনকোডিং (Mathematical encoding) ব্যবহার করে m টি প্যারিটি চাঙ্ক (parity chunks) তৈরি করা হয় (যেমন, m=2)।
  3. সবগুলো (k+m) চাঙ্ক বিভিন্ন নোড বা র‍্যাকে (Racks) ছড়িয়ে দেওয়া হয়।
  4. আসল অবজেক্টটি পুনরায় গঠন করতে যেকোনো k টি চাঙ্ক (k+m থেকে) প্রয়োজন হয়।

একটি ৪+২ স্কিম (4+2 scheme) এর ক্ষেত্রে:

  • আপনি কোনো ডেটা না হারিয়েই যেকোনো ২টি নোড ফেইলর সহ্য করতে পারবেন।
  • স্টোরেজ ওভারহেড: (৪+২)/৪ = ১.৫x (যেখানে রিপ্লিকেশনের জন্য ৩x)।
  • ১০০ PB অবজেক্টের ক্ষেত্রে, যেখানে আগে ৩০০ PB লাগত, সেখানে এখন মাত্র ১৫০ PB স্টোরেজ প্রয়োজন — ১৫০ PB সাশ্রয়!

তবে এর ট্রেড-অফ (Trade-off) হলো: এনকোডিং/ডিকোডিং এর জন্য ইরেজার কোডিংয়ে অনেক বেশি CPU পাওয়ার প্রয়োজন হয় এবং রিড ল্যাটেন্সি (Read latency) বেশি হয় (আপনাকে k টি চাঙ্ক ফেচ করে তারপর রিকনস্ট্রাক্ট বা পুনর্গঠন করতে হবে)। আর এ কারণেই অনেক সিস্টেমে হট/ছোট (Hot/small) অবজেক্টের জন্য রিপ্লিকেশন এবং কোল্ড/বড় (Cold/large) অবজেক্টের জন্য ইরেজার কোডিং ব্যবহৃত হয়।

ইরেজার কোডিং বনাম রিপ্লিকেশন

মেটাডেটা সার্ভিস (Metadata Service)

মেটাডেটা সার্ভিস হলো এই সিস্টেমের ব্রেইন। এটি একটি bucket/key কে ডেটা নোডের ফিজিক্যাল লোকেশনের (চাঙ্কগুলোর) সাথে ম্যাপ করে।

মেটাডেটা যা স্টোর করে:

  • বাকেটের নাম + অবজেক্ট কী → চাঙ্কগুলোর লোকেশন লিস্ট (নোড আইডি, ডিস্ক আইডি, অফসেট)
  • অবজেক্ট সাইজ, কন্টেন্ট টাইপ, ETag, তৈরির সময়
  • ভার্সন হিস্টোরি (ভার্সন করা বাকেটগুলোর জন্য)
  • ACL এবং মালিকানার (Ownership) তথ্য

ডেটা প্লেসমেন্ট (Data placement): অবজেক্টগুলোকে ডেটা নোডের সাথে ম্যাপ করার জন্য কনসিস্টেন্ট হ্যাশিং (consistent hashing) ব্যবহৃত হয়। যখন কোনো নোড যুক্ত বা বাদ হয়, তখন কেবল মাত্র ~1/N কীগুলো (keys) পুনরায় ম্যাপ করতে হয়। একটি প্লেসমেন্ট সার্ভিস এই রিংটি পরিচালনা করে এবং রিব্যালেন্সিং (rebalancing) সামলায়।

গার্বেজ কালেকশন (Garbage collection): যখন কোনো অবজেক্ট ডিলিট করা হয়, তখন আমরা সাথে সাথে ডেটা চাঙ্কগুলো মুছে ফেলি না। এর পরিবর্তে, আমরা মেটাডেটাকে 'ডিলিট করা হয়েছে' (টুম্বস্টোন - tombstone) হিসেবে মার্ক বা চিহ্নিত করি এবং পড়ে একটি ব্যাকগ্রাউন্ড জিসি (Background GC) প্রসেস স্টোরেজ খালি করে। এটি রাইট করার সময় তৎক্ষণাৎ ডিলিটের ভারী প্রক্রিয়া এড়িয়ে চলে এবং ইন-ফ্লাইট রিড (in-flight reads)-এর মতো এজ কেইসগুলো (edge cases) সামলায়।

কনসিস্টেন্সি (Consistency): স্ট্রং রিড-আফটার-রাইট কনসিস্টেন্সি (Strong read-after-write consistency) নিশ্চিত করার জন্য, একটি PUT রিকোয়েস্টের পর ক্লায়েন্টকে সফল মেসেজ জানানোর আগেই মেটাডেটা রাইট (Commit) করতে হয়। মেটাডেটা ডিবি ঐক্যমতের (Consensus) জন্য রাফট (Raft) বা প্যাক্সস (Paxos) ব্যবহার করতে পারে।

পূর্ণাঙ্গ আর্কিটেকচার (Full architecture)
Note: ইন্টারভিউ টিপস: ইরেজার কোডিং হলো সেই মূল পার্থক্য যা একটি সাধারণ 'replicate everything 3x' উত্তর থেকে এটিকে আলাদা করে। স্টোরেজ ইফিসিয়েন্সি (১.৫x বনাম ৩x), সিপিইউ (CPU) ও রিড ল্যাটেন্সির সাথে ট্রেড-অফ (Trade-off) এবং বাস্তব সিস্টেমে কখন রিপ্লিকেশন (হট ডেটার জন্য) ও কখন ইরেজার কোডিং (কোল্ড ডেটার জন্য) ব্যবহার করা হয়, এসব উল্লেখ করুন।

Key Metrics

PUT (ছোট অবজেক্ট)
প্লেসমেন্ট + প্যারালাল রাইটস + মেটাডেটা আপডেট
~১০-৫০ ms \(O(1)\)
GET (ক্যাশ হিট)
মেটাডেটা লুকআপ + সিঙ্গেল নোড রিড
~৫-২০ ms \(O(1)\)
GET (ইরেজার-কোডেড)
k টি চাঙ্ক ফেচ + পুনর্গঠন বা রিকনস্ট্রাক্ট
~৫০-২০০ ms \(O(k)\)
ডিউরাবিলিটি (১১ নাইনস)
ইরেজার কোডিং + ক্রস-র‍্যাক প্লেসমেন্ট
৯৯.৯৯৯৯৯৯৯৯৯% —
স্টোরেজ ইফিসিয়েন্সি (EC ৪+২)
৩x রিপ্লিকেশনের তুলনায়
১.৫x ওভারহেড —
টোটাল ক্যাপাসিটি
হরিজন্টালি স্কেলেবল ডেটা নোড
১০০+ PB —

ছোট কুইজ

ইরেজার কোডিং (যেমন, ৪+২) একই মাত্রার ডিউরাবিলিটি বজায় রেখে কীভাবে ৩x রিপ্লিকেশনের চেয়ে ভালো স্টোরেজ ইফিসিয়েন্সি অর্জন করে?

পড়া চালিয়ে যান