এই পর্বে যা শিখবেন
- Block, file আর object storage-এর পার্থক্য — কোন ডেটা কোথায় রাখবেন, আর কেন
- Bucket, key, metadata, versioning, lifecycle আর S3-ধাঁচের API — নিরাপদ presigned URL আর multipart upload
- একটি distributed object store-এর ভেতর: metadata, chunk, placement, replication আর erasure coding-এর অঙ্ক
- Bit rot, scrubbing, মেরামত আর “এগারো nine” durability-র দাবির আসল অর্থ
- GFS-HDFS-এর নকশা, আর একটি ছবির অ্যাপের আপলোড ও file-sync সেবার নকশা
১ · Block, file আর object — তিন রকম storage, তিন রকম প্রতিশ্রুতি
ধরুন একটি ছবি-শেয়ারের অ্যাপ (কাল্পনিক) প্রতিদিন লাখো ছবি পায়। App server-এর disk-এ রাখা সহজ, কিন্তু দ্বিতীয় server যোগ হলেই এক server-এর ছবি অন্যটি দেখে না (পর্ব ১৩); disk ভরলে বা যন্ত্র নষ্ট হলে আরও বিপদ। আসল প্রশ্ন: কোন ধরনের storage, কোন interface দিয়ে?
তিনটি জায়গা ভাবুন। Block storage একটি খালি গুদামঘর, তাক নম্বর ১ থেকে ১০,০০০; কোন জিনিস কোন তাকে, তার খাতা আপনার। File storage অফিসের আলমারি — ফোল্ডার আর ফাইল; অনেকে একসাথে পাতা বদলাতে, তালা দিতে পারেন। Object storage রেলস্টেশনের লাগেজ-ঘর: ব্যাগ জমা, টোকেন হাতে; টোকেন দেখালে পুরো ব্যাগ ফেরত। ভেতরের একটি শার্ট বদলাতে পুরো ব্যাগ আবার জমা দিতে হয় — বিনিময়ে লাগেজ-ঘর প্রায় অসীম বড় করা যায়, কারণ কাউকে ব্যাগের ভেতরের সাজানো মনে রাখতে হয় না।
| দিক | Block | File | Object |
|---|---|---|---|
| Interface | নম্বর ধরে পড়া-লেখা, যেমন block ৫০০-তে ৪ KiB | path, open, read, write, seek, rename, lock (POSIX) | HTTP-তে PUT, GET, DELETE, LIST — key ধরে পুরো object |
| বোঝে কী | কিছুই না; ওপরের file system বা database সব সাজায় | নাম, directory, অনুমতি, lock | key, বাইট আর metadata |
| সাধারণ ব্যবহার | database-এর disk, VM-এর boot disk | কয়েকটি server-এর ভাগ করা folder, পুরোনো অ্যাপ | ছবি, ভিডিও, backup, log, data lake, static ওয়েবসাইট |
| বাড়ানো | এক volume সাধারণত এক যন্ত্রে; latency কম | directory, lock আর rename-এর সমন্বয় বাড়ানো কঠিন | প্রায় সীমাহীন — শত কোটি object, PB মাপের; প্রতি অনুরোধে latency বেশি |
| উদাহরণ | cloud-এর disk volume, SAN | NFS, SMB, managed file share | S3 আর S3-সমর্থক store (MinIO, Ceph), অন্য cloud-এর blob storage |
ছবি, ভিডিও, backup আর log কেন object storage-এ? (১) একবার লেখা, বহুবার পড়া, প্রায় কখনো বদলানো নয়। (২) আকার বড়, সংখ্যা বিপুল — সমতল নাম আর ছড়ানো chunk দিয়ে সহজে বাড়ে। (৩) HTTP-তে সরাসরি পড়া যায়, তাই এটি CDN-এর স্বাভাবিক origin (পর্ব ১৭)। (৪) প্রতি GB খরচ কম, ঠান্ডা স্তরে আরও কম। (৫) App server stateless থাকে। কী রাখবেন না: ঘনঘন অল্প অংশ বদলানো ডেটা (database-এর page), ছোট ছোট transaction, lock লাগে এমন ভাগ করা ফাইল — সেগুলো block storage-এর ওপর database-এ (পর্ব ১৯, পর্ব ২০)।
প্রতিশ্রুতি যত কম, বাড়ানো তত সহজ। File storage নাম, folder, lock, আংশিক লেখা আর atomic rename — অনেক কিছুর প্রতিশ্রুতি দেয়, তাই বহু যন্ত্রে ছড়ানো কঠিন। Object storage ইচ্ছা করে প্রতিশ্রুতি কমায় — rename নেই, আংশিক লেখা নেই, lock নেই — আর বিনিময়ে বিপুল আকার কেনে। File system-এর ভেতরের কাজ OS কোর্সের ফাইল interface আর ফাইল সিস্টেমের বাস্তবায়ন পর্বে।
২ · Object-এর মডেল — bucket, key, metadata, version আর lifecycle
Bucket হলো object-এর পাত্র; অনুমতি, region, versioning আর lifecycle-এর নিয়ম সাধারণত bucket-এ বসে। অনেক সেবায় bucket-এর নাম পুরো সেবায় অনন্য হতে হয়। Bucket-এর ভেতরে প্রতিটি object-এর পুরো নাম তার key, যেমন photos/u91/2026/a7f3.jpg.
এখানে একটি বড় সত্য: / চিহ্নের কোনো বিশেষ অর্থ নেই। Namespace সমতল (flat) — শুধু key থেকে object-এর একটি বিশাল সাজানো তালিকা। Console-এ যে “folder” দেখেন, তা key-এর শুরুর অংশ (prefix) ধরে সাজানো একটি দৃশ্য। LIST-এ prefix আর delimiter দিলে store “common prefix” ফেরত দেয়, তাই folder-এর মতো দেখায়:
GET /?list-type=2&prefix=photos/u91/&delimiter=/&max-keys=1000 HTTP/1.1
Host: my-bucket.store.example.com
HTTP/1.1 200 OK
<ListBucketResult>
<Contents><Key>photos/u91/profile.jpg</Key><Size>48211</Size></Contents>
<CommonPrefixes><Prefix>photos/u91/2025/</Prefix></CommonPrefixes>
<CommonPrefixes><Prefix>photos/u91/2026/</Prefix></CommonPrefixes>
<IsTruncated>false</IsTruncated>
</ListBucketResult>ফল তিনটি। “Folder” rename মানে প্রতিটি object কপি করে পুরোনোটি মোছা — লাখো অনুরোধ, আর মাঝপথে থামলে অর্ধেক পুরোনো নামে। খালি folder বলে কিছু নেই; console শূন্য-byte-এর object দিয়ে নকল করে। আর LIST ধীর — অঙ্ক অনুশীলন ৩-এ।
Object = বাইট + metadata, আর বাইট বদলায় না
প্রতিটি object-এর সাথে থাকে metadata: system-এর (আকার, Content-Type, শেষ বদলের সময়, ETag, storage class) আর আপনার নিজের ছোট key-value (সাধারণত কয়েক KB-র সীমা)। বাইটগুলো অপরিবর্তনীয় (immutable): PUT পুরো object একবারে বদলে দেয় — পাঠক পুরোনোটি বা নতুনটি দেখেন, অর্ধেক-লেখা কখনো নয়। সাধারণ object-এ মাঝখানে লেখা বা শেষে জোড়া (append) নেই; কিছু নতুন storage class-এ সীমিত append যোগ হয়েছে (লেখার সময়ে)।
নকশার কৌশল: key-এ একটি অনন্য id বা বিষয়বস্তুর hash রাখুন (thumb/a7f3-400w.webp), তাহলে একই key-এ কখনো দ্বিতীয়বার লেখা হয় না। Key কখনো না বদলালে CDN সেটি নিশ্চিন্তে দীর্ঘদিন রাখে — পর্ব ১৭-এর asset fingerprinting-এর একই চিন্তা।
Versioning আর lifecycle
Versioning চালু থাকলে প্রতিটি PUT একটি নতুন version id বানায়, পুরোনো version থেকে যায়। Version না বলে DELETE করলে object মোছে না, ওপরে একটি delete marker বসে — ভুল মোছা ফেরানো যায়; নির্দিষ্ট version মুছলে তবেই চিরতরে যায়। দাম: প্রতিটি পুরোনো version জায়গা নেয়। তাই lifecycle rule দিয়ে পুরোনো version কয়েক সপ্তাহ পরে মোছা হয়, আর পুরোনো ডেটা সস্তা storage class-এ সরানো হয়:
| স্তর | জমা রাখার খরচ | পড়ার গতি ও খরচ | উপযুক্ত ডেটা |
|---|---|---|---|
| Hot (standard) | সবচেয়ে বেশি | ms-এ, পড়ায় বাড়তি খরচ সাধারণত নেই | সদ্য আপলোড, জনপ্রিয় ছবি |
| Cool (infrequent access) | কম | দ্রুত, কিন্তু প্রতি GB পড়ায় খরচ; প্রায়ই ন্যূনতম রাখার মেয়াদ | মাসখানেক পুরোনো ছবি, সাম্প্রতিক backup |
| Archive (cold) | সবচেয়ে কম | ফেরত আনতে কয়েক মিনিট থেকে কয়েক ঘণ্টা; পড়া দামি | বছরের পুরোনো log, আইনের কারণে রাখা নথি |
{ "Rules": [ {
"ID": "photos-age-out",
"Filter": { "Prefix": "orig/" },
"Transitions": [ { "Days": 30, "StorageClass": "COOL" },
{ "Days": 365, "StorageClass": "ARCHIVE" } ],
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
} ] }স্তরের নাম, দাম, ন্যূনতম মেয়াদ আর ফেরত আনার সময় vendor আর সময়ভেদে বদলায় — হিসাবের আগে নথি দেখুন। নকশার প্রশ্নটি স্থির: এই ডেটা কত ঘনঘন পড়া হবে, আর দেরিতে পেলে ক্ষতি কত? কতদিন রাখবেন আর কখন মুছবেন, সেটি retention policy-র প্রশ্ন।
৩ · S3-ধাঁচের API — PUT, GET, LIST, multipart upload আর presigned URL
২০০৬ সালে Amazon S3 চালুর পর তার API কার্যত মানদণ্ড হয়ে গেছে; MinIO, Ceph-এর মতো open-source store-ও একই ধাঁচের API দেয়। মূল কাজ অল্প কয়েকটি:
| কাজ | কী করে | মনে রাখার কথা |
|---|---|---|
PUT key | পুরো object লেখা বা বদলানো | একবারে দৃশ্যমান; একই key-এ দুজন লিখলে শেষজন জেতে |
GET key | পুরো object, বা Range দিয়ে একটি অংশ | অংশ চাইলে 206 Partial Content — range request, ভিডিওর মাঝখানে লাফ এভাবেই |
HEAD key | শুধু metadata | আকার, ETag, ধরন যাচাইয়ে সস্তা |
DELETE key | মোছা, বা versioning-এ delete marker | নেই এমন key মুছলেও সাধারণত সফল — idempotent |
LIST prefix | key-এর সাজানো তালিকা, পাতায় পাতায় | প্রতি পাতায় সর্বোচ্চ ১,০০০টি, পরের পাতার জন্য continuation token — পর্ব ৮-এর cursor |
Multipart upload — বড় ফাইল টুকরো করে
একটি ৩ GiB ভিডিও এক অনুরোধে পাঠালে মাঝপথে network কাটলে আবার শূন্য থেকে, আর একটি TCP সংযোগ পুরো link ভরাতে পারে না। Multipart upload-এ তিন ধাপ: Create (একটি uploadId), UploadPart (part নম্বর ধরে আলাদা অনুরোধে, সমান্তরালে; প্রতিটি দেয় একটি ETag) আর Complete (part নম্বর আর ETag-এর তালিকা) — তখনই object একবারে দৃশ্যমান। লেখার সময়ে S3-এর নথিতে: শেষটি ছাড়া প্রতিটি part অন্তত ৫ MiB, সর্বোচ্চ ৫ GiB, আর সর্বোচ্চ ১০,০০০টি part; অন্য store-এ আলাদা হতে পারে। ফাঁদ: অসমাপ্ত upload-এর part চোখে পড়ে না, কিন্তু জায়গা নেয় — তাই Abort, আর lifecycle দিয়ে কয়েক দিন পরে নিজে থেকে মোছা।
Presigned URL — চাবি না দিয়ে একটি কাজের অনুমতি
ফাইল app server দিয়ে পাঠালে তার bandwidth, memory আর সময় খরচ হয়। বিকল্প presigned URL: server নিজের গোপন চাবি দিয়ে একটি URL সই করে (HMAC-ভিত্তিক), যার ভেতরে method, bucket, key আর মেয়াদ। যার হাতে URL, তিনি চাবি না জেনেই ঠিক সেই কাজটি করতে পারেন; একটি অক্ষর বদলালে সই আর মেলে না।
Presigned URL একটি bearer token — যার হাতে, তারই অনুমতি। তাই প্রতিরক্ষার নিয়ম:
- মেয়াদ ছোট — কয়েক মিনিট। লেখার সময়ে S3-এর সই-পদ্ধতিতে সর্বোচ্চ ৭ দিন দেওয়া যায়, কিন্তু দরকারের বেশি নয়; যে সাময়িক credential দিয়ে সই, সেটি আগে ফুরোলে URL-ও আগে অচল।
- পরিধি সংকীর্ণ — একটি key, একটি method. Key বানাবে server, client-এর দেওয়া path নয়; নইলে কেউ অন্যের ফাইলের ওপর লিখতে পারেন। আকার আর Content-Type-ও সইয়ের ভেতরে বাঁধুন (POST policy-তে আকারের সীমা দেওয়া যায়)।
- ফাঁস রোধ — শুধু HTTPS; URL log, analytics বা Referer-এ যেতে পারে, তাই log-এ query string কেটে রাখুন।
- আপলোডের পরে বিশ্বাস নয়, যাচাই — আসল আকার, বিষয়বস্তুর ধরন (ফাইলের প্রথম কয়েক byte দেখে), malware scan; তারপর “ready”. Bucket private, public access বন্ধ; পড়ার জন্যও স্বল্পমেয়াদি URL বা CDN-এর signed URL. বিস্তারিত পর্ব ৫২ — নকশায় নিরাপত্তা।
এবার দুটি মিলিয়ে একটি ২০০ MiB ভিডিও আপলোড, part ৬৪ MiB — তাই ৪টি part: ৬৪ + ৬৪ + ৬৪ + ৮ MiB.
| Part-এর অবস্থা | |
| পৌঁছেছে | |
| GET করলে |
- অনুমতি চাওয়া। অ্যাপ API-কে বলল: ২০০ MiB-এর একটি video/mp4 পাঠাতে চাই। API লগইন আর quota দেখে নিজেই key বানাল (
orig/u91/7f3a.mp4) আর database-এ একটি সারি লিখল — status = pending. Store-এ কিছুই নেই, GET দিলে 404. - Multipart শুরু, চারটি URL সই। API store-এ Create ডাকল, পেল uploadId. তারপর ৪টি URL সই করল — প্রতিটিতে শুধু PUT, শুধু এই key, এই uploadId আর একটি part নম্বর, মেয়াদ ১৫ মিনিট। গোপন চাবি server-এর বাইরে যায়নি।
- দুটি part একসাথে, সরাসরি store-এ। অ্যাপ part ১ আর ২ সমান্তরালে PUT করল, পেল ETag e1, e2. ১২৮ MiB পৌঁছেছে, কিন্তু GET-এ এখনো 404 — অসমাপ্ত part কেউ দেখে না। App server দিয়ে একটি byte-ও যায়নি।
- Part ৩ ব্যর্থ, part ৪ সফল। Network কেটে part ৩ মাঝপথে থামল; ছোট part ৪ (৮ MiB) শেষ, ETag e4. পৌঁছেছে ১২৮ + ৮ = ১৩৬ MiB. এক অনুরোধে পাঠালে পুরো ২০০ MiB আবার পাঠাতে হতো।
- শুধু part ৩ আবার। অ্যাপ শুধু part ৩ (৬৪ MiB) আবার পাঠাল; মেয়াদ ফুরোলে API থেকে নতুন URL নিত। ২০০ / ২০০ MiB, ETag e3 — কিন্তু Complete-এর আগে object নেই।
- Complete — একবারে দৃশ্যমান। API store-কে চার জোড়া (part নম্বর, ETag) দিয়ে Complete ডাকল। Object একবারে দেখা গেল, অর্ধেক অবস্থা কেউ দেখেনি। Database-এ status = uploaded, আর store-এর event থেকে পরের কাজ (অংশ ৯) শুরু।
Part কত বড় হবে, কয়টি একসাথে? নিচের planner-এ একটি ৩ GiB ভিডিও, ৪০ Mbit/s-এর uplink (ধরা যাক), আর একটি TCP সংযোগ একা যে গতি পায় তার সীমা:
// Multipart upload planner. ASSUMED inputs: change them and run again.
const MiB = 1024 * 1024, GiB = 1024 * MiB;
const FILE = 3 * GiB; // a 3 GiB video from a phone
const LINK_MBIT = 40; // uplink capacity, Mbit/s
const STREAM_MBIT = 12; // one TCP connection alone reaches ~12 Mbit/s (RTT, window)
const PER_PART_S = 0.3; // per-part overhead: request, signing, response (seconds)
const MAX_PARTS = 10000; // S3-style limit at the time of writing; check your vendor
const MIN_PART = 5 * MiB; // every part except the last must be at least this big
const fmt = (s) => (s >= 60 ? (s / 60).toFixed(1) + " min" : s.toFixed(1) + " s");
console.log(`file ${FILE / GiB} GiB, link ${LINK_MBIT} Mbit/s, one stream ${STREAM_MBIT} Mbit/s`);
console.log("part parts max file streams total time resend if 1 part fails");
for (const partMiB of [5, 16, 64, 256]) {
const part = partMiB * MiB, parts = Math.ceil(FILE / part);
if (part < MIN_PART || parts > MAX_PARTS) { console.log(partMiB + " MiB: not allowed"); continue; }
const maxFile = (MAX_PARTS * part / GiB).toFixed(0) + " GiB";
for (const streams of [1, 4, 8]) {
const mbit = Math.min(LINK_MBIT, streams * STREAM_MBIT); // parallel streams share the link
const total = FILE * 8 / (mbit * 1e6) + Math.ceil(parts / streams) * PER_PART_S;
const resend = part * 8 / (STREAM_MBIT * 1e6); // the failed part goes again
console.log((partMiB + " MiB").padEnd(8) + String(parts).padEnd(7) + maxFile.padEnd(11) +
String(streams).padEnd(9) + fmt(total).padEnd(12) + fmt(resend));
}
}আউটপুট:
file 3 GiB, link 40 Mbit/s, one stream 12 Mbit/s
part parts max file streams total time resend if 1 part fails
5 MiB 615 49 GiB 1 38.9 min 3.5 s
5 MiB 615 49 GiB 4 11.5 min 3.5 s
5 MiB 615 49 GiB 8 11.1 min 3.5 s
16 MiB 192 156 GiB 1 36.8 min 11.2 s
16 MiB 192 156 GiB 4 11.0 min 11.2 s
16 MiB 192 156 GiB 8 10.9 min 11.2 s
64 MiB 48 625 GiB 1 36.0 min 44.7 s
64 MiB 48 625 GiB 4 10.8 min 44.7 s
64 MiB 48 625 GiB 8 10.8 min 44.7 s
256 MiB 12 2500 GiB 1 35.9 min 3.0 min
256 MiB 12 2500 GiB 4 10.8 min 3.0 min
256 MiB 12 2500 GiB 8 10.7 min 3.0 min
সমান্তরাল stream-ই বড় লাভ: একটি stream-এ প্রায় ৩৬–৩৯ মিনিট, কারণ একটি সংযোগ ১২ Mbit/s-এর বেশি পায় না; ৪টিতে link ভরে, প্রায় ১১ মিনিট; ৮টিতে আর লাভ নেই। Part-এর আকার মোট সময়ে সামান্য (৪ stream-এ ৫ MiB-এ ১১.৫, ৬৪ MiB-এ ১০.৮ মিনিট), কিন্তু ব্যর্থতার দাম ঠিক করে: ৫ MiB-এ ৩.৫ সেকেন্ড আবার পাঠানো, ২৫৬ MiB-এ ৩ মিনিট। ১০,০০০-এর সীমা সর্বোচ্চ ফাইল ঠিক করে: ৫ MiB-এ প্রায় ৪৯ GiB, ৬৪ MiB-এ ৬২৫ GiB; নিয়ম part ≥ ফাইলের আকার ÷ ১০,০০০.
৪ · Consistency, ETag আর conditional request
PUT সফল হওয়ার পরমুহূর্তে অন্য কেউ GET করলে নতুনটি পাবেন তো? এই প্রতিশ্রুতির নাম read-after-write consistency. S3-এর ইতিহাস শিক্ষণীয়। চালু থেকে বহু বছর নতুন key-এর PUT-এর পরে পড়া সাধারণত নতুনটিই দিত, কিন্তু একই key-এ overwrite, DELETE আর LIST ছিল eventually consistent — কিছুক্ষণ পুরোনো উত্তর আসতে পারত। AWS-এর ঘোষণা অনুযায়ী ডিসেম্বর ২০২০ থেকে S3-এ সব PUT, DELETE আর LIST-এর পরে strong read-after-write consistency, বাড়তি খরচ ছাড়া। অন্য S3-সমর্থক store বা অন্য cloud-এর আচরণ আলাদা হতে পারে, আর region-এর মধ্যে কপি (cross-region replication) প্রায় সবখানেই asynchronous — নথি দেখে নিশ্চিত হোন।
কোন “consistency”? এখানে অর্থ CAP-এর ভাষার — একটি key-এর লেখা শেষ হলে পরের প্রতিটি পড়া সেটি দেখবে (linearizability-র কাছাকাছি, পর্ব ৪০)। ACID-এর C নয়। আর এটি একটি key-এর প্রতিশ্রুতি: দুটি object একসাথে atomically বদলানোর transaction নেই। একই key-এ দুজন একসাথে PUT করলে যার অনুরোধ পরে পৌঁছায় সে জেতে, অন্যজনের লেখা নিঃশব্দে হারায় — lost update.
ETag — সংস্করণের ছাপ, সবসময় MD5 নয়
প্রতিটি object-এর ETag তার বিষয়বস্তুর একটি ছাপ; বিষয়বস্তু বদলালে ETag বদলায়। এক অনুরোধে লেখা object-এ এটি প্রায়ই বিষয়বস্তুর MD5 — কিন্তু multipart-এ নয় (সাধারণত "…-4", অর্থাৎ ৪টি part), কিছু encryption-এও নয়। তাই অখণ্ডতা প্রমাণে upload-এর সময় আলাদা checksum পাঠান (নতুন API-গুলোতে CRC32C বা SHA-256-এর মতো বিকল্প আছে, লেখার সময়ে)।
Conditional request — শর্ত দিয়ে পড়া আর লেখা
Conditional request পর্ব ৮-এ দেখেছেন (ETag, If-Match আর 304). Object storage-এ চার ব্যবহার। GET + If-None-Match: না বদলালে 304, CDN-এর যাচাই এভাবেই। GET + If-Match: কয়েকটি Range-এ নামানোর মাঝে কেউ বদলালে 412, অর্ধেক পুরোনো-অর্ধেক নতুন জোড়া লাগে না। PUT + If-None-Match: *: key না থাকলে তবেই লেখা — দুই worker একই ফল লিখতে গেলে একজনই সফল। PUT + If-Match: compare-and-swap, অর্থাৎ optimistic concurrency. AWS ২০২৪ সালে S3-এ এমন conditional write যোগ করার ঘোষণা দেয়; অন্য store-এ সমর্থন যাচাই করুন।
PUT /sync/u91/manifest.json HTTP/1.1
If-Match: "9f2c41" # যে version পড়ে বদল করেছি
HTTP/1.1 412 Precondition Failed # এর মধ্যে অন্য device নতুন version লিখেছে
# → নতুন করে পড়া, মেলানো, আবার চেষ্টা৫ · একটি distributed object store-এর ভেতরে — metadata, chunk আর placement
ডাকঘরের কাউন্টারে একটি রেজিস্টার খাতা — কোন পার্সেল কোন গুদামের কোন তাকে; পেছনে বিশাল গুদাম। খাতা ছোট কিন্তু প্রতিটি কাজে লাগে; গুদাম বিশাল কিন্তু শুধু মাল বয়। বড় storage সিস্টেমও এভাবে দুই ভাগে গড়া:
- API front-end: stateless সামনের স্তর — সই যাচাই, rate limit, ফাইলকে chunk-এ ভাগ। Load balancer-এর পেছনে অনেকগুলো।
- Metadata service: key থেকে {version, আকার, ETag, chunk-তালিকা ও তাদের স্থান}. প্রতিটি GET আর PUT এখানে আসে, তাই একে দ্রুত, একটি key-এর জন্য strongly consistent আর LIST-এর জন্য key-সাজানো হতে হয় — সাধারণত নিজেই একটি sharded, replicated key-value store: key-এর range ধরে ভাগ (পর্ব ২৩), প্রতিটি ভাগ consensus দিয়ে কপি করা (পর্ব ৪২)। আকারের হিসাব: ধরা যাক ১,০০০ কোটি object × প্রায় ১ KB metadata = ১০ TB — এক যন্ত্রে নয়।
- Data node: disk-ভরা অসংখ্য server; chunk থাকে local file system-এ সাধারণ ফাইল হিসেবে। নিয়মিত heartbeat-এ জানায় “আমি আছি, এত জায়গা খালি”।
Chunk কত বড়? নকশাভেদে কয়েক MB থেকে ৬৪ MB বা বেশি। বড় chunk মানে কম metadata আর টানা দ্রুত পড়া; ছোট chunk মানে বেশি সমান্তরাল পড়া। লাখো ছোট ছবির প্রতিটি আলাদা ফাইল হলে disk-এর file system-এ চাপ পড়ে, তাই অনেক নকশা ছোট object বড় একটি ফাইলে পরপর জোড়ে — Facebook-এর ছবি-store Haystack-এর ২০১০ সালের গবেষণাপত্রে এই চিন্তা আছে।
কোন chunk কোথায় (placement)? Metadata-তে প্রতিটি chunk-এর স্থান লিখে রাখা — নমনীয়, কিন্তু map বড়; অথবা chunk id-এর hash থেকে হিসাব করা — consistent hashing-এর মতো (পর্ব ১৬), lookup-table লাগে না। যে পথেই হোক, placement-কে failure domain মানতে হয়: একই chunk-এর কপি আলাদা যন্ত্রে, আলাদা rack-এ, আর region-স্তরের নিরাপত্তায় আলাদা availability zone-এ।
একটি distributed object store — খাতা আলাদা, গুদাম আলাদা
Client and CDN; a stateless API front-end; a metadata service and a repair manager; data nodes in three failure domains
লেখার পথ, আর তার ক্রম: front-end chunk-গুলোর সব কপি (বা fragment) সমান্তরালে লেখে, checksum মিলিয়ে নিশ্চিতি পায় — তারপর metadata-য় key-কে নতুন version-এর দিকে ফেরায়, তারপরই client পায় 200. এই শেষ metadata-লেখাই commit point: আগে কেউ নতুন version দেখে না, পরে সবাই দেখে — এজন্যই PUT “সব বা কিছুই না”। ব্যর্থ upload-এর chunk কোনো key-এর নাগালে থাকে না; পরে একটি পরিষ্কারের কাজ সেগুলো মোছে। এবার ছবি ২৬.১ ধরে একটি GET অনুসরণ করি — chunk ৮ MiB, ছবি ২০ MiB, তাই c1 (০–৮), c2 (৮–১৬), c3 (১৬–২০ MiB):
| পাঠানো হয়েছে | |
| কোন chunk | |
| Metadata-র খরচ |
- অনুরোধ এল front-end-এ। Client সই করা একটি GET পাঠাল। Stateless front-end-এর যেকোনো instance সই আর অনুমতি যাচাই করল; এখনো এক byte ডেটাও ছোঁয়নি।
- Metadata থেকে chunk-তালিকা। Metadata service জানাল: সর্বশেষ version v3, ২০ MiB, তিনটি chunk আর প্রতিটির তিনটি স্থান — c1: A1, B2, C3; c2: A2, B3, C1; c3: A3, B1, C2. একটি ছোট, দ্রুত প্রশ্ন; বাকি কাজ data node-এর।
- c1 পড়া — কাছের সুস্থ কপি থেকে। Front-end A1 থেকে c1 আনল আর chunk-এর checksum মিলিয়ে নিল (অংশ ৭)। মিলেছে, তাই প্রথম ৮ MiB client-এর দিকে রওনা।
- একটি কপি ধীর — অন্যটি থেকে। c2-এর প্রথম পছন্দ A2 সময়মতো সাড়া দিল না। Front-end অপেক্ষা না করে আরেকটি কপি B3 থেকে c2 আনল — তিন কপির একটি নীরব লাভ: পড়ার বিকল্প। ১৬ / ২০ MiB পৌঁছেছে।
- শেষ chunk, পুরো ছবি। A3 থেকে c3 (৪ MiB) এল; front-end ক্রম মেনে জুড়ে পাঠাল —
200 OK, ETag a1b2. Client কখনো জানল না যে ডেটা তিনটি আলাদা zone-এর যন্ত্র থেকে এসেছে। - Range — শুধু দরকারি অংশ। ভিডিও-player-এর মতো client চাইল শুধু 9–10 MiB (
bytes=9437184-10485759). Chunk-তালিকা থেকে front-end দেখল এটি c2-এর ভেতরে, শুরু থেকে ১ MiB দূরে; B3 থেকে শুধু ১ MiB পড়ে উত্তর206 Partial Content. Chunk-map থাকায় পুরো ২০ MiB পড়তে হয়নি।
৬ · Replication বনাম erasure coding — জায়গা, সহনশীলতা আর মেরামতের দাম
প্রতিটি chunk-কে বাঁচানোর দুই পথ। Replication: r টি পূর্ণ কপি — r গুণ জায়গা, r − ১টি হারানো সয়, যেকোনো একটি কপি থেকে সরাসরি পড়া। Erasure coding (k, m): ডেটাকে k টুকরো (fragment) করে আরও m টি parity fragment হিসাব করা, মোট n = k + m টি আলাদা disk-এ; যেকোনো k টি থাকলেই পুরো ডেটা ফেরে। Storage overhead = (k + m) ÷ k, সহনশীলতা m.
| পদ্ধতি | জায়গা | কয়টি হারানো সয় | একটি fragment মেরামতে পড়া | সাধারণ পড়া |
|---|---|---|---|---|
| ৩ কপি | ৩ ÷ ১ = ৩ গুণ | ২ | ১টি কপি | যেকোনো এক কপি থেকে |
| RS(6,3) | ৯ ÷ ৬ = ১.৫ গুণ | ৩ | ৬টি fragment | ৬টি data fragment থেকে, decode ছাড়া |
| RS(10,4) | ১৪ ÷ ১০ = ১.৪ গুণ | ৪ | ১০টি fragment | ১০টি data fragment থেকে |
k টি ডেটা-মানকে ঘাত k − ১-এর একটি বহুপদীর (polynomial) সহগ ভাবুন; বহুপদীটিকে n টি আলাদা বিন্দুতে হিসাব করে n টি fragment. বীজগণিতের পুরোনো সত্য: ঘাত k − ১-এর বহুপদী ঠিক k টি বিন্দু দিয়ে একমাত্রভাবে নির্ধারিত (Lagrange interpolation). তাই যেকোনো k টি fragment থেকে বহুপদী, আর তা থেকে মূল ডেটা ফেরে — যেকোনো m টি হারানো সয়, বেশি নয়। হিসাব হয় byte-এর একটি finite field-এ (GF(28)), যাতে ফল এক byte-এই থাকে। Reed–Solomon code-এর জন্ম ১৯৬০ সালে Irving Reed আর Gustave Solomon-এর গবেষণাপত্রে। m = ১-এর সবচেয়ে সরল রূপ হলো XOR parity — RAID 5-এর কৌশল (OS কোর্সের RAID পর্ব)।
XOR parity-র খেলনা-রূপ নিচে: ৪টি data chunk, একটি parity. সাথে প্রতিটি chunk-এর checksum — কেন লাগে, দেখবেন:
// Toy erasure code: k = 4 data chunks + ONE XOR parity chunk (like RAID 5),
// plus a per-chunk FNV-1a checksum so a scrubber can tell WHICH chunk went bad.
const fnv = (a) => { let h = 0x811c9dc5;
for (const b of a) { h ^= b; h = Math.imul(h, 0x01000193) >>> 0; }
return h.toString(16).padStart(8, "0"); };
const hex = (a) => Array.from(a, (b) => b.toString(16).padStart(2, "0")).join(" ");
const str = (a) => String.fromCharCode(...a);
const xorAll = (list) => list.reduce((acc, c) => acc.map((v, i) => v ^ c[i]));
const file = Array.from("PHOTO_DHAKA_2026", (c) => c.charCodeAt(0)); // 16 bytes
const K = 4, S = file.length / K;
const D = [];
for (let i = 0; i < K; i++) D.push(file.slice(i * S, (i + 1) * S));
const P = xorAll(D); // parity = D0 ^ D1 ^ D2 ^ D3
const saved = [...D, P].map(fnv); // checksums stored at write time
D.forEach((d, i) => console.log(`D${i} "${str(d)}" ${hex(d)} sum ${saved[i]}`));
console.log(`P (parity) ${hex(P)} sum ${saved[K]}`);
D[2][1] ^= 0x04; // bit rot: one bit flips on disk
console.log(`\nafter bit rot D2 = "${str(D[2])}"`);
const bad = [...D, P].map((c, i) => (fnv(c) === saved[i] ? -1 : i)).filter((i) => i >= 0);
console.log("scrubber: checksum mismatch in chunk", bad.map((i) => (i < K ? "D" + i : "P")).join(","));
const others = D.filter((_, i) => i !== bad[0]);
const fixed = xorAll([...others, P]); // D2 = D0 ^ D1 ^ D3 ^ P
console.log(`rebuilt D2 = "${str(fixed)}" sum ${fnv(fixed)} ok=${fnv(fixed) === saved[2]}`);
D[2] = fixed;
console.log(`file = "${D.map(str).join("")}"`);
// Two chunks lost (D1 and D3): XOR of the survivors only gives D1 ^ D3.
const mix = xorAll([D[0], D[2], P]);
console.log(`\nlose D1+D3: survivors give ${hex(mix)} = D1^D3 -> cannot split, need m >= 2`);আউটপুট:
D0 "PHOT" 50 48 4f 54 sum 6f2f695e
D1 "O_DH" 4f 5f 44 48 sum 4b961b27
D2 "AKA_" 41 4b 41 5f sum 9dc82e29
D3 "2026" 32 30 32 36 sum 40293d93
P (parity) 6c 6c 78 75 sum 1d31bf56
after bit rot D2 = "AOA_"
scrubber: checksum mismatch in chunk D2
rebuilt D2 = "AKA_" sum 9dc82e29 ok=true
file = "PHOTO_DHAKA_2026"
lose D1+D3: survivors give 7d 6f 76 7e = D1^D3 -> cannot split, need m >= 2
Parity-র প্রথম byte হাতে মিলিয়ে নিন: 50 ^ 4f ^ 41 ^ 32 = 6c. তারপর D2-এর একটি বিট উল্টে “AKA_” হলো “AOA_” (0x4b ^ 0x04 = 0x4f). এমন পড়া সফল হয়, disk কোনো error দেয় না — এটিই bit rot. শুধু parity থাকলে জানা যেত “কোথাও গোলমাল”, কিন্তু কোনটি নয়। Checksum দোষী chunk চেনায়, parity সেটি বানায়: বাকি তিনটি আর P-এর XOR-এ D2 ফিরল, checksum 9dc82e29 মিলল। শেষ লাইন: দুটি chunk হারালে XOR দেয় শুধু D1 ⊕ D3 (7d 6f 76 7e) — দুটি অজানা, একটি সমীকরণ। m টি ব্যর্থতা সইতে m টি স্বাধীন সমীকরণ লাগে; Reed–Solomon ঠিক সেটিই দেয়।
এবার বড় ছবি: জায়গা, সহনশীলতা, মেরামতে কত পড়া, আর একটি সরল durability অনুমান। মডেলটি পর্ব ৫-এর replication-মডেলের সাধারণ রূপ: n টি fragment-এর m + ১টি মেরামতের আগেই মরলে ডেটা হারায়; হার ≈ n!/(n − m − ১)! · λm+1 · Rm, যেখানে λ প্রতি disk-ঘণ্টায় নষ্টের হার আর R মেরামতের ঘণ্টা। r কপির জন্য k = ১, m = r − ১ বসালে পর্ব ৫-এর সূত্রই ফেরে।
// Replication vs erasure coding. ASSUMED inputs: a TEACHING MODEL, not a real system.
const AFR = 0.02; // assumed: a disk fails 2% per year, independently
const lambda = -Math.log(1 - AFR) / 8760; // failures per disk-hour
const NODE_TB = 10; // assumed: data on the one node that died
const fact = (n) => (n <= 1 ? 1 : n * fact(n - 1));
// A stripe = k data + m parity fragments on n = k + m different disks. It is lost
// only if m + 1 fragments die before repair finishes (R hours). Markov estimate:
// rate = n!/(n-m-1)! * lambda^(m+1) * R^m (r full copies: k = 1, m = r - 1)
function nines(k, m, R) {
const n = k + m;
const rate = fact(n) / fact(n - m - 1) * Math.pow(lambda, m + 1) * Math.pow(R, m);
const p = -Math.expm1(-rate * 8760); // P(this stripe is lost within a year)
return { p, nines: -Math.log10(p) };
}
const pad = (s, w) => String(s).padEnd(w);
console.log(pad("scheme", 10) + pad("disks", 7) + pad("space", 7) + pad("lose ok", 9) +
pad("repair read", 13) + pad("P(loss)/yr R=24h", 18) + "nines 24h / 72h");
for (const [name, k, m] of [["2 copies", 1, 1], ["3 copies", 1, 2], ["RS(4,2)", 4, 2],
["RS(6,3)", 6, 3], ["RS(10,4)", 10, 4]]) {
const a = nines(k, m, 24), b = nines(k, m, 72);
console.log(pad(name, 10) + pad(k + m, 7) + pad(((k + m) / k).toFixed(2) + "x", 7) +
pad(m, 9) + pad(k * NODE_TB + " TB", 13) + pad(a.p.toExponential(2), 18) +
a.nines.toFixed(1) + " / " + b.nines.toFixed(1));
}আউটপুট:
scheme disks space lose ok repair read P(loss)/yr R=24h nines 24h / 72h
2 copies 2 2.00x 1 10 TB 2.24e-6 5.7 / 5.2
3 copies 3 3.00x 2 10 TB 3.71e-10 9.4 / 8.5
RS(4,2) 6 1.50x 2 40 TB 7.43e-9 8.1 / 7.2
RS(6,3) 9 1.50x 3 60 TB 1.04e-11 11.0 / 9.6
RS(10,4) 14 1.40x 4 100 TB 4.56e-14 13.3 / 11.4
জায়গা আর সহনশীলতা: ৩ কপি ৩ গুণে ২টি ব্যর্থতা সয়; RS(6,3) অর্ধেক জায়গায় ৩টি, RS(10,4) ১.৪ গুণে ৪টি। দাম repair read কলামে: ১০ TB-এর node মরলে ৩ কপিতে ১০ TB পড়া, RS(6,3)-এ ৬০ TB, RS(10,4)-এ ১০০ TB — প্রতিটি হারানো fragment বানাতে k টি পড়তে হয়, তাই repair traffic k গুণ। Durability: একই অনুমানে (বছরে ২% disk, ২৪ ঘণ্টায় মেরামত) ৩ কপি ৯.৪ nine, RS(6,3) ১১.০, RS(10,4) ১৩.৩; কিন্তু RS(4,2)-এর ৮.১ দেখায়, “১.৫ গুণ” বললেই হয় না — m কত, সেটিই আসল। মেরামত ৭২ ঘণ্টা হলে প্রতিটি সারি আধা থেকে প্রায় দুই nine হারায় — m যত বড়, ক্ষতি তত বেশি। মডেলটি ব্যর্থতা স্বাধীন ধরে — আসল সংখ্যা এর চেয়ে খারাপ (অংশ ৭)।
কখন কোনটি? ছোট, গরম, ঘনঘন পড়া object-এ replication — latency কম, decode নেই। বড় আর উষ্ণ-ঠান্ডা ডেটায় EC — PB মাপে সাশ্রয় বিশাল। অনেক নকশা আগে কপি করে লেখে, পরে পেছনে EC-তে রূপান্তর করে; HDFS-এ Hadoop ৩ থেকে EC, প্রচলিত policy RS-6-3. এবার RS(6,3)-এর একটি stripe — ৯টি fragment, প্রতি zone-এ ৩টি — একটি node হারালে কী হয়:
| জীবিত fragment | |
| আর কয়টি হারানো সয় | |
| Network-এ পড়া |
- সব ঠিক। ৬ MiB ডেটা ৬টি data fragment (d1–d6) আর ৩টি parity (p1–p3)-তে, প্রতি zone-এ ৩টি। সাধারণ পড়ায় শুধু d1–d6 লাগে, decode ছাড়াই — code-টি “systematic”. ৯-এর মধ্যে ৯ জীবিত, আরও ৩টি হারানো সয়।
- B2 নীরব। B2 থেকে heartbeat আসছে না। Repair manager একটি grace period (ধরা যাক ১৫ মিনিট) অপেক্ষা করে, কারণ যন্ত্রটি হয়তো শুধু restart হচ্ছে; প্রতিটি reboot-এ TB-এর পর TB মেরামত চালালে network ডুবে যেত। সহনশীলতা এখন ২.
- Degraded read. এর মধ্যে একজন d4 চাইলেন। d4 নেই, তাই front-end বাকি যেকোনো ৬টি (d1, d2, d3, d5, d6, p1) পড়ে d4 হিসাব করল — ১ MiB দিতে ৬ MiB পড়া। Degraded read কাজ করে, কিন্তু ধীর আর দামি।
- মেরামত শুরু। Grace শেষ, B2 মৃত ঘোষিত। Repair manager Zone B-এর একটি নতুন node (B4) বেছে নিল — Zone B-ই, যাতে প্রতি zone-এ ৩টির নিয়ম থাকে। ৬টি fragment পড়ে d4 আবার বানিয়ে B4-এ লেখা: ৬ MiB পড়া, ১ MiB লেখা। যেসব stripe-এর বেশি fragment গেছে, সেগুলো আগে।
- মেরামত শেষ। Metadata-য় d4-এর স্থান এখন B4. আবার ৯ / ৯, সহনশীলতা ৩. পুরো node-এর বেলায় এই কাজ লাখো stripe-এ, হাজারো node থেকে সমান্তরালে চলে।
- পুরো Zone C বিকল। বিদ্যুৎ-বিভ্রাটে C1, C2, C3 একসাথে গেল — ঠিক ৩টি = m. বাকি ৬টি দিয়ে সব ডেটা এখনো পড়া যায় (d5, d6 decode করে), কিন্তু সহনশীলতা ০: আর একটি disk গেলেই এই stripe পড়া যাবে না। এজন্যই fragment zone-এ সমানভাবে ছড়ানো — কোনো zone-এ ৪টি থাকলে এই ঘটনাতেই ডেটা অচল হতো।
৭ · Bit rot, scrubbing, মেরামত আর “এগারো nine”
Bit rot বা নিঃশব্দ ডেটা-বিকৃতি: disk, controller, memory বা firmware-এর ত্রুটিতে বিট বদলায়, অথচ পড়া “সফল” হয়। লাখো disk-এর ওপর ২০০৮ সালের একটি বড় মাঠ-গবেষণায় (FAST সম্মেলন) কয়েক লাখ নিঃশব্দ checksum-অমিল ধরা পড়ে। প্রতিরক্ষা তিন স্তরে:
- শুরু থেকে শেষ পর্যন্ত checksum: client upload-এর সময় checksum পাঠায়, store প্রতিটি chunk-এর পাশে checksum রাখে (GFS-এর গবেষণাপত্রে প্রতি ৬৪ KB-তে একটি ৩২-বিট checksum), আর প্রতিটি পড়ায় মিলিয়ে নেয়। CRC-এর গণিত CN কোর্সের error detection পর্বে।
- Scrubbing: একটি পেছনের কাজ নিয়মিত (যেমন কয়েক সপ্তাহে একবার) সব chunk পড়ে checksum মেলায়, যাতে কম-পড়া ডেটার ক্ষতি জমে না থাকে; অমিল হলে অন্য কপি বা EC থেকে নতুন করে লেখা — উপরের XOR কোডের scrubber ঠিক এটাই করেছে।
- পেছনের মেরামত: heartbeat দিয়ে ব্যর্থতা চেনা (পর্ব ৪৪), grace period, তারপর সবচেয়ে ঝুঁকির stripe আগে। মেরামতের গতি সীমিত রাখলে ব্যবহারকারীর অনুরোধ ধীর হয় না, কিন্তু সূত্রের R বাড়ে — এটিই টানাপোড়েন।
সমান্তরাল মেরামতের হিসাব: ধরা যাক ১০ TB-এর একটি node মরল, ডেটা RS(6,3)-এ, আর তার fragment-গুলো ১,০০০টি node-এর stripe-এ ছড়ানো। মোট পড়া ৬০ TB, প্রতি node-এ ৬০ GB; প্রতিটি সেকেন্ডে ১০০ MB দিলে ৬০০ সেকেন্ড, প্রায় ১০ মিনিট — cluster জুড়ে তখন সেকেন্ডে ১০০ GB চলাচল। একটি RAID array-এর ভেতরে একই মেরামতে ঘণ্টার পর ঘণ্টা লাগত (RAID-এর rebuild). ছড়ানো placement মানে ছোট R, অর্থাৎ বেশি durability.
“এগারো nine” আসলে কী বলে
AWS তার প্রকাশিত FAQ-এ বলে S3-এর সাধারণ storage এক বছরে ৯৯.৯৯৯৯৯৯৯৯৯% durability-র জন্য নকশা করা — এগারোটি nine, আর ব্যাখ্যা দেয়: ১ কোটি object রাখলে গড়ে প্রতি ১০,০০০ বছরে একটি হারানোর আশা। অঙ্ক: ১০৭ × ১০−১১ = বছরে ১০−৪টি object, অর্থাৎ ১০,০০০ বছরে একটি। আমাদের খেলনা-মডেলেও RS(6,3) দিয়েছিল ১১.০ nine — কিন্তু তিনটি সীমা মনে রাখুন:
- এটি নকশার লক্ষ্য, একটি মডেলের ফল — হার্ডওয়্যার ব্যর্থতার, স্বাধীনতা আর সময়মতো মেরামত ধরে। চুক্তির নিশ্চয়তা নয়; cloud-এর চুক্তি (SLA) সাধারণত availability নিয়ে।
- বাস্তবের ঝুঁকি একসাথে আসে: একই ব্যাচের disk, একই বিদ্যুৎ-লাইন, একটি ভুল release — correlated failure স্বাধীনতার অনুমান ভাঙে। মডেলে ২৪ থেকে ৭২ ঘণ্টায় যেতেই ১.৪ nine গেল।
- সবচেয়ে বড় ঝুঁকি আপনি নিজে: নিজের code-এর ভুল DELETE, ভুল lifecycle নিয়ম, চুরি যাওয়া চাবি — store নিখুঁতভাবে নির্দেশ মানবে। তাই versioning, মুছতে বাধা দেয় এমন lock (WORM) আর আলাদা অ্যাকাউন্টে backup — পর্ব ৫১, failover আর disaster recovery.
আরও গভীরে: Windows Azure Storage-এর ২০১২ সালের গবেষণাপত্রের Local Reconstruction Code — LRC(12,2,2) — প্রায় ১.৩৩ গুণ জায়গায় একটি fragment মেরামতে ১২টির বদলে ৬টি পড়ে। Facebook-এর f4 (OSDI ২০১৪) data center-এর ভেতরে RS(10,4) আর তিন data center জুড়ে XOR মিলিয়ে, গবেষণাপত্রের হিসাবে, কার্যকর replication factor প্রায় ২.১-এ নামায়। Copysets (USENIX ATC ২০১৩) দেখায়, এলোমেলো placement-এ কয়েকটি node একসাথে গেলে কোনো না কোনো chunk-এর সব কপি হারানোর সম্ভাবনা বাড়ে।
৮ · Distributed file system — GFS, HDFS আর তাদের পরে
Object storage-এর আগে আর পাশাপাশি এসেছে distributed file system — বহু যন্ত্র জুড়ে একটি file system. সবচেয়ে প্রভাবশালী নকশা ২০০৩ সালের SOSP সম্মেলনে Ghemawat, Gobioff আর Leung-এর Google File System. এর অনুমানগুলোই নকশা ঠিক করেছে: সস্তা হাজারো যন্ত্রে ব্যর্থতা রোজকার ঘটনা; ফাইল বিশাল (কয়েক GB); কাজ মূলত বড় টানা পড়া আর শেষে জোড়া (append), মাঝখানে লেখা বিরল।
- একটি master, অনেক chunkserver: master রাখে namespace, ফাইল থেকে chunk-এর map আর chunk-এর স্থান; স্থানগুলো disk-এ লেখে না, chunkserver-দের heartbeat থেকে জেনে নেয়। প্রতিটি chunk ৬৪ MB, ডিফল্টে ৩টি কপি।
- ডেটা master দিয়ে যায় না: client master-কে শুধু স্থান জিজ্ঞেস করে, উত্তর cache-এ রাখে, বাইট সরাসরি chunkserver থেকে — উপরের ছবির metadata আর data path-এর ভাগ এখানেই।
- লেখার ক্রম: master একটি কপিকে কিছু সময়ের জন্য primary বানায় (lease); primary লেখার ক্রম ঠিক করে, বাকিরা মানে। ডেটা chunkserver থেকে chunkserver-এ শিকলের মতো যায়।
- Record append: অনেক writer একই ফাইলে জোড়ে; GFS নিজে offset বেছে রেকর্ডটি অন্তত একবার atomically বসায় — retry-তে duplicate থাকতে পারে, পাঠক id আর checksum দিয়ে ছাঁকেন। শিথিল consistency, কিন্তু সহজে বড় হয়।
Hadoop-এর HDFS একই ছাঁচে গড়া: NameNode (master) আর DataNode; নতুন সংস্করণে ডিফল্ট block ১২৮ MB (আগে ৬৪), replication ৩. ডিফল্ট placement rack মানে: একটি কপি লেখকের node-এ, বাকি দুটি অন্য এক rack-এর দুটি node-এ — পুরো rack গেলেও কপি থাকে (সংস্করণভেদে আলাদা)।
একটি master-এর সীমা
বড় chunk কেন? GFS-এর গবেষণাপত্র বলে, master প্রতি ৬৪ MB chunk-এ ৬৪ byte-এর কম metadata রাখে। ১ PiB ডেটা = ২৫০ ÷ ২২৬ ≈ ১.৬৮ কোটি chunk, metadata প্রায় ১ GiB — এক যন্ত্রের memory-তে আঁটে। ১ MiB chunk হলে ১০৭ কোটির বেশি chunk আর প্রায় ৬৪ GiB. তবু সীমা এল: ফাইলের সংখ্যা master-এর memory-তে বাঁধা; লাখো ছোট ফাইল (small files problem) metadata ফোলায়; সব metadata-প্রশ্ন একটি যন্ত্রে; আর master বিকল হলে সব থামে — GFS-এ shadow master শুধু পড়তে দিত। ২০০৯ সালে ACM Queue-এর একটি সাক্ষাৎকারে GFS-এর একজন প্রকৌশলী এই চাপগুলোর কথা বলেন। HDFS পরে standby NameNode (high availability) আর namespace ভাগ (federation) যোগ করে।
পরের নকশাগুলোর মূল চাল: metadata-কেও distributed করা। Google-এর প্রকাশিত লেখা অনুযায়ী GFS-এর উত্তরসূরি Colossus metadata রাখে Bigtable-ধাঁচের একটি distributed database-এ; Ceph placement হিসাব করে নেয়; আধুনিক object store metadata রাখে sharded database-এ। Data lake-এর বড় অংশও HDFS থেকে object storage-এ সরেছে, compute আর storage আলাদা বাড়ানোর জন্য; rename দামি বলে সেখানে table format-গুলো manifest আর একটি pointer-এর atomic বদল ব্যবহার করে। Batch কাজ পর্ব ৩৬-এ।
| দিক | GFS (২০০৩) | HDFS | সাধারণ object store |
|---|---|---|---|
| Interface | নিজস্ব file API, record append | file API, লেখা একবার, পরে append | HTTP: PUT, GET, LIST |
| টুকরো | ৬৪ MB chunk | ১২৮ MB block (ডিফল্ট) | নকশাভেদে; বাইরে থেকে দেখা যায় না |
| Metadata | একটি master, memory-তে | NameNode; HA আর federation | ভাগ করা, replicated database |
| Rename | namespace-এ সহজ | atomic, সস্তা | কপি + মোছা, প্রতিটি object |
| সুরক্ষা | ৩ কপি | ৩ কপি; Hadoop ৩ থেকে EC | কপি আর/বা EC, zone জুড়ে |
OS-এর দিক থেকে NFS, AFS আর GFS-এর consistency semantics OS কোর্সের distributed OS পর্বে।
৯ · নকশা — ছবির অ্যাপের আপলোড, আর Dropbox-ধাঁচের file sync
ক · একটি ছবির অ্যাপের আপলোড, শুরু থেকে শেষ
একটি কাল্পনিক ছবি-শেয়ারের অ্যাপ। ধরা যাক দিনে ২০ লাখ ছবি, গড় ৩ MB: দিনে ৬ TB, বছরে প্রায় ২.১৯ PB মূল ডেটা — ৩ কপিতে ৬.৫৭ PB, RS(6,3)-এ প্রায় ৩.২৯ PB (হিসাবের কৌশল পর্ব ৩-এ)। পথটি:
ছবির আপলোড — অনুমতি, সরাসরি store, event, worker, CDN
App → presigned URL → object store → event → thumbnail worker → CDN
- অ্যাপ
POST /uploadsAPI দেয় সই করা URL - Object store
orig/…সরাসরি PUT - Event → queue
ObjectCreatedঅন্তত একবার - Worker
thumb/…যাচাই, ছোট রূপ - CDN
GET thumbকাছ থেকে পড়া
- অনুমতি: API লগইন আর quota দেখে, key বানায় (
orig/u91/7f3a.jpg), status = pending লেখে, আর দেয় ৫ মিনিটের সই করা PUT URL, আকার-ধরন বাঁধা। - সরাসরি store-এ: বড় হলে multipart (অংশ ৩)।
- Event: store “object তৈরি হয়েছে” event notification পাঠায় একটি queue-তে (পর্ব ৩১)।
- Worker: আসল ধরন আর আকার যাচাই, malware scan, ছবির অবস্থান-তথ্য (EXIF GPS) মুছে গোপনীয়তা রক্ষা, তিন মাপের thumbnail (নামে hash, তাই immutable), তারপর status = ready. Event দুবার আসতে পারে (at-least-once, পর্ব ৩৪), তাই কাজটি idempotent: ছবির id-ই চাবি, একই output key.
- পড়া: CDN thumbnail দেয় (পর্ব ১৭); ব্যক্তিগত ছবিতে স্বল্পমেয়াদি signed URL.
- Event হারালে: একটি মিলিয়ে-দেখার কাজ পুরোনো pending সারি খুঁজে HEAD দিয়ে দেখে, আবার queue-তে দেয়। Database আর store — দুই জায়গায় লেখার সমস্যা আর outbox পর্ব ২৫-এ; দীর্ঘ কাজ পর্ব ৩৭-এ; ভিডিওর transcoding পর্ব ৫৯-এর case study-তে।
খ · Dropbox-ধাঁচের file sync — একটি খসড়া
কোনো প্রতিষ্ঠানের ভেতরের নকশা নয়, একটি সাধারণ খসড়া। মূল চিন্তা content-addressed storage: প্রতিটি chunk-এর নাম তার বিষয়বস্তুর SHA-256 hash; ফাইলের একটি version মানে chunk-hash-এর ক্রমিক তালিকা (manifest). একই বিষয়বস্তু দুবার রাখার প্রশ্নই নেই — স্বাভাবিক deduplication.
- আপলোড: device নিজে chunk আর hash বানায়, server-কে জিজ্ঞেস করে কোনগুলো নেই, শুধু সেগুলো পাঠায়, তারপর নতুন manifest লেখে conditional write দিয়ে (অংশ ৪)। দুই device অফলাইনে একই ফাইল বদলালে একটির লেখা 412 পায় — তখন দুটিই রাখা হয়, “conflicted copy” নামে।
- অন্য device: long polling বা WebSocket-এ খবর পায় (পর্ব ১০), manifest মিলিয়ে শুধু নতুন chunk নামায়।
- Chunk কোথায় কাটবেন: নির্দিষ্ট আকারে কাটলে শুরুতে একটি byte ঢোকালেই পরের সব chunk সরে যায়। Content-defined chunking সীমা ঠিক করে বিষয়বস্তু দেখে, একটি rolling hash দিয়ে; chunk-এর ভেতরের ছোট বদলে rsync-ধাঁচের delta sync.
- প্রতিরক্ষা: hash জানা মানেই পড়ার অধিকার নয় — প্রতিটি fetch-এ মালিকানা যাচাই। বহুজনের মধ্যে dedup “ফাইলটি কি আগেই আছে?” ফাঁস করতে পারে, তাই পরিধি প্রায়ই এক ব্যবহারকারী বা এক দল।
Fixed-size আর content-defined chunking-এর পার্থক্য সংখ্যায় দেখুন — ২৫৬ KiB-এর একটি ফাইলে একটি byte ঢুকিয়ে:
// Dedup after inserting ONE byte: fixed-size chunks vs content-defined chunking (CDC).
function mulberry32(a) { return () => { a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; }
const rnd = mulberry32(26); // seeded: same "file" on every run
const N = 256 * 1024, INSERT_AT = 5000; // 256 KiB file; edit position (try others)
const v1 = Uint8Array.from({ length: N }, () => Math.floor(rnd() * 256));
const v2 = new Uint8Array(N + 1); // v2 = v1 with one byte inserted
v2.set(v1.subarray(0, INSERT_AT)); v2[INSERT_AT] = 42; v2.set(v1.subarray(INSERT_AT), INSERT_AT + 1);
const GEAR = Array.from({ length: 256 }, () => Math.floor(rnd() * 4294967296));
const fnv = (b, s, e) => { let h = 0x811c9dc5; // chunk id (real systems: SHA-256)
for (let i = s; i < e; i++) { h ^= b[i]; h = Math.imul(h, 0x01000193) >>> 0; } return h; };
function fixedCuts(b, size) { const c = [];
for (let s = 0; s < b.length; s += size) c.push([s, Math.min(s + size, b.length)]); return c; }
function cdcCuts(b, MIN, MASK, MAX) { // gear rolling hash: depends on last 32 bytes
const c = []; let s = 0, h = 0;
for (let i = 0; i < b.length; i++) {
h = ((h << 1) + GEAR[b[i]]) >>> 0;
const len = i - s + 1;
if ((len >= MIN && (h & MASK) === 0) || len >= MAX) { c.push([s, i + 1]); s = i + 1; h = 0; }
}
if (s < b.length) c.push([s, b.length]); return c;
}
function report(name, cut) {
const c1 = cut(v1), c2 = cut(v2), have = new Set(c1.map(([s, e]) => fnv(v1, s, e)));
let reused = 0, upload = 0;
for (const [s, e] of c2) { if (have.has(fnv(v2, s, e))) reused++; else upload += e - s; }
console.log(name.padEnd(14) + String(c1.length).padStart(5) + String(c2.length).padStart(5) +
String(reused).padStart(8) + String(upload).padStart(11) + (100 * upload / v2.length).toFixed(1).padStart(8) + "%");
}
console.log(`insert 1 byte at ${INSERT_AT} of ${N} bytes`);
console.log("method".padEnd(14) + " v1 v2 reused upload B of file");
report("fixed 4 KiB", (b) => fixedCuts(b, 4096));
report("CDC ~4 KiB", (b) => cdcCuts(b, 2048, 0x7ff, 16384));আউটপুট:
insert 1 byte at 5000 of 262144 bytes
method v1 v2 reused upload B of file
fixed 4 KiB 64 65 1 258049 98.4%
CDC ~4 KiB 73 73 72 3370 1.3%
Fixed-size: byte ৫,০০০-এর আগের chunk ০ (byte ০–৪,০৯৫) শুধু মিলেছে; তার পরের প্রতিটি chunk-এর সীমা এক byte সরে গেছে, তাই প্রতিটির hash নতুন — ৬৫-এর মধ্যে ১টি পুরোনো, upload ২,৫৮,০৪৯ byte, ফাইলের ৯৮.৪%. CDC: সীমা ঠিক হয় বিষয়বস্তু দেখে — শেষ ৩২ byte-এর gear hash-এর নিচের ১১টি বিট শূন্য হলে কাটা (chunk অন্তত ২,০৪৮ byte হলে)। ঢোকানো byte শুধু আশপাশের সীমার হিসাব বদলায়; তার পরের সীমাগুলো একই বিষয়বস্তুতে আবার একই জায়গায় পড়ে। ফল: ৭৩-এর ৭২টি পুনর্ব্যবহার, upload মাত্র ৩,৩৭০ byte (১.৩%). গড় CDC chunk ২,৬২,১৪৪ ÷ ৭৩ ≈ ৩,৫৯১ byte. INSERT_AT বদলে আবার চালান — অনুশীলন ৪-এ অনুমান করে মেলাতে হবে।
Object store আর আলাদা metadata-র একটি পূর্ণ ইন্টারভিউ-ধাঁচের walkthrough — pastebin-এর requirement, API, schema আর scale — পর্ব ৬৬-এর mock interview-এ।
পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা system design ইন্টারভিউর পরীক্ষায় এই ধরনের প্রশ্ন আসতে পারে:
- RS(6,3)-এর overhead আর সহনশীলতা? ৯ ÷ ৬ = ১.৫ গুণ; যেকোনো ৩টি fragment হারানো সয়; ৩ কপি ৩ গুণে মাত্র ২টি।
- EC-র দাম কোথায়? মেরামত আর degraded read-এ k টি fragment পড়া — k গুণ network; ছোট object-এ বাড়তি খরচ।
- বড় ফাইল app server দিয়ে নয় কেন? bandwidth আর memory খরচ; presigned URL আর multipart দিয়ে সরাসরি store-এ, server শুধু অনুমতি দেয়।
- Parity থাকলে checksum কেন? parity ফেরাতে পারে, কিন্তু কোন chunk নষ্ট তা বলে না; checksum চেনায়।
- GFS-এ chunk ৬৪ MB কেন? master-এর metadata ছোট, master-এ কম প্রশ্ন, টানা পড়া দ্রুত; দাম — ছোট ফাইলে অপচয় আর hot spot.
১০ · AI-এর যুগে — storage নকশার পরামর্শ যাচাই
“ছবি কোথায় রাখব?” — AI-এর খসড়ায় প্রায়ই “S3 + Kafka + Redis + microservices” একসাথে। Storage-এর ভুল ধরা পড়ে দেরিতে: হারানো ডেটা, ফাঁস হওয়া URL বা বিশাল বিল। চেনা ভুল:
| AI-এর দাবি | কেন ভুল বা অসম্পূর্ণ | কীভাবে যাচাই করবেন |
|---|---|---|
| “সব ছবি database-এর BLOB-এ রাখুন” | database বড় আর ধীর হয়, backup ফোলে, CDN-এর origin হিসেবে অসুবিধাজনক | বছরের storage আর egress নিজে হিসাব |
| “EC সবসময় ভালো — অর্ধেক জায়গা” | repair traffic k গুণ, degraded read ধীর | অংশ ৬-এর calculator-এ নিজের সংখ্যা |
| “এগারো nine, তাই backup লাগবে না” | ভুল DELETE, চাবি চুরি আর correlated failure মডেলের বাইরে | failure-mode review |
| “Presigned URL ২৪ ঘণ্টার দিন, path client দিক” | bearer token; অন্যের key-তে লেখা সম্ভব | মেয়াদ, key আর আকারের সীমা পড়ে দেখা |
| “S3 eventually consistent” বা “সব store strongly consistent” | প্রথমটি পুরোনো তথ্য, দ্বিতীয়টি সাধারণীকরণ; ACID আর CAP-এর consistency গুলিয়ে ফেলা | নিজের store-এর বর্তমান নথি |
| “প্রতিটি পাতায় LIST দিয়ে ফাইল খুঁজুন”, part-এর সীমা বা দাম আত্মবিশ্বাসে বলা | LIST ধীর আর পাতায় পাতায়; সীমা ও দাম প্রায়ই ভুল বা পুরোনো | নিজের index; vendor-এর নথি; ছোট load test |
প্রশ্নে সংখ্যা, ঝুঁকি আর সীমা দিয়ে দিন, প্রতিটি সিদ্ধান্তের কারণ চান:
AI-এর storage-পরিকল্পনা হাতে পেলে — যাচাইয়ের চেকলিস্ট
Overhead গুনুন, মেরামতের traffic গুনুন, URL-এর পরিধি পড়ুন।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। Product-এর খুঁটিনাটি নয়, নকশার সিদ্ধান্তের পেছনের কারণগুলোই আসল লক্ষ্য।
প্র ০১File system-এর এত সুবিধা থাকতে key আর HTTP-র একটি সরল interface কীভাবে cloud-এর প্রধান storage হয়ে উঠল? বিনিময়ে কী ছাড়তে হলো?
২০০৬ সালে Amazon S3 যখন চালু হয়, তার প্রস্তাব ছিল প্রায় হাস্যকর রকম সরল: একটি bucket, একটি key, একটি PUT আর একটি GET — আর যত ব্যবহার, তত বিল। কোনো disk কেনা নেই, file server চালানো নেই, ক্ষমতার আগাম হিসাব নেই। ছোট দল একদিনেই বিশ্বমানের storage পেল।
সরলতাই এর শক্তি। POSIX file system প্রতিশ্রুতি দেয় directory, atomic rename, ফাইলের মাঝখানে লেখা, lock আর “লেখা শেষ হলেই সবাই দেখবে”। এগুলো এক যন্ত্রে সস্তা, কিন্তু হাজারো যন্ত্রে প্রতিটি প্রতিশ্রুতি মানে সমন্বয় — আর সমন্বয়ই scale-এর শত্রু। S3 প্রায় সব ছেড়ে দিল: rename নেই, আংশিক লেখা নেই, lock নেই, আর বহু বছর পুরো consistency-ও নয় — overwrite আর LIST ছিল eventually consistent, যা ২০২০ সালের ডিসেম্বরে AWS strong consistency ঘোষণা করা পর্যন্ত চলেছে। প্রতিটি key স্বাধীন, তাই metadata ভাগ করা যায় key-এর range ধরে, আর data যায় যেকোনো যন্ত্রে।
দ্বিতীয় কারণ HTTP. ব্রাউজার, মোবাইল অ্যাপ, CDN — সবাই HTTP বোঝে, তাই object storage একই সাথে storage আর বিতরণের উৎস। Presigned URL দিয়ে client সরাসরি পাঠাতে পারে, app server বাইট বয় না।
তৃতীয় কারণ বাস্তুতন্ত্র। API-টি এত প্রচলিত হলো যে MinIO, Ceph-এর মতো open-source store আর অন্য cloud-ও একই ধাঁচের API দিতে লাগল — একটি কার্যত মানদণ্ড, যদিও খুঁটিনাটি আর consistency store ভেদে আলাদা।
দামও আছে। Folder rename লাখো কপি; LIST ধীর; একসাথে কয়েকটি object বদলানোর transaction নেই; প্রতিটি অনুরোধে latency database-এর চেয়ে বেশি। তাই data lake-এর table format-গুলো rename-এর বদলে manifest আর pointer-এর atomic বদল ব্যবহার করে, আর অ্যাপ নিজের database-এ ফাইলের index রাখে।
মূল উপলব্ধি: একটি interface যত কম প্রতিশ্রুতি দেয়, তত সহজে বাড়ে। ভালো নকশা প্রশ্ন করে — আমার আসলে কোন প্রতিশ্রুতি দরকার? ছবি আর backup-এর বেলায় উত্তর প্রায়ই: শুধু “নাম দিয়ে পুরো জিনিস ফেরত, আর কখনো না হারানো”।
প্র ০২GFS-এর একটিমাত্র master কি single point of failure আর bottleneck ছিল না? Google কেন এমন নকশা বেছে নিয়েছিল, আর পরে কী বদলাল?
ছিল — আর নকশাকারীরা তা জানতেন। ২০০৩ সালের গবেষণাপত্র সচেতনভাবে সরলতা বেছে নেয়। একটি master-এর হাতে পুরো ছবি থাকলে placement, re-replication আর garbage collection-এর সিদ্ধান্ত সহজ ও সঠিক হয় — কোনো distributed consensus লাগে না। সময়টা মনে রাখুন: Paxos-এর ব্যবহারিক বাস্তবায়ন তখন কঠিন বলে পরিচিত, আর Raft (২০১৪) তখনো আসেনি।
Bottleneck কমাতে তিনটি কৌশল। ডেটা master দিয়ে যায় না — client শুধু chunk-এর স্থান জিজ্ঞেস করে, cache-এ রাখে, তারপর chunkserver থেকে সরাসরি। বড় chunk — ৬৪ MB, তাই একটি ফাইলের জন্য অল্প কয়েকটি প্রশ্ন, আর chunk-প্রতি ৬৪ byte-এর কম metadata সব memory-তে রাখা যায়। Lease — লেখার ক্রম ঠিক করার দায় একটি primary chunkserver-কে কিছু সময়ের জন্য দেওয়া, master বারবার জড়ায় না।
Single point of failure-এর প্রতিকার: master-এর operation log একাধিক যন্ত্রে কপি হতো; master মরলে বাইরের ব্যবস্থা অন্য যন্ত্রে নতুন master চালু করত; আর shadow master শুধু পড়ার কাজ চালিয়ে যেত। অর্থাৎ ডেটা নিরাপদ, কিন্তু কিছুক্ষণ লেখা বন্ধ — batch কাজে তা মানা যেত।
সমস্যা এল কাজের ধরন বদলানোয়। ২০০৯ সালে ACM Queue-এর একটি সাক্ষাৎকারে GFS-এর একজন প্রকৌশলী বলেন, ফাইলের সংখ্যা বাড়তে থাকায় master-এর memory আর প্রশ্নের চাপ সমস্যা হয়ে উঠছিল, আর latency-সংবেদনশীল কাজ ৬৪ MB chunk-এর batch-ধাঁচের নকশায় ভালো মেলেনি। Google-এর পরে প্রকাশিত লেখা অনুযায়ী উত্তরসূরি Colossus metadata রাখে Bigtable-ধাঁচের একটি distributed database-এ। HDFS একই পথে হেঁটেছে: standby NameNode আর ভাগ করা edit log দিয়ে high availability, আর namespace কয়েকটি NameNode-এ ভাগ করে federation.
মূল উপলব্ধি: “single master” ভুল নয়, একটি পর্যায়ের সঠিক trade-off — যতক্ষণ metadata এক যন্ত্রে আঁটে আর কিছুক্ষণের লেখা-বন্ধ মানা যায়। নকশার কাজ হলো জানা, সেই সীমা কবে পার হবে, আর তার আগেই পরের ধাপের পথ খোলা রাখা।
প্র ০৩Reed–Solomon code ১৯৬০ সালের — CD, QR code আর মহাকাশযানে বহু আগে থেকে ব্যবহার হচ্ছে। তাহলে data center-এ ৩ কপির জায়গা নিতে এত সময় লাগল কেন?
Irving Reed আর Gustave Solomon-এর ১৯৬০ সালের গবেষণাপত্র যে code দেয়, তা পরে সর্বত্র: audio CD-র আঁচড় সামলানো, QR code-এর ছেঁড়া অংশ থেকে পড়া, দূর মহাকাশ থেকে আসা দুর্বল সংকেত। এসব ক্ষেত্রে সমস্যা একটি — ডেটা পথে বা মাধ্যমে নষ্ট হয়, আর ফেরত চাওয়া যায় না।
Data center-এর সমস্যা আলাদা, আর সেখানেই দাম। মেরামতের traffic: ৩ কপিতে একটি হারানো chunk বানাতে একটি কপি পড়লেই চলে; RS(10,4)-এ ১০টি fragment পড়তে হয়, ১০টি যন্ত্র থেকে। হাজারো disk-এর cluster-এ রোজই কিছু নষ্ট হয়, তাই এই বাড়তি traffic চিরস্থায়ী — rack-এর বাইরের network তখন দামি আর সীমিত ছিল। Latency: একটি fragment-এর যন্ত্র ধীর হলে বা নেই হলে পড়া মানে k টি জায়গা থেকে এনে decode — tail latency বাড়ে। CPU আর জটিলতা: finite field-এর গুণ আগে ব্যয়বহুল ছিল; আধুনিক CPU-র SIMD নির্দেশ আর ভালো library তা সস্তা করেছে।
তাই EC প্রথমে এসেছে উষ্ণ আর ঠান্ডা ডেটায়, যেখানে জায়গার সাশ্রয় বিশাল আর পড়া কম। গবেষণা দামটি কমিয়েছে। Windows Azure Storage-এর ২০১২ সালের গবেষণাপত্রের Local Reconstruction Code ছোট ছোট দলে local parity রাখে, ফলে একটি fragment মেরামতে ১২টির বদলে ৬টি পড়তে হয়। Facebook-এর f4 (২০১৪) উষ্ণ ছবির জন্য data center-এর ভেতরে RS(10,4) আর data center-এর মধ্যে XOR মেলায়। Hadoop ৩ HDFS-এ EC যোগ করে, যেখানে RS-6-3 একটি প্রচলিত policy.
আরেকটি নকশা-চাল: আগে কপি করে লেখা, পরে ডেটা ঠান্ডা হলে পেছনে EC-তে রূপান্তর। গরম অবস্থায় দ্রুত পড়া, ঠান্ডা হলে সস্তা জমা — একই ডেটার জীবনের দুই পর্যায়ে দুই পদ্ধতি।
মূল উপলব্ধি: একটি গাণিতিক কৌশল কাগজে কতটা ভালো, তা নকশার অর্ধেক। বাকি অর্ধেক — মেরামত, latency আর network-এর দাম — নির্ভর করে পরিবেশের ওপর। পরিবেশ বদলালে (দ্রুত network, সস্তা CPU, PB মাপের ডেটা) পুরোনো কৌশল নতুন করে লাভজনক হয়।
প্র ০৪Storage “এগারো nine durable” হলে প্রতিষ্ঠানগুলো তবু ডেটা হারায় কেন? Durability আর backup-এর সম্পর্ক কী?
“এগারো nine” একটি নির্দিষ্ট প্রশ্নের উত্তর: হার্ডওয়্যার নষ্ট হয়ে আপনার একটি object হারানোর বার্ষিক সম্ভাবনা কত, যদি store নিজের নিয়মে কাজ করে। AWS-এর নিজের ব্যাখ্যায় ১ কোটি object-এ গড়ে ১০,০০০ বছরে একটি হারানো। এটি একটি মডেলের ফল — স্বাধীন ব্যর্থতা, দ্রুত মেরামত, ছড়ানো placement — আর নকশার লক্ষ্য, চুক্তির নিশ্চয়তা নয়।
বাস্তবে ডেটা হারায় অন্য পথে। নিজের হাতে: একটি ভুল script, একটি ভুল lifecycle নিয়ম যা “৩০ দিনের পুরোনো সব মুছে ফেলা” পুরো bucket-এ চালায়, অথবা পরীক্ষার পরিবেশ ভেবে production-এ কমান্ড। Store এগুলো নিখুঁত durability-র সাথে মানে — মুছে ফেলা ডেটাও “টেকসইভাবে” মুছে যায়। অন্যের হাতে: চুরি যাওয়া চাবি দিয়ে মোছা বা encrypt করে মুক্তিপণ চাওয়া। প্রশাসনিক: বিল না দেওয়ায় অ্যাকাউন্ট বন্ধ।
একটি প্রকাশ্য শিক্ষা ২০১৭ সালের জানুয়ারির GitLab-এর ঘটনা। প্রতিষ্ঠানটির প্রকাশিত postmortem অনুযায়ী, replication-এর সমস্যা সারানোর সময় একজন প্রকৌশলী ভুল server-এ database-এর ডেটা-ফোল্ডার মুছে ফেলেন; তারপর দেখা যায় কয়েকটি backup-পদ্ধতি নীরবে কাজ করছিল না, আর প্রায় ছয় ঘণ্টার ডেটা হারায়। এটি object storage নয়, database — কিন্তু শিক্ষা একই: replication ভুলও কপি করে, আর যে backup ফেরত আনার মহড়া হয়নি, তা থাকা-না-থাকা সমান।
তাই durability-র পাশে লাগে আলাদা প্রতিরক্ষা: versioning (মোছা ফেরানো যায়), WORM বা object lock (নির্দিষ্ট সময় কেউ মুছতে পারে না), আলাদা অ্যাকাউন্ট ও region-এ backup যার চাবি ভিন্ন, ন্যূনতম অনুমতি, আর নিয়মিত restore-এর মহড়া। RPO-RTO আর multi-region পর্ব ৫১-এ।
মূল উপলব্ধি: durability হার্ডওয়্যারের বিরুদ্ধে সুরক্ষা, ভুলের বিরুদ্ধে নয়। “কত nine?” প্রশ্নের পরে সবসময় জিজ্ঞেস করুন — “আমি নিজে ভুল করলে কী হবে?”
প্র ০৫Content-defined chunking আর deduplication-এর ধারণা কোথা থেকে এল? আর বহু ব্যবহারকারীর মধ্যে dedup করলে গোপনীয়তার কী ঝুঁকি?
শিকড় দুটি। ১৯৯৬ সালে Andrew Tridgell আর Paul Mackerras-এর rsync algorithm দেখায়, ধীর লাইনে দুই প্রান্তের দুটি প্রায়-একই ফাইল মেলাতে পুরো ফাইল পাঠাতে হয় না: একটি rolling checksum প্রতিটি byte-অবস্থানে সস্তায় এগিয়ে নিয়ে মিলে যাওয়া block খোঁজা যায়, পাঠাতে হয় শুধু পার্থক্য। ২০০১ সালের SOSP-এ LBFS (Muthitacharoen, Chen ও Mazières) এক ধাপ এগোয়: Rabin fingerprint দিয়ে ফাইলের সীমা ঠিক করা হয় বিষয়বস্তু দেখে, তাই একটি byte ঢোকালে শুধু আশপাশের chunk বদলায় — যা উপরের কোডে দেখেছেন, fixed-size-এ ৯৮.৪% আবার পাঠানো, CDC-তে ১.৩%.
এর পরে backup সফটওয়্যার, file-sync সেবা আর storage appliance-এ ধারণাটি ছড়িয়েছে। Chunk-এর নাম তার hash হলে dedup স্বয়ংক্রিয়: একই chunk কোটি ফাইলে থাকলেও একবার জমা। কাজের তালিকা, প্রতিদিনের backup আর virtual machine-এর image-এ সাশ্রয় বিশাল।
কিন্তু বহু ব্যবহারকারীর মধ্যে dedup একটি পার্শ্ব-পথ (side channel) খোলে। আপলোড যদি তাৎক্ষণিক শেষ হয় কারণ server-এর কাছে chunk আগেই আছে, তাহলে আপলোডকারী জেনে যান অন্য কেউ এই ফাইল রেখেছেন। ২০১০ সালের একটি গবেষণা (Harnik, Pinkas ও Shulman-Peleg) দেখায়, এভাবে অনুমানযোগ্য ফাইল — যেমন একটি নির্দিষ্ট নথির ছাঁচ, যার একটি ঘরই শুধু অজানা — সম্পর্কে তথ্য ফাঁস হতে পারে। শুধু hash জানলেই ফাইল নামিয়ে নেওয়া যায় এমন নকশাও বিপজ্জনক — hash তখন চাবি হয়ে যায়।
প্রতিরক্ষার পথ: dedup-এর পরিধি এক ব্যবহারকারী বা এক দলের মধ্যে রাখা; প্রতিটি chunk-fetch-এ মালিকানা যাচাই; বড় ফাইলে কখনো কখনো সার্ভার-পাশে dedup, যাতে client “আগেই ছিল” বুঝতে না পারে; আর encryption-এর নকশা ভেবে বাছা, কারণ প্রতিটি ব্যবহারকারীর আলাদা চাবিতে encrypt করলে বহুজনের dedup কার্যত থাকে না। Encryption-এর নকশা পর্ব ৫২-এ।
মূল উপলব্ধি: সাশ্রয় আর গোপনীয়তা প্রায়ই বিপরীত দিকে টানে। Dedup-এর পরিধি একটি নিরাপত্তা-সিদ্ধান্ত, শুধু খরচের নয় — নকশার শুরুতেই লিখে রাখুন কে কার ডেটার অস্তিত্ব টের পেতে পারে।
অনুশীলন
কাগজ-কলমে হিসাব করুন, নকশা আঁকুন, তারপর উত্তর মেলান। শেষ প্রশ্নে আগে অনুমান, তারপর কোড চালিয়ে যাচাই।
-
EC-র overhead আর সহনশীলতা: একটি store-এ ব্যবহারকারীর ডেটা ৬০০ TB. তিনটি বিকল্প: ৩ কপি, RS(8,4) আর RS(10,4). (ক) প্রতিটির overhead আর মোট কাঁচা (raw) জায়গা কত? (খ) কয়টি fragment বা কপি হারানো সয়? (গ) ১২ TB-এর একটি node মরলে মেরামতে মোট কত TB পড়তে হয়? (ঘ) কপি বা fragment তিনটি zone-এ যতটা সম্ভব সমানভাবে ছড়ালে কোনটি একটি পুরো zone হারানো সয়?
- (ক) ৩ কপি: ৩ গুণ, ৬০০ × ৩ = ১,৮০০ TB. RS(8,4): ১২ ÷ ৮ = ১.৫ গুণ, ৯০০ TB. RS(10,4): ১৪ ÷ ১০ = ১.৪ গুণ, ৮৪০ TB.
- (খ) ৩ কপি: ২. RS(8,4) আর RS(10,4): দুটিই ৪ (m = ৪)।
- (গ) ৩ কপি: অন্য কপি থেকে ১২ TB. RS(8,4): ৮ × ১২ = ৯৬ TB. RS(10,4): ১০ × ১২ = ১২০ TB. জায়গায় সবচেয়ে সস্তাটির মেরামত সবচেয়ে দামি।
- (ঘ) ৩ কপি: প্রতি zone-এ একটি, একটি zone গেলে ২টি থাকে — সয়, আর তখনো আরেকটি হারানোর সুযোগ বাকি। RS(8,4): ১২টি fragment, প্রতি zone-এ ৪টি; একটি zone গেলে ঠিক ৪ = m হারায় — সয়, কিন্তু সহনশীলতা তখন শূন্য। RS(10,4): ১৪টি fragment, ভাগ ৫ + ৫ + ৪; ৫টির zone গেলে ৯টি থাকে, লাগে ১০ — সয় না; zone ফেরা পর্যন্ত ডেটা অচল, zone ধ্বংস হলে হারানো। নিয়ম: zone-এর সংখ্যা z হলে প্রতি zone-এ fragment ≤ m হতে হবে, অর্থাৎ ⌈(k + m) ÷ z⌉ ≤ m.
-
একটি আপলোড নকশা সমালোচনা করে নতুন করে আঁকুন: একটি কাল্পনিক ছবির অ্যাপে অ্যাপ ছবি পাঠায় API server-এ; server সেটি নিজের local disk-এ লেখে, সাথে সাথে তিনটি thumbnail বানায়, তারপর
200; ছবির URL server-এর disk-এর path. এখন দুটি server; ভিড়ে আপলোড ব্যর্থ হয়, কিছু ছবি মাঝে মাঝে দেখা যায় না। সমস্যা বলুন, নতুন নকশা দিন।সমস্যা: (১) local disk — অন্য server-এ ছবি নেই, তাই “মাঝে মাঝে দেখা যায় না”; server মরলে ছবিই হারায় (durability শূন্য)। (২) বড় বাইট server দিয়ে যায় — bandwidth, memory আর সংযোগ আটকে থাকে, ভিড়ে ব্যর্থতা। (৩) thumbnail synchronous — ব্যবহারকারী অপেক্ষা করেন, CPU-র চাপে অন্য অনুরোধও ধীর। (৪) ছবি সরাসরি server থেকে পড়া — CDN নেই, দূরের পাঠকের latency বেশি। (৫) কোনো যাচাই নেই — ভুল ধরন বা বিশাল ফাইল।
নতুন নকশা: (ক)
POST /uploads: লগইন, quota, আকার-ধরন যাচাই; server-এর বানানো key; status = pending; ৫ মিনিটের presigned PUT, আকার ও ধরন বাঁধা; বড় ফাইলে multipart. (খ) অ্যাপ সরাসরি private bucket-এ পাঠায়। (গ) Event → queue → worker: যাচাই, malware scan, EXIF-এর অবস্থান মোছা, তিনটি thumbnail (নামে hash), status = ready; ছবির id দিয়ে idempotent. (ঘ) পড়া CDN দিয়ে; ব্যক্তিগত ছবিতে স্বল্পমেয়াদি signed URL. (ঙ) মিলিয়ে-দেখার কাজ: ১০ মিনিটের পুরোনো pending সারিতে HEAD, পেলে আবার queue-তে। (চ) API server এখন stateless, যেকোনো সংখ্যায় বাড়ে; ভিড়ে queue ফোলে, worker নিজের গতিতে কাজ সারে। -
LIST কেন ধীর: একটি bucket-এ ৫ কোটি key, সবই
photos/<user>/<id>.jpgধাঁচের। একটি অ্যাপের “সব ছবির সংখ্যা” পাতা প্রতিবারLIST prefix=photos/চালিয়ে গোনে। প্রতি পাতায় ১,০০০টি key আর প্রতি পাতায় ৫০ ms ধরলে কত সময়? কেন এটি মৌলিকভাবে ধীর, আর কীভাবে এড়াবেন?হিসাব: ৫,০০,০০,০০০ ÷ ১,০০০ = ৫০,০০০টি পাতা. প্রতিটির continuation token আগের উত্তরে আসে, তাই পরপর চালাতে হয়: ৫০,০০০ × ০.০৫ = ২,৫০০ সেকেন্ড ≈ ৪১.৭ মিনিট — একটি পাতা খুলতে!
মৌলিক কারণ: LIST মানে বহু shard-এ ছড়ানো metadata-র সাজানো key-এর ওপর range scan; পাতাগুলো ক্রমিক, আর store কোনো আলাদা গণনা রাখে না, তাই “কয়টি আছে” প্রশ্নে সব হাঁটতে হয়।
এড়ানোর উপায়: (১) নিজের database-এ ছবির index রাখুন — সারি (owner_id, photo_id, key, status, created_at), owner_id-এর ওপর index, পাতা keyset pagination-এ (পর্ব ২১, পর্ব ৮); গণনা একটি counter বা সারাংশ table-এ। (২) Index হালনাগাদ রাখুন আপলোড-পথেই বা store-এর event দিয়ে। (৩) একজনের ছবি দেখাতে হলে প্রতি-ব্যবহারকারী prefix (
photos/u91/) — LIST ছোট থাকে। (৪) পুরো bucket-এর নিরীক্ষায় store-এর inventory report বা পেছনের batch কাজ, অনেক prefix-এ সমান্তরালে — কখনো ব্যবহারকারীর অনুরোধের পথে নয়। -
Dedup-এর ফল অনুমান করুন: অংশ ৯-এর
chunk_dedup.js-এINSERT_AT = 5000-এর বদলে200000বসালে fixed 4 KiB আর CDC-র সারিতে reused আর upload B কত হবে? চালানোর আগে লিখুন, তারপর চালিয়ে মেলান। আরINSERT_AT = 0হলে?অনুমানের নিয়ম — fixed: ঢোকানোর আগের সম্পূর্ণ chunk-গুলোই শুধু মেলে: ⌊২,০০,০০০ ÷ ৪,০৯৬⌋ = ৪৮টি. Upload = ২,৬২,১৪৫ − ৪৮ × ৪,০৯৬ = ৬৫,৫৩৭ byte (প্রায় ২৫%). CDC: অবস্থান যা-ই হোক, প্রায় একটি গড় chunk (কয়েক KB) নতুন — ঠিক byte-সংখ্যা আগে বলা যায় না, কারণ কোন chunk-এ পড়ল তার ওপর নির্ভর করে।
চালানোর আসল ফল:
insert 1 byte at 200000 of 262144 bytes method v1 v2 reused upload B of file fixed 4 KiB 64 65 48 65537 25.0% CDC ~4 KiB 73 73 72 5073 1.9%INSERT_AT = 0-তে fixed-এর একটিও মেলে না — reused ০, upload ২,৬২,১৪৫ byte (১০০%); CDC-তে আবার ৭২টি reused, upload ৩,৪৮৪ byte (১.৩%). শিক্ষা: fixed-size-এর লাভ নির্ভর করে বদল কোথায় তার ওপর — শেষে জোড়া (log-এর মতো) হলে ভালো, শুরুতে বা মাঝে হলে প্রায় শূন্য; CDC-র লাভ অবস্থান-নিরপেক্ষ।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমPresigned URL আর multipart দিয়ে একটি বড় ছবি আপলোড থেকে পাঠকের কাছে পৌঁছানো পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 🔐 অ্যাপ API-র কাছে অনুমতি চায়; API key বানায়, multipart শুরু করে
- ✍️ API প্রতিটি part-এর জন্য স্বল্পমেয়াদি সই করা URL দেয়
- 📤 অ্যাপ part-গুলো সরাসরি object store-এ পাঠায়, প্রতিটির ETag পায়
- ✅ API part-তালিকা দিয়ে Complete ডাকে — object একবারে দৃশ্যমান
- ⚙️ Store-এর event থেকে worker যাচাই করে thumbnail বানায়
- 🌍 পাঠক CDN থেকে thumbnail পান
একটি store ডেটা রাখে RS(10,4) erasure coding-এ — ১০টি data আর ৪টি parity fragment, প্রতিটি আলাদা disk-এ। এর storage overhead আর সহনশীলতা কী?
- ১.৪ গুণ জায়গা; যেকোনো ৪টি fragment হারানো সয়
- ১.৪ গুণ জায়গা; যেকোনো ১০টি fragment হারানো সয়
- ২.৫ গুণ জায়গা; যেকোনো ৪টি হারানো সয়
- ৪ গুণ জায়গা; যেকোনো ১০টি হারানো সয়
একটি S3-ধাঁচের object store-এ reports/2025/ prefix-এর নিচে ২০ লাখ object আছে। কেউ console-এ “folder”-টির নাম বদলে reports/archive-2025/ করতে চান। ভেতরে আসলে কী ঘটে?
- একটি folder-এর নাম একবারে বদলায়, সব object সাথে সাথে নতুন path-এ
- Store নতুন নামটি পুরোনো prefix-এর alias হিসেবে মনে রাখে
- প্রতিটি object নতুন key-এ কপি করে পুরোনোটি মোছা হয় — লাখো অনুরোধ, atomic নয়
- শুধু metadata-র একটি ঘর বদলায়, এক অনুরোধে শেষ
একটি অ্যাপ ব্যবহারকারীদের সরাসরি object storage-এ ফাইল পাঠাতে presigned PUT URL দেয়। কোন নীতিটি সবচেয়ে নিরাপদ?
- মেয়াদ ৭ দিন, যাতে দুর্বল network-এও কাজ করে; path client বেছে নেয়
- কয়েক মিনিটের মেয়াদ, server-এর বানানো একটি key আর শুধু PUT; আপলোডের পরে যাচাই
- Client-এর অ্যাপে store-এর গোপন চাবি রেখে দেওয়া, যাতে নিজেই সই করে
- Bucket-টি সবার জন্য লেখা-যোগ্য (public write) করে দেওয়া
একটি cluster-এ ডেটা RS(6,3)-এ রাখা। ৮ TB fragment ধারণকারী একটি node স্থায়ীভাবে নষ্ট হলো। সব হারানো fragment আবার বানাতে মোট প্রায় কত ডেটা পড়তে হবে? (তুলনায় ৩ কপির replication-এ ৮ TB)
- প্রায় ৮ TB
- প্রায় ১২ TB
- প্রায় ২৪ TB
- প্রায় ৪৮ TB
দুটি worker একই ফলাফল-ফাইল results/job-42.json লিখতে পারে, আর চাওয়া হলো শুধু প্রথম লেখাটিই টিকবে। Conditional write সমর্থন করে এমন একটি object store-এ কোনটি ঠিক উপায়?
- আগে GET দিয়ে দেখা ফাইল আছে কি না, না থাকলে সাধারণ PUT
- দুজনই সাধারণ PUT করবে; store নিজেই প্রথমটি রাখবে
- PUT-এর সাথে
If-None-Match: *— key না থাকলে তবেই লেখা - Bucket-এ versioning চালু করা
একটি file-sync সেবা ২৫৬ KiB-এর একটি ফাইলের একেবারে শুরুতে একটি byte ঢোকানোর পর chunk-hash মিলিয়ে শুধু নতুন chunk পাঠায়। ৪ KiB-এর fixed-size chunking আর গড়ে প্রায় ৪ KiB-এর content-defined chunking-এ কী আশা করবেন?
- দুটিতেই প্রায় সব chunk পুনর্ব্যবহার হবে
- Fixed-size-এ প্রায় সবই নতুন; CDC-তে মাত্র এক-দুটি chunk নতুন
- CDC-তে প্রায় সবই নতুন; fixed-size-এ এক-দুটি
- দুটিতেই পুরো ফাইল আবার পাঠাতে হবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Block (নম্বর ধরে, database-এর disk), file (folder, lock, rename) আর object (key দিয়ে পুরো object, HTTP-তে) — প্রতিশ্রুতি যত কম, scale তত সহজ; ছবি, ভিডিও, backup আর log যায় object storage-এ।
- Namespace সমতল — folder শুধু prefix, rename মানে কপি + মোছা, LIST ধীর আর পাতায় পাতায়; object immutable, versioning আর lifecycle দিয়ে বয়স অনুযায়ী সস্তা স্তরে।
- বড় ফাইল multipart-এ, client সরাসরি store-এ presigned URL দিয়ে — স্বল্প মেয়াদ, একটি key, একটি কাজ, পরে যাচাই; conditional write দিয়ে lost update ঠেকানো।
- ভেতরে metadata আলাদা, data আলাদা; placement failure domain মানে। ৩ কপি ৩ গুণে ২টি সয়; RS(k, m) (k + m) ÷ k গুণে m টি সয়, কিন্তু মেরামতে k গুণ পড়া।
- Checksum + scrubbing bit rot ধরে; দ্রুত, ছড়ানো মেরামত durability বাড়ায়; “এগারো nine” হার্ডওয়্যারের মডেল — নিজের ভুলে বাঁচায় versioning আর backup. Sync-এ CDC আর content hash.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৭ · Search — inverted index, ranking, autocomplete আর search clusterপরবর্তী পর্বরাখা ডেটা দ্রুত খুঁজে পাওয়ার নকশা।
- পর্ব ২২ · Replication — leader-follower, multi-leader, leaderless আর replication lagএকই মডিউলMetadata-র মতো ছোট, ঘনঘন বদলানো ডেটার কপি কীভাবে মেলানো থাকে।
- পর্ব ১৬ · Consistent hashing — ring, virtual node আর rendezvous hashingফিরে দেখুনChunk কোন node-এ — node যোগ-বিয়োগে কম ডেটা সরিয়ে।
- সিস্টেম ডিজাইন · কোর্স হোম৬৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Operating Systems · RAID আর নির্ভরযোগ্য স্টোরেজগভীরে যানXOR parity, RAID 5-6, rebuild, URE আর scrubbing — একটি যন্ত্রের ভেতরে।
- Operating Systems · Distributed OS — RPC, clock আর DFSপাশের ট্র্যাকNFS, AFS আর GFS-HDFS-এর consistency semantics.
- Computer Networks · CRC, checksum আর Hamming codeপাশের ট্র্যাকChecksum কীভাবে বিকৃতি ধরে — গণিতসহ।