সরাসরি পর্বে যান
পর্ব ৪৪ · ৬৪-এর মধ্যে · মডিউল ৬

Crash consistency — fsck, journaling আর copy-on-write file system

Crash Consistency — fsck, Journaling (Data, Ordered and Writeback Modes), Write Barriers, Log-Structured and Copy-on-Write File Systems, and fsync

Taught in Bangla, this lesson explains the crash-consistency problem of one append needing three writes, fsck and its full-disk check, journaling with data, ordered and writeback modes and their guarantees, disk caches with FLUSH, FUA and write barriers, log-structured and copy-on-write file systems, and the application's duty with fsync, fdatasync and atomic replace.

এই পর্বে যা শিখবেন

  • Crash consistency সমস্যা — তিন লেখার যেকোনো এক বা দুটি পৌঁছালে ঠিক কী ভাঙে, প্রতিটি ক্ষেত্র আলাদা করে
  • fsck কী কী যাচাই করে, কেন ধীর, আর কোন ক্ষতি সে কখনো সারাতে পারে না
  • Journaling-এর পুরো জীবনচক্র, recovery, batching, circular log আর revoke record; ext3/ext4-এর তিন মোডের সঠিক ক্রম-নিশ্চয়তা
  • Disk write cache, FLUSH, FUA আর write barrier — journaling কেন এদের ওপর নির্ভরশীল
  • Log-structured, copy-on-write আর soft updates — বিকল্প নকশা; আর অ্যাপ্লিকেশনের দিক থেকে fsync, fdatasync ও atomic replace প্যাটার্ন

১ · একটি block যোগ, তিনটি লেখা — crash consistency সমস্যা

পর্ব ৪৩-এ দেখেছেন, একটি সাধারণ Unix-ধাঁচের file system ডিস্ককে কয়েকটি অংশে ভাগ করে: superblock (পুরো file system-এর হিসাব), inode bitmap আর data bitmap (কোন inode আর কোন block খালি), inode-এর টেবিল, আর data block. একটি ফাইলের নাম, আকার, মালিক আর তার data block-গুলোর ঠিকানা থাকে তার inode-এ। এই সব হিসাবকে একসাথে বলে metadata — “ডেটা সম্পর্কে ডেটা”।

এবার একটি অতি সাধারণ কাজ ভাবুন: notes.txt ফাইলের শেষে লিখে এমনভাবে ডেটা যোগ করা হলো যে নতুন একটি block লাগবে। File system-কে তিনটি জায়গায় লিখতে হবে:

  1. Inode (I): ফাইলের আকার বাড়ানো আর নতুন block-এর ঠিকানা (ধরা যাক block 5) যোগ করা।
  2. Data bitmap (B): block 5-কে “ব্যবহৃত” চিহ্নিত করা।
  3. Data block (D): block 5-এ আসল নতুন লেখাটি রাখা।

তিনটি লেখা ডিস্কের তিনটি আলাদা জায়গায়। ডিস্ক সাধারণত শুধু একটি sector (৫১২ বাইট বা ৪ KiB) লেখাকে অবিভাজ্যভাবে (atomically) শেষ করার চেষ্টা করে — তিনটি আলাদা জায়গার লেখাকে একসাথে “সব নয়তো কিছুই না” করার কোনো উপায় তার নেই। আর kernel এগুলো page cache-এ জমিয়ে রাখে, পরে writeback-এর সময় যেকোনো ক্রমে পাঠায়; ডিস্ক নিজেও ক্রম বদলাতে পারে। এর মাঝে যদি বিদ্যুৎ চলে যায় (লোডশেডিং, UPS-এর ব্যাটারি শেষ) বা kernel panic হয়, ডিস্কে পৌঁছাবে তিনটির যেকোনো উপসেট। File system-কে এমন অবস্থায় রেখে যাওয়ার সমস্যাকেই বলে crash consistency সমস্যা।

একটি ব্যাংকের শাখায় টাকা জমার কথা ভাবুন। ক্যাশিয়ার তিনটি কাজ করেন: গ্রাহকের পাসবইয়ে অঙ্ক লেখেন (inode), শাখার হিসাবের খাতায় লেখেন যে ভল্টের অমুক তাক এখন ভরা (bitmap), আর নোটের বান্ডিল ওই তাকে রাখেন (data). মাঝপথে যদি বিদ্যুৎ চলে যায় আর ক্যাশিয়ার উঠে চলে যান — পাসবইয়ে টাকা আছে কিন্তু তাক খালি, বা তাক ভরা কিন্তু কারও নামে লেখা নেই, বা খাতায় তাক খালি দেখানো অথচ পাসবই বলছে ওটাই এই গ্রাহকের। পরদিন সকালে সব খাতা মিলিয়ে দেখা — সেটিই fsck. আর ব্যাংক যদি প্রতিটি কাজের আগে একটি আলাদা রেজিস্টারে “এই তিনটি কাজ করতে যাচ্ছি” লিখে সই করে রাখে, তাহলে পরদিন শুধু সেই রেজিস্টার দেখলেই চলে — সেটিই journaling.

নিচের ডায়াগ্রামে inode 12 (notes.txt) এখন ১টি block-এর (block 4). নতুন block হবে 5; block 5-এ এই মুহূর্তে আছে আগের কোনো মুছে ফেলা ফাইলের পুরোনো জঞ্জাল। আটটি সম্ভাব্য অবস্থা (তিনটি লেখার প্রতিটি পৌঁছেছে বা পৌঁছায়নি, ২৩ = ৮) একে একে দেখুন:

1crash before any write2only D reached disk3only I reached disk4only B reached disk5I + B, not D6I + D, not B7B + D, not I8I + B + D (all three)
ডিস্কে, reboot-এর পরে
inode 12
bitmap: block 5
block 5-এর ভেতরে
ফল
  1. কিছুই পৌঁছায়নি। ডিস্কে সবকিছু আগের মতো: inode 12-এর আকার ১ block, ঠিকানা শুধু [4], bitmap-এ block 5 খালি। File system পুরোপুরি consistent — শুধু ব্যবহারকারীর নতুন লেখাটি নেই। এটি “কিছুই না” অবস্থা, যা ঠিক আছে।
  2. শুধু data block. Block 5-এ নতুন লেখা বসেছে, কিন্তু inode তাকে চেনে না আর bitmap তাকে খালি বলে। Metadata পরস্পরের সাথে মেলে, তাই consistent. কিন্তু ব্যবহারকারীর লেখা নিঃশব্দে হারিয়ে গেছে।
  3. শুধু inode — সবচেয়ে খারাপগুলোর একটি। Inode বলছে ফাইলটি ২ block, দ্বিতীয়টি block 5. কিন্তু block 5-এ আছে পুরোনো জঞ্জাল — ফাইল খুললে অন্য কারও মুছে ফেলা ডেটা দেখা যাবে (garbage). তার ওপর bitmap বলছে block 5 খালি, অর্থাৎ পরে অন্য ফাইলকেও block 5 দেওয়া হতে পারে — দুটি ফাইল একই block ভাগ করবে। Metadata পরস্পরবিরোধী: corrupt.
  4. শুধু bitmap. Bitmap বলছে block 5 ব্যবহৃত, অথচ কোনো inode তার মালিক নয়। এটি space leak: block 5 আর কখনো কাউকে দেওয়া হবে না, যতক্ষণ না কেউ পুরো ডিস্ক মিলিয়ে দেখে।
  5. Inode আর bitmap, data নয়। দুটি metadata পরস্পরের সাথে মেলে — inode বলছে block 5 আমার, bitmap বলছে block 5 ব্যবহৃত। fsck-এর চোখে সব ঠিক! কিন্তু block 5-এ আছে পুরোনো জঞ্জাল। এটি garbage: file system “consistent”, অথচ ফাইলের বিষয়বস্তু ভুল।
  6. Inode আর data, bitmap নয়। Inode ঠিক block-কে দেখাচ্ছে, block-এ ঠিক লেখা আছে — কিন্তু bitmap বলছে block 5 খালি; পরের কোনো বরাদ্দে block 5 অন্য ফাইলকে দেওয়া হলে সেই ফাইল এটির ওপর লিখবে। Metadata পরস্পরবিরোধী — corrupt, ধীরে ঘটা বিপদ।
  7. Bitmap আর data, inode নয়। Block 5-এ নতুন লেখা আছে, bitmap তাকে ব্যবহৃত বলে, কিন্তু কোনো inode তাকে চেনে না। আবার space leak — আর ব্যবহারকারীর লেখাও হারাল, কারণ কোন ফাইলের অংশ ছিল তা কোথাও লেখা নেই।
  8. তিনটিই পৌঁছেছে। Inode-এ আকার ২ আর ঠিকানা [4, 5], bitmap-এ block 5 ব্যবহৃত, block 5-এ নতুন লেখা। নতুন consistent অবস্থা। লক্ষ্য: crash যখনই হোক, ফল শুধু ধাপ ১ বা ধাপ ৮-এর মতো।
ডিস্কে পৌঁছেছেফলের নামকী ভাঙলfsck ধরতে পারবে?
শুধু Dconsistent, লেখা হারালকিছুই না (শুধু নতুন ডেটা নেই)ধরার কিছু নেই
শুধু Icorrupt + garbageinode-bitmap অমিল, ফাইলে জঞ্জালঅমিল ধরবে, জঞ্জাল নয়
শুধু Bspace leakমালিকহীন ব্যবহৃত blockহ্যাঁ, block খালি করে দেবে
I + Bgarbagemetadata ঠিক, ডেটা ভুলনা
I + Dcorruptbitmap-এ ভুল করে খালিহ্যাঁ, bitmap ঠিক করবে
B + Dspace leakমালিকহীন block, লেখাও হারালleak ধরবে, লেখা ফেরাবে না
মূল ধারণা

সমস্যাটি হলো: file system একটি কাজকে (একটি block যোগ) একাধিক আলাদা ডিস্ক-লেখায় ভাঙে, অথচ ডিস্ক শুধু একটি একটি লেখার নিশ্চয়তা দেয়। সমাধানের তিনটি বড় দর্শন: (১) যা হওয়ার হোক, পরে মেরামত — fsck; (২) আগে log-এ লিখে কাজটিকে atomic করা — journaling; (৩) কখনো পুরোনো জায়গায় না লিখে শেষে এক লেখায় সব বদলানো — copy-on-write ও log-structured.

২ · fsck — crash-এর পরে পুরো ডিস্ক মিলিয়ে দেখা

প্রথম যুগের Unix file system আর MS-DOS-এর FAT প্রথম দর্শন মানত: crash হলে হোক, পরের বার চালু হওয়ার সময় একটি প্রোগ্রাম পুরো file system ঘেঁটে অসংগতি খুঁজে ঠিক করবে। Unix-এ এই প্রোগ্রামের নাম fsck (file system check); Windows-এ সমতুল্য কাজ করে chkdsk. fsck একটি মূলনীতি মানে: metadata-কে পরস্পরের সাথে মেলানো. সে যা যা দেখে:

যাচাইকী দেখেঅমিল পেলে
Superblockfile system-এর আকার, block আর inode-এর সংখ্যা যুক্তিসংগত কি নাসন্দেহ হলে ডিস্কের অন্য জায়গায় রাখা superblock-এর কপি ব্যবহার করে
Free block বনাম bitmapসব inode পড়ে নিজেই ব্যবহৃত block-এর হিসাব করে bitmap-এর সাথে মেলায়inode-কেই বিশ্বাস করে bitmap নতুন করে লেখে — leak আর “ভুল করে খালি” দুটোই সারে
Inode-এর অবস্থাব্যবহৃত inode-এর ধরন আর ঠিকানা বৈধ কি নাসারানো না গেলে inode মুছে দেয় আর bitmap ঠিক করে
Link countফোল্ডার-গাছ ঘুরে প্রতিটি inode-এর এন্ট্রি গুনে inode-এ লেখা সংখ্যার সাথে মেলায়সংখ্যা ঠিক করে; কোনো ফোল্ডার যাকে দেখায় না এমন ব্যবহৃত inode-কে lost+found-এ রাখে
Duplicate blockদুটি inode একই block দাবি করছে কি না (শুধু-inode crash-এর পরের বিপদ)block কপি করে দুজনকে আলাদা কপি দেয় বা একটিকে বাদ দেয়
Bad block pointerঠিকানা ডিস্কের সীমার বাইরে বা metadata এলাকার ভেতরে কি নাসেই pointer মুছে দেয়
Directoryপ্রতিটি ফোল্ডারে . আর .. ঠিক আছে কি না, এন্ট্রিগুলো বরাদ্দ করা inode দেখায় কি না, কোনো ফোল্ডার দুবার link হয়নি তোএন্ট্রি ঠিক করে বা সরায়

Linux-এর ext2/3/4-এর fsck প্রোগ্রাম e2fsck এই কাজগুলো পাঁচটি ধাপে (pass) করে, আর চালানোর সময় সেগুলোর নামই ছাপে — inode, block আর আকার; directory-র গঠন; directory-র সংযোগ; reference count; আর শেষে group-এর সারাংশ (bitmap আর গণনা)। এটি একটি উদাহরণ সেশন (root লাগে, আর mount করা file system-এ কখনো চালাবেন না):

$ sudo umount /dev/sdb1
$ sudo e2fsck -f /dev/sdb1
e2fsck 1.47.0 (5-Feb-2023)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
Block bitmap differences:  +5
Fix<y>? yes
/dev/sdb1: ***** FILE SYSTEM WAS MODIFIED *****
/dev/sdb1: 12/65536 files (0.0% non-contiguous), 12753/262144 blocks

fsck-এর দুটি মৌলিক সমস্যা

এক, সে ধীর — আর ডিস্ক যত বড়, তত ধীর। একটি block-এর অমিল খুঁজতে fsck-কে পুরো inode টেবিল আর সব ফোল্ডার পড়তে হয়, কারণ সে জানে না crash-এর আগে কোন কাজ চলছিল। কাজের সময় বদলের আকারের অনুপাতে নয়, file system-এর আকারের অনুপাতে। ১৯৮০-এর দশকে কয়েকশো MB-র ডিস্কে এটি কয়েক মিনিট; আজ কয়েক TB-র ডিস্কে, বিশেষত লাখ লাখ ফাইলসহ, ঘণ্টা ছাড়াতে পারে।

দুই, সে শুধু metadata-কে পরস্পরের সাথে মেলায় — সত্যের সাথে নয়। উপরের ধাপ ৫-এর (I + B) ক্ষেত্রে inode আর bitmap মেলে, তাই fsck সন্তুষ্ট — অথচ ফাইলে জঞ্জাল। যেখানে লেখা হারিয়েছে, সেখানে fsck শুধু leak বন্ধ করে, লেখা ফেরায় না। fsck-এর লক্ষ্য file system-কে আবার ব্যবহারযোগ্য করা, ব্যবহারকারীর ডেটা বাঁচানো নয়।

fsck-এর শিকড় ১৯৭০-এর দশকের Bell Labs-এর Unix-এ — আগে icheck, dcheck, ncheck নামের আলাদা যাচাই-প্রোগ্রাম ছিল, পরে সেগুলো একটি স্বয়ংক্রিয় প্রোগ্রামে মেলে। ১৯৮০-এর দশকে BSD-র Fast File System-এর জন্য Marshall Kirk McKusick আর T. J. Kowalski fsck-এর নকশা ও যুক্তির বিস্তারিত বিবরণ লেখেন। ডিস্ক বড় হতে থাকায় বুট-এর সময়ের দীর্ঘ fsck-ই journaling-এর দিকে শিল্পের ঝোঁকের বড় কারণ হয়ে ওঠে।

৩ · Journaling — আগে খাতায় লিখুন, তারপর কাজ

ডেটাবেসের জগতে একটি পুরোনো কৌশল আছে: write-ahead logging (WAL) — কোনো বদল আসল জায়গায় করার আগে একটি log-এ লিখে রাখা, যাতে crash-এর পর log দেখে কাজটি শেষ বা বাতিল করা যায়। DBMS কোর্সের Recovery পর্বে WAL, LSN, checkpoint আর ARIES বিস্তারিত আছে; এখানে আমরা দেখব file system কীভাবে একই ধারণা আরও সরলভাবে ব্যবহার করে। File system-এ এর নাম journaling, আর log-টির নাম journal — ডিস্কের একটি নির্দিষ্ট এলাকা (ext4-এ সাধারণত একটি লুকানো ফাইল, inode 8).

একটি transaction-এর চারটি ধাপ, আর recovery

আগের উদাহরণটি (I, B, D) data journaling দিয়ে ধরুন — অর্থাৎ data-ও journal-এ যায়। একটি journal transaction এভাবে চলে:

  1. Journal write: journal-এ লেখা হয় একটি শুরু-চিহ্ন TxB (transaction begin; এতে transaction নম্বর আর কোন block-গুলো আসল কোথায় যাবে তার তালিকা), তারপর I, B, D-এর পুরো নতুন কপি।
  2. Journal commit: এবার একটি ছোট commit block TxE লেখা হয়। এটি একটি sector-এর লেখা, তাই atomic. TxE ডিস্কে বসার মুহূর্তটিই transaction-এর “committed” হওয়ার মুহূর্ত।
  3. Checkpoint: I, B, D-কে তাদের আসল জায়গায় (inode টেবিল, bitmap, block 5) লেখা। একে বলে checkpoint.
  4. Free: checkpoint শেষ হলে journal-এর ওই অংশ আর দরকার নেই — journal-এর superblock-এ লিখে জায়গাটি মুক্ত করা হয়।

আর crash-এর পর recovery: বুট-এর সময় file system journal পড়ে। যে transaction-এর TxB আর TxE দুটোই আছে, সেগুলোর block আসল জায়গায় আবার লেখা হয় — একে বলে replay (redo logging). যার TxE নেই, সেটি বাদ — যেন কখনো শুরুই হয়নি। Replay একাধিকবার হলেও ক্ষতি নেই, কারণ প্রতিবার একই পূর্ণ block-কপি একই জায়গায় বসে — কাজটি idempotent. তাই recovery-র মাঝেও crash হলে আবার শুরু থেকে replay করলেই চলে।

কেন TxE আলাদা করে, পরে? সব block (TxB, I, B, D, TxE) একসাথে পাঠালে দ্রুত হতো। কিন্তু ডিস্ক এগুলো যেকোনো ক্রমে লিখতে পারে। যদি TxE আগে বসে আর D বসার আগে crash হয়, recovery দেখবে একটি “সম্পূর্ণ” transaction, আর journal-এর D-এর জায়গায় থাকা জঞ্জাল আসল block 5-এ কপি করবে — journaling নিজেই corruption আনবে। তাই নিয়ম: TxE লেখার আগে transaction-এর বাকি সব লেখা ডিস্কে স্থায়ী হতে হবে।

নিচের ডায়াগ্রাম ext4-এর ডিফল্ট মোডে (ordered — পরের অংশে বিস্তারিত) একই কাজটির জীবনচক্র দেখায়। Transaction নম্বর 41; journal-এ I′ আর B′ মানে inode আর bitmap-এর নতুন কপি। প্রতিটি ধাপে ভাবুন: “ঠিক এখানে বিদ্যুৎ গেলে কী হবে?”

1start handle (in memory)2write D -> block 5, wait3journal: TxB(41), I', B'4FLUSH disk cache5journal: TxE(41) with FUA6checkpoint: I', B' -> home7free: advance journal tail
ডিস্কের অবস্থা
journal
আসল inode 12
আসল bitmap
block 5
এখানে crash হলে
  1. শুরু — শুধু মেমরিতে। write() system call-এর ভেতর ext4 journal-এর কাছে একটি “handle” চায়। Inode আর bitmap মেমরিতে বদলাল, ডিস্কে এখনো কিছুই না। এখানে crash হলে journal-এ কিছু নেই, ডিস্কে পুরোনো অবস্থা।
  2. আগে data, আসল জায়গায়। Ordered মোডের নিয়ম: commit-এর আগে data block তার আসল জায়গায় (block 5) লিখতে হবে। Block 5-এর লেখা শেষ। এখানে crash হলে block 5-এ নতুন লেখা আছে, কিন্তু bitmap-এ সে খালি আর inode তাকে চেনে না — metadata consistent, শুধু লেখাটি হারাল।
  3. Journal-এ metadata. Journal-এ লেখা হলো TxB(41), তারপর inode আর bitmap-এর নতুন পূর্ণ কপি I′ আর B′. এগুলো হয়তো এখনো ডিস্কের write cache-এ। এখানে crash হলে recovery TxB পাবে কিন্তু TxE পাবে না — transaction বাদ, পুরোনো অবস্থা।
  4. FLUSH — cache খালি করার আদেশ। Kernel ডিস্ককে একটি cache flush আদেশ পাঠাল — cache-এ যা আছে, সব স্থায়ী মাধ্যমে লিখে তবেই উত্তর দিতে হবে। এখন block 5, TxB, I′, B′ নিশ্চিতভাবে প্ল্যাটার বা flash-এ। এখানে crash হলেও TxE নেই, তাই বাদ — পুরোনো অবস্থা।
  5. Commit. একটি sector-এর TxE(41) লেখা হলো FUA (force unit access) দিয়ে। এই মুহূর্তে transaction committed: এখন থেকে crash হলে recovery I′ আর B′ আসল জায়গায় কপি করবে — ফল নতুন অবস্থা। এখানেই fsync() ফিরে আসতে পারে।
  6. Checkpoint. পরে, I′ আর B′ তাদের আসল জায়গায় লেখা হলো। মাঝপথে (ধরা যাক inode বসেছে, bitmap বসেনি) crash হলেও ক্ষতি নেই — journal-এ এখনো committed কপি আছে, recovery দুটোই আবার লিখবে (idempotent replay).
  7. Free. Checkpoint-এর লেখা স্থায়ী হওয়ার পর journal-এর superblock-এ “tail” এগিয়ে দেওয়া হলো — transaction 41-এর জায়গা আবার ব্যবহারযোগ্য। এখানে crash হলে journal-এ replay করার কিছু নেই, আর আসল জায়গায় সব নতুন। জীবনচক্র শেষ।

Batching, circular log আর revoke record

Batching: প্রতিটি write()-এর জন্য আলাদা transaction হলে একই inode বা bitmap block বারবার journal-এ লেখা হতো। তাই ext3/ext4 মেমরিতে একটি বড় compound transaction খোলা রাখে, সব বদল তাতে জমায়, আর কিছুক্ষণ পরপর (ext4-এ ডিফল্ট প্রতি ৫ সেকেন্ডে, commit=5) বা কেউ fsync চাইলে একবারে commit করে। একই block-এর দশটি বদলের জন্য journal-এ একটিই কপি যায়।

Circular log: journal-এর আকার সীমিত (কয়েক দশ MB থেকে ১ GiB-এর মতো)। তাই এটি একটি circular log — শেষে পৌঁছালে আবার শুরুতে ফিরে লেখে, ঠিক যেমন circular queue. Journal-এর নিজের superblock মনে রাখে সবচেয়ে পুরোনো এখনো-checkpoint-না-হওয়া transaction (tail) কোথায়। Journal ভরে গেলে নতুন transaction অপেক্ষা করে, যতক্ষণ না পুরোনোগুলোর checkpoint শেষ হয়ে জায়গা খালি হয়।

Revoke record: ধরুন metadata-journaling মোডে একটি ফোল্ডারের block 1000 (metadata) journal-এ লেখা হয়েছিল। পরে ফোল্ডারটি মুছে block 1000 একটি সাধারণ ফাইলের data হিসেবে আবার ব্যবহৃত হলো — data journal-এ যায় না। এবার crash: recovery journal-এর পুরোনো ফোল্ডার-কপিটি replay করলে নতুন ফাইলের ডেটা মুছে যাবে! তাই block মুক্ত হলে journal-এ একটি revoke record লেখা হয় — block 1000-এর আগের কপি আর replay করা যাবে না। Recovery আগে সব revoke সংগ্রহ করে, তারপর replay-র সময় সেগুলো বাদ দেয়।

৪ · data=journal, ordered, writeback — কোন মোডে কী নিশ্চয়তা

Data journaling-এ প্রতিটি data block দুবার লেখা হয় — একবার journal-এ, একবার আসল জায়গায়। বড় ফাইল লেখায় ডিস্কের কাজ প্রায় দ্বিগুণ। অথচ data block-এর সংখ্যা metadata-র চেয়ে অনেক বেশি। তাই বেশিরভাগ journaling file system শুধু metadata journal করে — একে বলে metadata journaling. প্রশ্ন তখন একটাই: data block কখন লেখা হবে, metadata-র commit-এর আগে না পরে? Linux-এর ext3 আর ext4 এই উত্তর অনুযায়ী তিনটি মোড দেয়, mount-এর সময় data= বিকল্পে:

মোডjournal-এ যায়ক্রমের নিশ্চয়তাcrash-এর পর সবচেয়ে খারাপ ফলখরচ
data=journalmetadata আর data দুটোইdata আর metadata একই transaction-এ; TxE-র পর দুটোরই checkpointশেষ commit-এর পরের বদল হারায়; জঞ্জাল নয়প্রতিটি data block দুবার লেখা
data=ordered (ডিফল্ট)শুধু metadatatransaction-এর inode-গুলোর নতুন data block আসল জায়গায় লেখা শেষ হওয়ার পরে metadata-র TxEনতুন বরাদ্দ করা block-এ জঞ্জাল দেখা যায় না; তবে জায়গায়-জায়গায় overwrite-এ পুরোনো-নতুন মেশানো থাকতে পারেdata একবার; commit-এর আগে data-র অপেক্ষা
data=writebackশুধু metadatadata আর metadata-র মধ্যে কোনো ক্রম নেইmetadata consistent, কিন্তু ফাইলে পুরোনো জঞ্জাল (অন্য ফাইলের মুছে ফেলা ডেটা) দেখা যেতে পারেসবচেয়ে কম অপেক্ষা

লক্ষ করুন, তিন মোডেই metadata crash-এর পর consistent থাকে — fsck লাগে না। পার্থক্য শুধু data-র নিশ্চয়তায়। Ordered মোডের নিয়মটি আসলে আগের ছকের “I + B, D নয়” (garbage) ক্ষেত্রটি নিষিদ্ধ করে: metadata কখনো এমন block দেখাবে না, যেখানে নতুন data এখনো বসেনি। Writeback মোডে সেই নিষেধ নেই — তাই সেটি দ্রুত, কিন্তু জঞ্জাল দেখা যেতে পারে।

Ordered মানে “ফাইলের বদল atomic” নয়। ফাইলের ভেতরের বিদ্যমান block-এর ওপর নতুন লেখা (overwrite) ordered মোডে journal ছাড়াই সরাসরি আসল জায়গায় যায়। ১০টি block-এর একটি overwrite-এর মাঝে crash হলে ফাইলে ৬টি নতুন আর ৪টি পুরোনো block থাকতে পারে — file system-এর metadata তবু পুরোপুরি consistent. ডেটাবেস আর অ্যাপ্লিকেশনকে নিজের atomicity নিজেকেই নিশ্চিত করতে হয় (অংশ ৭)।

লেখার সংখ্যা — পরীক্ষার পরিচিত হিসাব

একটি block-এর append (I, B, D, প্রতিটি 4 KiB), journal-এর TxB আর TxE প্রতিটি ১ block ধরে:

  • Journal নেই: I, B, D = ৩টি লেখা — কিন্তু crash-নিরাপত্তা নেই।
  • data=journal: journal-এ TxB + I + B + D + TxE = ৫, checkpoint-এ I + B + D = ৩ — মোট ৮টি.
  • data=ordered: D (আসল জায়গায়) ১ + journal-এ TxB + I + B + TxE = ৪ + checkpoint-এ I + B = ২ — মোট ৭টি.
  • data=writeback: সংখ্যা ordered-এর মতোই ৭টি; পার্থক্য শুধু ক্রমে — D যেকোনো সময় যেতে পারে।

১ GB-র ভিডিও লেখায় data=journal প্রায় ২ GB লেখে। আবার উল্টো দিকও আছে: অনেক ছোট synchronous লেখার (যেমন মেইল সার্ভার) ক্ষেত্রে data=journal কখনো দ্রুত হয়, কারণ journal-এ লেখা পরপর (sequential), আর fsync শুধু journal-এর লেখা শেষ হলেই ফিরতে পারে।

Simulator — প্রতিটি সম্ভাব্য মুহূর্তে crash

নিচের প্রোগ্রাম এই পর্বের মূল যুক্তিটি যন্ত্রের মতো যাচাই করে। প্রথম অংশে journal নেই: তিন লেখার আটটি উপসেটের প্রতিটি ডিস্কে আছে ধরে ফল শ্রেণিভুক্ত করে। দ্বিতীয় অংশে ordered journaling: পাঁচটি লেখার ক্রমে (D, journal-এর TxB+I+B যাকে J বলা হলো, TxE, তারপর checkpoint-এ I আর B) প্রতিটি ধাপের পরে crash ঘটিয়ে recovery চালায়। তৃতীয় অংশে FLUSH নেই, আর ডিস্ক TxE-কে journal-এর বাকি লেখার আগে বসিয়েছে:

// Append one block: inode I (pointer + size), data bitmap B, data block D
function classify(s) {                  // s = which writes reached their home location
  if (s.I && s.B && s.D) return "consistent (new file)";
  if (s.I && !s.B) return "CORRUPT: inode uses a block the bitmap calls free" + (s.D ? "" : " + garbage");
  if (s.I) return "GARBAGE: metadata agrees, block holds old junk";
  if (s.B) return "LEAK: block marked used, no inode owns it";
  return "consistent (old file)" + (s.D ? ", new data lost" : "");
}
const has = (done, k) => done.includes(k);
const home = done => ({ I: has(done, "I"), B: has(done, "B"), D: has(done, "D") });

console.log("1) No journaling - the disk may finish any subset before the crash:");
for (let m = 0; m < 8; m++) {
  const done = ["I", "B", "D"].filter((k, i) => m & (4 >> i));
  console.log("   on disk " + (done.join("+") || "none").padEnd(6) + "-> " + classify(home(done)));
}

function recover(done) {                // journal recovery after reboot
  const s = home(done);
  if (!has(done, "TxE")) return "discard -> " + classify(s);
  if (!has(done, "J")) return "replay  -> CORRUPT: journal slots held junk, copied over I and B";
  s.I = s.B = true;                     // replay copies I and B from the journal
  return "replay  -> " + classify(s);
}
function run(title, steps) {
  console.log(title);
  for (let k = 0; k <= steps.length; k++) {
    const done = steps.slice(0, k);
    console.log(("   crash after " + k + " [" + (done.join(",") || "-") + "]").padEnd(31) + recover(done));
  }
}
run("2) Ordered journaling: D, J = TxB+I+B, FLUSH, TxE, then checkpoint I, B", ["D", "J", "TxE", "I", "B"]);
run("3) No FLUSH - the disk reordered TxE ahead of the journal blocks", ["D", "TxE", "J", "I", "B"]);

আউটপুট (নির্ধারিত হিসাব, প্রতিবার একই):

1) No journaling - the disk may finish any subset before the crash:
   on disk none  -> consistent (old file)
   on disk D     -> consistent (old file), new data lost
   on disk B     -> LEAK: block marked used, no inode owns it
   on disk B+D   -> LEAK: block marked used, no inode owns it
   on disk I     -> CORRUPT: inode uses a block the bitmap calls free + garbage
   on disk I+D   -> CORRUPT: inode uses a block the bitmap calls free
   on disk I+B   -> GARBAGE: metadata agrees, block holds old junk
   on disk I+B+D -> consistent (new file)
2) Ordered journaling: D, J = TxB+I+B, FLUSH, TxE, then checkpoint I, B
   crash after 0 [-]           discard -> consistent (old file)
   crash after 1 [D]           discard -> consistent (old file), new data lost
   crash after 2 [D,J]         discard -> consistent (old file), new data lost
   crash after 3 [D,J,TxE]     replay  -> consistent (new file)
   crash after 4 [D,J,TxE,I]   replay  -> consistent (new file)
   crash after 5 [D,J,TxE,I,B] replay  -> consistent (new file)
3) No FLUSH - the disk reordered TxE ahead of the journal blocks
   crash after 0 [-]           discard -> consistent (old file)
   crash after 1 [D]           discard -> consistent (old file), new data lost
   crash after 2 [D,TxE]       replay  -> CORRUPT: journal slots held junk, copied over I and B
   crash after 3 [D,TxE,J]     replay  -> consistent (new file)
   crash after 4 [D,TxE,J,I]   replay  -> consistent (new file)
   crash after 5 [D,TxE,J,I,B] replay  -> consistent (new file)

প্রথম অংশ আগের ছকটিই যন্ত্র দিয়ে মেলায়: আটটির মধ্যে মাত্র তিনটি (none, D, I+B+D) consistent, বাকি পাঁচটি leak, corrupt বা garbage — journal ছাড়া crash মানে ভাগ্যের ওপর ভরসা। দ্বিতীয় অংশে ছয়টি সম্ভাব্য crash-মুহূর্তের প্রতিটিতে ফল হয় পুরোনো নয়তো নতুন consistent অবস্থা: TxE-র আগে crash হলে transaction বাদ, পরে হলে replay. মাঝের অসম্পূর্ণ checkpoint (crash after 4: I বসেছে, B বসেনি) replay-তে আপনাআপনি সেরে যায়। “new data lost” অবস্থাটি file system-এর দিক থেকে বৈধ — commit-এর আগের লেখার কোনো প্রতিশ্রুতি file system দেয়নি। তৃতীয় অংশ দেখায় একটি মাত্র নিয়ম ভাঙলে কী হয়: ডিস্ক যদি TxE আগে বসায় আর তারপর crash হয়, recovery TxE দেখে replay করে আর journal-এর খালি জায়গার জঞ্জাল inode ও bitmap-এর ওপর কপি করে — journal-ই corruption-এর কারণ। এই জন্যই পরের অংশের FLUSH.

Journaling file system-এর বাণিজ্যিক যাত্রা শুরু ১৯৮০-এর দশকের শেষ আর নব্বইয়ের শুরুতে — IBM-এর JFS (AIX-এ), SGI-র XFS (১৯৯৩-৯৪ সালে IRIX-এ), আর Microsoft-এর NTFS (Windows NT 3.1, ১৯৯৩), যার metadata log-এর নাম $LogFile. Linux-এ ext2-কে journaling দিতে Stephen Tweedie ext3 তৈরি করেন; ১৯৯৮ সালে তাঁর প্রবন্ধ “Journaling the Linux ext2fs Filesystem” এর নকশা বর্ণনা করে, আর ext3 মূল kernel-এ আসে ২০০১ সালে (2.4.15). ext2-এর সাথে একই ডিস্ক-বিন্যাস থাকায় পুরোনো পার্টিশনে শুধু journal যোগ করলেই ext3. এর উত্তরসূরি ext4 স্থিতিশীল ঘোষিত হয় ২০০৮ সালের শেষে (Linux 2.6.28), journal সামলানোর অংশটির নাম JBD2.

৫ · ডিস্কের cache “মিথ্যা” বলতে পারে — FLUSH, FUA আর barrier

Journaling-এর পুরো যুক্তি একটি অনুমানের ওপর দাঁড়িয়ে: “ডিস্ক যখন বলে লেখা শেষ, তখন লেখাটি সত্যিই স্থায়ী।” আধুনিক ডিস্কে এটি ডিফল্টভাবে সত্য নয়। হার্ড ডিস্ক আর SSD দুটোরই ভেতরে থাকে একটি volatile write cache. ডিস্ক লেখাটি এই cache-এ রেখেই “শেষ” জানিয়ে দেয় (একে বলে write-back caching), আর পরে নিজের সুবিধামতো ক্রমে আসল মাধ্যমে লেখে। বিদ্যুৎ চলে গেলে cache-এর লেখা হারায়, আর যেটুকু বসেছে সেটুকু হয়তো kernel যে ক্রমে পাঠিয়েছিল সেই ক্রমে নয়।

তাই ক্রম বজায় রাখতে kernel ডিস্ককে দুটি বিশেষ আদেশ দেয়:

  • Cache flush (FLUSH): এ পর্যন্ত যত লেখাকে ডিস্ক “শেষ” বলেছে, সব স্থায়ী মাধ্যমে লিখে তবেই উত্তর দেওয়ার আদেশ। SATA-তে FLUSH CACHE, NVMe-তে Flush আদেশ।
  • FUA (Force Unit Access): একটি নির্দিষ্ট লেখার সাথে লাগানো চিহ্ন — “এই লেখাটি cache-এ রেখে নয়, স্থায়ী মাধ্যমে লিখে তবেই শেষ বলবে।”

Journal commit-এর জন্য ext4/JBD2 সাধারণত দুটোই ব্যবহার করে: commit block পাঠানোর আগে একটি FLUSH (যাতে data আর journal-এর বাকি block আগে স্থায়ী হয়), আর commit block নিজে FUA-সহ। Linux kernel-এর ভেতরে এই অনুরোধগুলোর চিহ্ন REQ_PREFLUSH আর REQ_FUA. পুরোনো নাম ছিল write barrier — “এই দেয়ালের আগের লেখা দেয়ালের পরের লেখার আগে স্থায়ী হবে” — আর ext4-এর mount বিকল্প barrier (ডিফল্ট চালু) এখনো সেই নাম বহন করে।

nobarrier দিয়ে গতি বাড়ানোর পুরোনো পরামর্শ থেকে সাবধান। Barrier বন্ধ করলে journaling থাকে, কিন্তু তার নিশ্চয়তা থাকে না — উপরের simulator-এর তৃতীয় অংশের মতো ফল হতে পারে। এটি নিরাপদ শুধু তখন, যখন ডিস্কের cache নিজেই বিদ্যুৎ-বিভ্রাটে টিকে থাকে — যেমন ব্যাটারি-সমর্থিত cache-যুক্ত RAID কন্ট্রোলার বা power-loss protection-যুক্ত enterprise SSD — আর সেটিও নির্মাতার কাগজে নিশ্চিত হয়ে। কিছু ডিস্ক FLUSH-ই ঠিকমতো মানে না (প্র ০৪)।

এবার দেখুন, এই কোর্সের কোড যে container-এ চলে তার root আর বর্তমান ফোল্ডার কোন file system-এ, কোন বিকল্পে mount করা। একটি জরুরি কথা: /proc/mounts শুধু সেই বিকল্পগুলো দেখায় যা ডিফল্ট থেকে আলাদা — data=ordered ডিফল্ট বলে সেখানে প্রায়ই থাকে না। ext4 নিজের পূর্ণ বিকল্প-তালিকা দেখায় /proc/fs/ext4/<device>/options-এ:

shopt -s nullglob
echo "== 1. root (/) =="
findmnt -n -o FSTYPE /
findmnt -n -o OPTIONS / | tr ',' '\n' | grep -E '^(rw|ro|relatime|fsync=.*)$' | paste -sd' '
echo "== 2. current directory $(pwd) =="
findmnt -n -o FSTYPE,OPTIONS -T .
echo "== 3. ext4 options the kernel really uses (defaults included) =="
files=(/proc/fs/ext4/*/options)
[ ${#files[@]} -gt 0 ] || echo "not visible here"
for f in "${files[@]}"; do
  printf '%s: ' "$(basename "$(dirname "$f")")"
  grep -oE 'data=[a-z]+|nobarrier|barrier|journal_checksum|delalloc|auto_da_alloc|commit=[0-9]+' "$f" | paste -sd' '
done
echo "== 4. jbd2 (the ext4 journal) =="
for f in /proc/fs/jbd2/*/info; do
  echo "$(basename "$(dirname "$f")"): $(grep -m1 'commit time' "$f" | sed 's/^ *//')"
done
echo "== 5. write cache of the virtual disks =="
for f in /sys/block/vd*/queue/write_cache /sys/block/sd*/queue/write_cache /sys/block/nvme*/queue/write_cache; do
  echo "$(echo "$f" | cut -d/ -f4): $(cat "$f")"
done

একটি আসল রানের আউটপুট (container ভেদে ভিন্ন হবে; সংখ্যা প্রতিবার বদলায়):

== 1. root (/) ==
overlay
rw relatime fsync=volatile
== 2. current directory /home/wandbox ==
ext4   rw,relatime
== 3. ext4 options the kernel really uses (defaults included) ==
vda2: delalloc journal_checksum barrier auto_da_alloc commit=5 data=ordered
vdb1: delalloc journal_checksum barrier auto_da_alloc commit=5 data=ordered
== 4. jbd2 (the ext4 journal) ==
vda2-8: 928us average transaction commit time
vdb1-8: 145081us average transaction commit time
== 5. write cache of the virtual disks ==
vda: write back
vdb: write back

সৎভাবে পড়ুন, কারণ ফলটি শিক্ষণীয়। (১) Container-এর root একটি overlayfs — ext4 নয়, তাই সেখানে data= বলে কিছু নেই। তার ওপর fsync=volatile বিকল্পটির অর্থ, এই overlay-তে fsync নীচের ডিস্ক পর্যন্ত পৌঁছায় না, কার্যত উপেক্ষিত — crash হলে এমন overlay ফেলে দিয়ে নতুন করে তৈরি করাই নিয়ম। অস্থায়ী container-এ এটি গতির জন্য সচেতন সিদ্ধান্ত, কিন্তু ডেটাবেসের জন্য বিপজ্জনক। (২) আমাদের বর্তমান ফোল্ডার (যেখানে পরের C প্রোগ্রাম ফাইল লিখবে) আসলে একটি ext4-এর অংশ, mount বিকল্প শুধু rw,relatime — এখানে data=ordered চোখে পড়ে না, কারণ সেটি ডিফল্ট। (৩) ext4-এর নিজের তালিকা আসল ছবিটি দেয়: data=ordered, barrier চালু, journal_checksum, delayed allocation (delalloc) আর তার নিরাপত্তা-জাল auto_da_alloc, আর প্রতি ৫ সেকেন্ডে commit. (৪) প্রতিটি ext4-এর journal JBD2-এর একটি আলাদা instance (নামের শেষের -8 হলো journal-এর inode নম্বর), আর গড় commit-সময় ডিস্ক ও লোড ভেদে এক মিলিসেকেন্ডের কম থেকে শত মিলিসেকেন্ড পর্যন্ত। (৫) ভার্চুয়াল ডিস্কের cache write back — অর্থাৎ লেখা আগে cache-এ যায়, তাই barrier আর FLUSH এখানে সত্যিই দরকার।

৬ · পুরোনো জায়গায় না লেখা — log-structured আর copy-on-write

Journaling-এ প্রতিটি metadata দুবার লেখা হয় — journal-এ আর আসল জায়গায়। যদি প্রতিটি নতুন সংস্করণ সবসময় নতুন খালি জায়গায় লেখা হয়, আর পুরোনোটি অক্ষত থাকে, তাহলে crash-এর সময় পুরোনো সংস্করণটি সবসময় অটুট — শুধু শেষে একটি pointer বদলে “এখন থেকে নতুনটি” বলতে হয়। এই দর্শনের দুটি রূপ।

Log-structured file system (LFS)

১৯৯১ সালে Berkeley-র Mendel Rosenblum আর John Ousterhout SOSP সম্মেলনে Sprite LFS-এর নকশা প্রকাশ করেন। তাঁদের যুক্তি ছিল: RAM বাড়ছে, তাই বেশিরভাগ পড়া cache থেকেই মিটবে; ডিস্কের কাজ হবে মূলত লেখা, আর হার্ড ডিস্ক এলোমেলো ছোট লেখায় ভীষণ ধীর, পরপর বড় লেখায় দ্রুত। তাই LFS সব বদল — data, inode, ফোল্ডার — মেমরিতে জমিয়ে বড় বড় segment-এ (সেই নকশায় ০.৫ থেকে ১ MB) ডিস্কের খালি অংশে পরপর লিখে দেয়, যেন পুরো ডিস্কটাই একটি log.

  • Inode কোথায়? Inode-ও প্রতিবার নতুন জায়গায় যায়, তাই তার ঠিকানা স্থির নয়। LFS একটি inode map রাখে (inode নম্বর → এখনকার ঠিকানা), যা নিজেও log-এ লেখা হয়; আর ডিস্কের একটি স্থির জায়গায় checkpoint region সেই map-এর সর্বশেষ টুকরোগুলোর ঠিকানা রাখে, কয়েক সেকেন্ড পরপর হালনাগাদ হয়।
  • Crash recovery: শেষ বৈধ checkpoint region পড়া, তারপর তার পরে লেখা segment-গুলো “roll forward” করে যতটা সম্ভব উদ্ধার — fsck-এর মতো পুরো ডিস্ক ঘাঁটতে হয় না।
  • দাম — cleaning: পুরোনো সংস্করণগুলো ডিস্কে মৃত হয়ে পড়ে থাকে। একটি cleaner কিছু segment পড়ে, এখনো জীবিত block-গুলো নতুন segment-এ একসাথে লেখে, আর পুরোনো segment খালি করে। কোন segment আগে পরিষ্কার হবে (প্রবন্ধের “cost-benefit” নীতি) কর্মক্ষমতার মূল প্রশ্ন; ডিস্ক প্রায় ভরা থাকলে এর খরচ খুব বাড়ে।

হার্ড ডিস্কে LFS বেশি ছড়ায়নি, কিন্তু flash-এর যুগে তার ধারণা ফিরে এসেছে: SSD-র ভেতরের flash translation layer (পর্ব ৪০) log-structured, আর Samsung-এর F2FS (Linux 3.8) সরাসরি LFS-ধাঁচের — প্র ০৫ দেখুন।

Copy-on-write file system

পর্ব ৩৫-এ fork-এর copy-on-write দেখেছেন — মেমরি-পাতা লিখলে তবেই কপি। File system-এর copy-on-write (COW) একই নামের হলেও নিয়মটি আরও কঠোর: কোনো ব্যবহৃত block কখনো জায়গায় overwrite হয় না। File system-টি একটি গাছ (সাধারণত B-tree-এর রূপ, DSA কোর্সের B-tree পর্ব দেখুন): একেবারে ওপরে একটি root block, নিচে ফোল্ডার আর inode-এর node, পাতায় data block. একটি data block বদলাতে হলে:

  1. নতুন data block একটি খালি জায়গায় লেখা হয়।
  2. যে node তাকে দেখাত, তার একটি নতুন কপি লেখা হয় নতুন ঠিকানাসহ — সেটিও নতুন জায়গায়।
  3. এভাবে root পর্যন্ত পুরো পথের প্রতিটি node-এর নতুন কপি।
  4. শেষে ডিস্কের একটি স্থির জায়গার ছোট একটি block — ZFS-এ uberblock, Btrfs-এ superblock — নতুন root-কে দেখাতে হালনাগাদ হয়। এই এক-block লেখাটিই পুরো বদলের commit.

নিচের গাছে root R-এর নিচে দুটি node A আর B, তাদের নিচে চারটি data block L1–L4. ব্যবহারকারী L3 বদলাচ্ছেন। R′, B′, L3′ মানে নতুন কপি; বন্ধনীর সংখ্যা ডিস্কের block নম্বর:

1uberblock -> R ; R -> A, B ; B -> L3, L42write L3' to free block 903write B' (-> L3', L4) to block 914write R' (-> A, B') to block 925FLUSH6write uberblock (txg 101) -> R'7free R, B, L3 unless a snapshot holds them
ডিস্কের গাছ
uberblock দেখায়
পুরোনো গাছ
নতুন block
ভাগ করা (অপরিবর্তিত)
এখানে crash হলে
  1. শুরুর গাছ। Uberblock (transaction group বা txg 100) দেখায় root R-কে; R দেখায় A আর B-কে, B দেখায় L3 আর L4-কে। প্রতিটি pointer-এর সাথে নিচের block-এর checksum. ব্যবহারকারী L3 বদলাতে চান।
  2. নতুন data, নতুন জায়গায়। L3′ লেখা হলো খালি block 90-এ। L3 নিজে অক্ষত। এখানে crash হলে কোনো pointer block 90-কে দেখায় না — সেটি শুধুই খালি জায়গা, পুরোনো গাছ পুরোপুরি বৈধ। কোনো leak-ও নেই, কারণ খালি জায়গার হিসাব গাছ থেকেই আসে।
  3. Parent-এর কপি। B-কে বদলানো যাবে না, তাই B′ লেখা হলো block 91-এ — সে দেখায় L3′ (নতুন checksum-সহ) আর পুরোনো L4-কে। L4 এখন পুরোনো আর নতুন দুই গাছেই ভাগ করা। Crash হলে এখনো পুরোনো গাছ।
  4. Root পর্যন্ত। R′ লেখা হলো block 92-এ, সে দেখায় অপরিবর্তিত A আর নতুন B′-কে। একটি block বদলাতে তিনটি নতুন block লাগল — পাতা থেকে root পর্যন্ত পথের দৈর্ঘ্য। অনেক বদল একসাথে জমিয়ে (ZFS-এ কয়েক সেকেন্ডের একটি transaction group) এই খরচ ভাগ করা হয়। Crash হলে এখনো পুরোনো গাছ।
  5. FLUSH. নতুন সব block স্থায়ী হওয়া নিশ্চিত করা হলো — journaling-এর মতো এখানেও commit-এর আগে এটি অপরিহার্য, নইলে uberblock এমন R′-কে দেখাতে পারে যা ডিস্কে বসেনি।
  6. এক লেখায় commit. Uberblock-এর নতুন সংস্করণ (txg 101) লেখা হলো, যা R′-কে দেখায়। ZFS uberblock-গুলো একটি ছোট ঘূর্ণায়মান সারিতে রাখে আর বুট-এর সময় checksum-সহ বৈধ সর্বোচ্চ txg-টি বেছে নেয় — তাই এই লেখাটিও অর্ধেক হয়ে বিপদ ঘটায় না। এখন থেকে crash হলে নতুন গাছ। পুরো বদলটি atomic: হয় পুরোনো গাছ, নয় নতুন — মাঝামাঝি কিছু নেই, fsck লাগে না।
  7. পুরোনো block — মুক্ত, নাকি snapshot? R, B আর L3-কে আর কেউ দেখায় না, তাই সাধারণত এগুলো মুক্ত হয়। কিন্তু কেউ যদি txg 100-এর সময় একটি snapshot নিয়ে থাকেন, সেই snapshot পুরোনো R-কে ধরে রাখে — তাহলে R, B, L3 মুক্ত হবে না, আর পুরোনো গাছটি পুরো অবস্থায় পড়া যাবে। Snapshot নেওয়া মানে শুধু একটি root pointer রেখে দেওয়া — প্রায় বিনা খরচে।

COW-এর তিনটি উপহার

fsck ছাড়াই consistent

পুরোনো গাছ কখনো ছোঁয়া হয় না, নতুন গাছ commit হয় এক লেখায়।

atomic tree update

প্রায় বিনামূল্যের snapshot

একটি পুরোনো root ধরে রাখলেই পুরো file system-এর সেই মুহূর্তের ছবি। জায়গা খরচ হয় শুধু পরে বদলানো block-এর জন্য।

backup, rollback, clone

End-to-end checksum

প্রতিটি pointer-এর সাথে নিচের block-এর checksum — গাছটি একটি Merkle tree. ডিস্ক ভুল ডেটা ফেরত দিলে (bit rot, ভুল জায়গায় লেখা) পড়ার সময়ই ধরা পড়ে।

silent corruption ধরা

বাস্তবের COW file system:

  • WAFL (Write Anywhere File Layout) — NetApp-এর ফাইল সার্ভারের file system, ১৯৯৪ সালের একটি USENIX প্রবন্ধে (Hitz, Lau ও Malcolm) বর্ণিত; সহজ snapshot-এর পথিকৃৎ।
  • ZFS — Sun Microsystems-এ Jeff Bonwick আর Matthew Ahrens-এর দল, ২০০৫ সালে OpenSolaris-এ। File system আর volume manager (RAID, পর্ব ৪৫) একসাথে; প্রতিটি block pointer-এ checksum (ডিফল্ট fletcher4), mirror বা RAID-Z থাকলে খারাপ কপি নিজে সারায়; দ্রুত fsync-এর জন্য আলাদা intent log (ZIL). আজ OpenZFS নামে Linux আর FreeBSD-তে।
  • Btrfs — Oracle-এ Chris Mason-এর শুরু (২০০৭), Linux-এ ২০০৯ সালে; COW B-tree, checksum, subvolume ও snapshot; Fedora আর openSUSE-এর ডিফল্ট।
  • APFS — Apple-এর file system, ২০১৭ সাল থেকে iOS 10.3 আর macOS High Sierra-তে ডিফল্ট; metadata-র COW, snapshot (Time Machine ব্যবহার করে) আর ফাইলের তাৎক্ষণিক clone. লক্ষ করুন, APFS-এর checksum শুধু metadata-র, ব্যবহারকারীর data-র নয়।

COW-এর দামও আছে: ছোট বদলেও root পর্যন্ত পথ নতুন করে লেখা (write amplification), ক্রমাগত overwrite হওয়া ফাইলের (ডেটাবেস, VM image) block ডিস্কে ছড়িয়ে পড়া (fragmentation), আর ডিস্ক প্রায় ভরা হলে ধীর হয়ে যাওয়া — প্র ০৩-এ বিস্তারিত।

Soft updates আর NTFS — সংক্ষেপে

Soft updates (Greg Ganger আর Yale Patt-এর ধারণা; BSD-র FFS-এ McKusick আর Ganger বাস্তবায়ন করেন, ১৯৯৯ সালে প্রকাশিত) journal ছাড়াই কাজ করে: file system মেমরিতে প্রতিটি block-এর নির্ভরতা মনে রাখে আর লেখার ক্রম এমনভাবে সাজায় যে ডিস্কে কখনো এমন pointer থাকে না যা অপ্রস্তুত কিছুকে দেখায় — যেমন inode-এর আগে data, ফোল্ডার-এন্ট্রির আগে inode. এতে crash-এর পর সবচেয়ে খারাপ ফল শুধু space leak, যা পটভূমিতে fsck দিয়ে ধরা যায়। নকশাটি সুন্দর কিন্তু বাস্তবায়ন অত্যন্ত জটিল; FreeBSD পরে এর সাথে একটি ছোট journal যোগ করে (SU+J).

NTFS-এর $LogFile একটি metadata journal, তবে ext4-এর মতো পুরো block-কপি (physical logging) নয় — এতে লেখা হয় কাজের redo আর undo তথ্য (logical logging), ডেটাবেসের ARIES-ধাঁচের log-এর কাছাকাছি; ব্যবহারকারীর data এতে journal হয় না। Microsoft-এর নতুন ReFS (Windows Server 2012 থেকে) metadata-র জন্য copy-on-write-ধাঁচের পদ্ধতি আর checksum নেয়।

পদ্ধতিমূল কৌশলRecovery-র সময়উদাহরণ
fsckযা হওয়ার হোক, পরে মেলানোডিস্কের আকারের অনুপাতে (মিনিট–ঘণ্টা)ext2, FAT
Journalingআগে log, পরে আসল জায়গাjournal-এর আকারের অনুপাতে (সেকেন্ড)ext3/ext4, XFS, NTFS, JFS
Soft updatesলেখার ক্রম সাজানো, শুধু leak সম্ভবপ্রায় তাৎক্ষণিক, পটভূমিতে fsckFreeBSD-র UFS
Log-structuredসব লেখা log-এ, checkpoint regionশেষ checkpoint থেকে roll forwardSprite LFS, F2FS
Copy-on-writeনতুন জায়গায় লেখা, শেষে root বদলসর্বশেষ বৈধ root খোঁজাZFS, Btrfs, APFS, WAFL

৭ · অ্যাপ্লিকেশনের দায় — fsync, fdatasync আর atomic replace

এ পর্যন্ত যা দেখলেন, সবই file system-এর নিজের কাঠামো বাঁচানোর ব্যবস্থা। ব্যবহারকারীর ডেটা কখন টেকসই হবে, সেটি আলাদা প্রশ্ন। write() ফিরে এলে ডেটা শুধু page cache-এ (dirty page); ext4 সেটি হয়তো কয়েক সেকেন্ড পরে ডিস্কে পাঠাবে। ডেটা যে সত্যিই স্থায়ী হয়েছে — ডেটাবেসের ভাষায় durability — তা নিশ্চিত করতে অ্যাপকে স্পষ্ট করে চাইতে হয় (পর্ব ৪১-এ এর প্রাথমিক পরিচয়):

  • fsync(fd): এই ফাইলের সব dirty data আর metadata ডিস্কে লেখা, ডিস্কের cache flush-সহ, শেষ হলে ফেরা। ব্যর্থ হলে -1 আর errno (যেমন EIO).
  • fdatasync(fd): data আর শুধু সেই metadata যা পরে ডেটা পড়তে লাগবে (যেমন ফাইলের আকার); শেষ-পরিবর্তনের সময়ের মতো অপ্রয়োজনীয় metadata বাদ — তাই কখনো এক লেখা কম।
  • ফোল্ডারের fsync: নতুন ফাইল তৈরি বা rename একটি ফোল্ডারের বদল। Linux-এ নতুন নামটি টেকসই করতে ফোল্ডারটিও খুলে fsync করতে হয়।

Atomic replace প্যাটার্ন

একটি কনফিগ ফাইল বা সেভ-করা গেম হালনাগাদ করতে সবচেয়ে সরল পথ — ফাইল খুলে O_TRUNC দিয়ে খালি করে নতুন লেখা — সবচেয়ে বিপজ্জনক: মাঝপথে crash হলে পুরোনোও নেই, নতুনও অর্ধেক। নিরাপদ পথটি চার ধাপের:

  1. নতুন বিষয়বস্তু একই ফোল্ডারের একটি temp ফাইলে লেখা।
  2. temp ফাইলের fsync — তার data এখন স্থায়ী।
  3. rename(tmp, real) — POSIX নিশ্চয়তা দেয় যে চলমান অন্য প্রসেস কখনো real-কে অনুপস্থিত বা অর্ধেক দেখবে না: হয় পুরোনো ফাইল, নয় নতুন। Journaling file system-এ এই rename একটি metadata transaction, তাই crash-এর পরেও পুরোনো বা নতুন — মাঝামাঝি নয়।
  4. ফোল্ডারের fsync — rename নিজে এখন স্থায়ী।
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static int save(const char *path, const char *text) {   /* write it, fsync it */
    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) return -1;
    ssize_t n = write(fd, text, strlen(text));
    int ok = (n == (ssize_t)strlen(text)) && fsync(fd) == 0;
    return (close(fd) == 0 && ok) ? 0 : -1;
}

int main(void) {
    const char *v2 = "balance=7500\nowner=Rahim\n";
    if (save("account.cfg", "balance=5000\nowner=Rahim\n") != 0) return 1;  /* old */
    printf("1. write account.cfg.tmp + fsync      : %s\n",
           save("account.cfg.tmp", v2) == 0 ? "ok" : "FAILED");
    printf("2. rename tmp -> account.cfg (atomic) : %s\n",
           rename("account.cfg.tmp", "account.cfg") == 0 ? "ok" : "FAILED");
    int dir = open(".", O_RDONLY | O_DIRECTORY);         /* make the rename durable */
    printf("3. fsync the directory                : %s\n",
           (dir >= 0 && fsync(dir) == 0) ? "ok" : "FAILED");
    if (dir >= 0) close(dir);
    char buf[64] = {0};
    int fd = open("account.cfg", O_RDONLY);
    ssize_t n = fd >= 0 ? read(fd, buf, sizeof buf - 1) : -1;
    if (fd >= 0) close(fd);
    printf("4. read back %zd bytes, equals new copy: %s\n", n,
           (n > 0 && strcmp(buf, v2) == 0) ? "yes" : "NO");
    printf("5. temp file still there?             : %s\n",
           access("account.cfg.tmp", F_OK) == 0 ? "yes" : "no");
    return 0;
}

আসল রানের আউটপুট (প্রতিবার একই):

1. write account.cfg.tmp + fsync      : ok
2. rename tmp -> account.cfg (atomic) : ok
3. fsync the directory                : ok
4. read back 25 bytes, equals new copy: yes
5. temp file still there?             : no

প্রোগ্রামটি প্রথমে পুরোনো সংস্করণ (balance=5000) রাখে, তারপর চার ধাপে নতুনটি বসায়। ধাপ ১-এ save() লেখার পর fsync করে, আর close()-এর ফেরত মানও দেখে (NFS-এর মতো কিছু file system ত্রুটি close-এ জানায়)। ধাপ ২-এর rename-এর পর account.cfg নামটি এক মুহূর্তেই নতুন inode-কে দেখায়। ধাপ ৩-এ O_DIRECTORY দিয়ে ফোল্ডারটি খুলে fsync — সবচেয়ে বেশি ভুলে যাওয়া ধাপ। ধাপ ৪-৫ যাচাই করে: পড়ে আনা ২৫ বাইট নতুন বিষয়বস্তুর সাথে হুবহু মেলে, আর temp ফাইলটি আর নেই (rename সেটিকে “সরিয়ে” দিয়েছে, কপি করেনি)। কোনো ধাপে crash হলে কী থাকত? ধাপ ২-এর আগে: পুরোনো account.cfg অক্ষত (বড়জোর একটি অবশিষ্ট .tmp). ধাপ ২-এর পরে: পুরোনো বা নতুন, কখনো খালি বা অর্ধেক নয়। ধাপ ৩-এর পরে: নিশ্চিতভাবে নতুন। এই প্রোগ্রাম চলেছে আগের bash আউটপুটের ext4 ফোল্ডারে — overlay root-এ চালালে fsync-গুলো সফল দেখাত, কিন্তু fsync=volatile-এর কারণে আসল নিশ্চয়তা দিত না।

ভিন্ন OS-এ একই প্যাটার্ন

open → write → fsync(file) → rename → fsync(dir). fdatasync চলবে যদি শুধু data দরকার। Shell থেকে GNU coreutils-এর sync FILE (নির্দিষ্ট ফাইলের fsync) আর mv (একই file system-এ rename) দিয়ে একই কাজ করা যায়।

macOS-এ fsync() data-কে ডিস্ক পর্যন্ত পাঠায়, কিন্তু Apple-এর man page অনুযায়ী ডিস্ককে তার নিজের cache খালি করতে বাধ্য করে না। পূর্ণ নিশ্চয়তার জন্য fcntl(fd, F_FULLFSYNC) লাগে, যা ধীর।

FlushFileBuffers(handle) fsync-এর সমতুল্য (ডিস্কের cache flush-সহ)। Replace-এর জন্য ReplaceFileW বা MoveFileExW-এ MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH. NTFS-এ rename একটি journaled metadata বদল; ঠিক কোন flag-এ কতটা নিশ্চয়তা, Microsoft-এর ডকুমেন্টেশন মিলিয়ে নিন।

দুটি বাস্তব শিক্ষা — ২০০৯ আর ২০১৮

২০০৯ — ext4-এর “শূন্য-বাইট ফাইল” বিতর্ক। ext4-এর একটি বড় উন্নতি delayed allocation: write()-এর সময় block বরাদ্দ না করে writeback পর্যন্ত অপেক্ষা, যাতে বড় একটানা জায়গা পাওয়া যায়। ২০০৯ সালের শুরুতে Ubuntu-র পরীক্ষামূলক সংস্করণে ext4 ব্যবহারকারীদের কেউ কেউ জানান, crash-এর পর কনফিগ ফাইল শূন্য বাইটের হয়ে গেছে। যেসব অ্যাপ write tmp → rename করত কিন্তু fsync করত না, তাদের rename-এর metadata কয়েক সেকেন্ডেই commit হতো, অথচ data তখনো বরাদ্দই হয়নি — নতুন নাম একটি খালি ফাইলকে দেখাত। ext3-এর ordered মোডে একই প্যাটার্ন “ঘটনাচক্রে” নিরাপদ ছিল। শেষে দুই দিকেই বদল আসে: ext4-এ একটি heuristic (আজকের auto_da_alloc, উপরের bash আউটপুটে দেখেছেন), আর অনেক অ্যাপে fsync. বিবরণে সূত্রভেদে পার্থক্য আছে; বিতর্কটি প্র ০১-এ। শিক্ষা: আকস্মিক আচরণের ওপর নয়, লিখিত নিশ্চয়তার ওপর ভরসা।

২০১৮ — “fsyncgate”. ২০১৮ সালের মার্চে PostgreSQL-এর মেইলিং লিস্টে Craig Ringer দেখান: PostgreSQL ধরে নিত যে fsync ব্যর্থ হলে পরে আবার fsync করলে kernel লেখাটি আবার চেষ্টা করবে। কিন্তু Linux-এ (আর কয়েকটি অন্য OS-এ) writeback-এর I/O ত্রুটির পর dirty page সাধারণত “পরিষ্কার” হয়ে যেত, ত্রুটির খবর আসত একবারই — দ্বিতীয় fsync সফল ফিরত, অথচ ডেটা ডিস্কে পৌঁছায়নি। PostgreSQL পরের সংশোধনী রিলিজগুলোতে fsync ব্যর্থ হলে সার্ভার থামিয়ে (PANIC) WAL থেকে recovery-র পথ নেয় (প্র ০২)। শিক্ষা: fsync-এর ত্রুটি মানে ডেটা হয়তো চিরতরে গেছে। ডেটাবেসের WAL আর fsync-এর বিস্তারিত DBMS কোর্সের Recovery পর্বে।

আরও গভীরে: Linux-এ JBD2-এর commit-কোড (fs/jbd2/commit.c) ordered মোডে প্রথমে transaction-এর inode-গুলোর data writeback শেষ হওয়ার অপেক্ষা করে, তারপর metadata block journal-এ লেখে, আর barrier চালু থাকলে commit block পাঠায় REQ_PREFLUSH | REQ_FUA দিয়ে; journal_async_commit বিকল্পে checksum-এর ভরসায় commit block আগের লেখার অপেক্ষা ছাড়াই যায়। ২০১৪ সালের OSDI-তে Pillai ও সহকর্মীদের প্রবন্ধ “All File Systems Are Not Created Equal” ALICE নামের টুল দিয়ে দেখায় যে জনপ্রিয় অনেক অ্যাপ্লিকেশন (ডেটাবেস, version control, key-value store) কোনো না কোনো file system-এর বৈধ আচরণে crash-এর পর ভুল অবস্থায় পড়তে পারে — কারণ তারা file system-এর অলিখিত ক্রমের ওপর নির্ভর করে। আর ২০২০ সালের USENIX ATC-তে “Can Applications Recover from fsync Failures?” প্রবন্ধ fsyncgate-এর প্রশ্নটি বিভিন্ন file system আর অ্যাপ জুড়ে পদ্ধতিগতভাবে মেপেছে।

৮ · AI-এর যুগে crash consistency

ফাইল নিরাপদে সেভ করার কোড চাইলে AI দ্রুত কোড দেবে, আর সেটি চালালে প্রতিবার “কাজ করবে”। সমস্যা হলো, crash consistency-র বাগ সাধারণ পরীক্ষায় ধরা পড়ে না, ধরা পড়ে লোডশেডিং-এর রাতে। AI-এর পরিচিত ভুলগুলো:

AI-এর ভুলফলকীভাবে ধরবেন
সরাসরি O_TRUNC করে লেখা, বা rename আছে কিন্তু fsync নেইcrash-এ খালি বা অর্ধেক ফাইলচার ধাপ মিলিয়ে দেখুন: write → fsync(file) → rename → fsync(dir)
ফোল্ডারের fsync বাদনতুন নাম crash-এ হারাতে পারেman 2 fsync-এর NOTES অংশ; কোডে O_DIRECTORY খুঁজুন
fsync-এর ফেরত মান না দেখা, বা ব্যর্থ হলে আবার চেষ্টা করে সফল ধরানিঃশব্দে ডেটা হারানো (fsyncgate)প্রতিটি fsync/close-এর ত্রুটি সামলানো হয়েছে কি না দেখুন
গতির জন্য nobarrier, data=writeback বা ডিস্কের cache-সেটিং বদলের পরামর্শjournal থাকলেও corruption বা জঞ্জালহার্ডওয়্যারে power-loss protection আছে কি না নিশ্চিত না হলে বদলাবেন না
macOS-এ সাধারণ fsync-কে পূর্ণ নিশ্চয়তা ধরা, বা Windows-এর জন্য Linux-এর কোডভুল OS-এ ভুল নিশ্চয়তাপ্রম্পটে OS আর file system বলুন; Apple-এর F_FULLFSYNC, Win32-এর FlushFileBuffers মিলিয়ে নিন
mount করা পার্টিশনে fsck চালাতে বলা, বা “ext4-এ প্রতি crash-এর পর fsck” বলাfsck নিজেই ক্ষতি করতে পারে; অকারণে দীর্ঘ downtimefsck শুধু unmount বা read-only অবস্থায়; journaling-এ সাধারণত শুধু replay লাগে

ডেটা-নিরাপত্তার কোডে AI-কে কাজে লাগাতে পরিবেশ, প্রতিশ্রুতি আর পরীক্ষার দাবি — তিনটিই স্পষ্ট করে দিন:

আমার পরিবেশ: Ubuntu 24.04, ext4 (data=ordered, ডিফল্ট mount), প্রোগ্রাম C-তে। আমার অ্যাপ প্রতি মিনিটে একটি ছোট JSON স্টেট-ফাইল (প্রায় ২ KB) হালনাগাদ করে। লক্ষ্য: যেকোনো মুহূর্তে বিদ্যুৎ গেলেও reboot-এর পর ফাইলটি হয় পুরোনো সম্পূর্ণ সংস্করণ, নয় নতুন সম্পূর্ণ সংস্করণ — কখনো খালি বা অর্ধেক নয়। ১. atomic replace প্যাটার্নে কোড লেখো: temp ফাইল, fsync, rename, ফোল্ডারের fsync — প্রতিটি system call-এর ফেরত মান আর errno সামলে। ২. প্রতিটি ধাপের পরে crash হলে ডিস্কে কী থাকবে, একটি টেবিলে দেখাও। ৩. fsync ব্যর্থ হলে কী করা উচিত (আবার চেষ্টা কেন যথেষ্ট নয়) ব্যাখ্যা করো। ৪. macOS আর Windows-এ কোন অংশ বদলাতে হবে, আলাদা করে বলো। ৫. nobarrier, data=writeback বা sync বন্ধ করার মতো গতি-বাড়ানোর পরামর্শ দেবে না।

AI-এর কোড মানার আগে — চেকলিস্ট

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Crash consistency সমস্যা কী? একটি উদাহরণ দিন। — একটি append-এ inode, bitmap আর data block তিনটি আলাদা লেখা; যেকোনো উপসেট পৌঁছালে leak, garbage বা পরস্পরবিরোধী metadata হতে পারে।
  • fsck-এর সীমাবদ্ধতা কী? — সময় ডিস্কের আকারের অনুপাতে; শুধু metadata-কে পরস্পরের সাথে মেলায়, তাই inode আর bitmap মিললেও block-এর জঞ্জাল ধরতে পারে না।
  • Journaling-এ commit block আলাদা করে কেন লেখা হয়? — ডিস্ক ক্রম বদলাতে পারে; TxE আগে বসে crash হলে recovery জঞ্জাল replay করত। তাই আগে বাকি সব স্থায়ী (FLUSH), তারপর একটি sector-এর atomic TxE.
  • ext4-এর data=ordered কী নিশ্চয়তা দেয়? — শুধু metadata journal হয়, কিন্তু নতুন data আসল জায়গায় লেখার পরে metadata commit; তাই নতুন বরাদ্দ করা block-এ জঞ্জাল দেখা যায় না।
  • Copy-on-write file system-এ snapshot কেন সস্তা? — কোনো block জায়গায় বদলায় না; পুরোনো root ধরে রাখলেই পুরো পুরোনো গাছ থাকে, খরচ শুধু পরে বদলানো block-এর।

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

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

প্র ০১data=journal সবচেয়ে নিরাপদ হলে ext3 আর ext4 ডিফল্ট হিসেবে ordered মোড কেন বেছে নিল? আর ২০০৯-এর শূন্য-বাইট বিতর্কে আসলে দোষ কার ছিল — file system-এর, নাকি অ্যাপের?

Data journaling প্রতিটি data block দুবার লেখে। একটি সাধারণ ডেস্কটপ বা সার্ভারে লেখার বেশিরভাগই data — ছবি, লগ, ডাউনলোড, ডেটাবেসের পাতা। তাই data=journal মানে ডিস্কের লেখার ক্ষমতা প্রায় অর্ধেক। অন্যদিকে writeback মোড দ্রুত, কিন্তু crash-এর পর ফাইলে অন্য কারও পুরোনো ডেটা দেখা যেতে পারে — বহু-ব্যবহারকারীর Unix সিস্টেমে এটি নিরাপত্তার ছিদ্র। Ordered মোড মাঝের পথ: journal-এ শুধু ছোট metadata, অথচ “metadata কখনো অপ্রস্তুত data দেখাবে না” নিয়মে জঞ্জাল দূর।

২০০৯-এর বিতর্ক দেখায় ডিফল্টের প্রভাব কত গভীর। ext3-এর ordered মোডে প্রতি ৫ সেকেন্ডের commit-এর সাথে data-ও লিখে ফেলতে হতো, তাই “write tmp → rename” (fsync ছাড়া) প্যাটার্ন বাস্তবে প্রায় সবসময় কাজ করত। বছরের পর বছর অ্যাপ ডেভেলপাররা এর ওপর নির্ভর করে ফেলেছিলেন, আর fsync এড়াতেন কারণ ext3-এ একটি fsync পুরো file system-এর জমে থাকা data লিখিয়ে দিত। ext4-এর delayed allocation সেই আকস্মিক নিশ্চয়তা সরিয়ে দিল।

আনুষ্ঠানিক যুক্তিতে Ted Ts'o ঠিক ছিলেন: POSIX rename-এর আগে data টেকসই হওয়ার কথা কোথাও বলে না, আর fsync ছাড়া কোনো নিশ্চয়তা নেই। ব্যবহারিক যুক্তিতে সমালোচকরাও ঠিক: হাজার হাজার বিদ্যমান অ্যাপ ভেঙে দেওয়া একটি file system-এর গ্রহণযোগ্যতা নষ্ট করে, আর ব্যবহারকারী শুধু দেখেন তাঁর সেটিং হারিয়ে গেছে। তাই সমাধান এল দুই দিক থেকে — ext4 rename-over-existing আর truncate-এর ক্ষেত্রে data আগে পাঠানোর heuristic যোগ করল, আর অ্যাপগুলো fsync যোগ করতে লাগল।

মূল উপলব্ধি: একটি নিশ্চয়তা কাগজে না থাকলেও, যথেষ্ট দিন বাস্তবে থাকলে মানুষ তার ওপর নির্ভর করে ফেলে। সিস্টেম-ডিজাইনারকে তাই শুধু স্পেসিফিকেশন নয়, প্রচলিত আচরণকেও সম্মান করতে হয় — আর অ্যাপ-ডেভেলপারকে অলিখিত আচরণের বদলে লিখিত প্রতিশ্রুতি (fsync) ব্যবহার করতে হয়।

প্র ০২fsyncgate-এর পর ডেটাবেসগুলো fsync ব্যর্থ হলে পুরো সার্ভার থামিয়ে দেয়। এটি কি অতিরিক্ত প্রতিক্রিয়া, নাকি একমাত্র সৎ পথ?

একটি ডেটাবেসের durability-র প্রতিশ্রুতি এমন: “commit সফল বলেছি মানে crash-এর পরেও থাকবে।” এই প্রতিশ্রুতির ভিত্তি WAL আর fsync — log record ডিস্কে স্থায়ী হলে তবেই commit-কে সফল বলা। PostgreSQL ডেটা-ফাইলগুলো checkpoint-এর সময় fsync করে, আর ধরে নিত যে fsync একবার ব্যর্থ হলে পরের checkpoint-এ আবার চেষ্টা করা যাবে, কারণ dirty page তো মেমরিতে থেকেই যাবে।

২০১৮ সালে বোঝা গেল অনুমানটি Linux-এ (আর কয়েকটি অন্য OS-এ) সত্য নয়। Writeback-এর সময় ডিস্ক ত্রুটি দিলে kernel page-গুলো আর dirty রাখত না (নইলে নষ্ট ডিস্ক RAM ভরিয়ে রাখত)। ত্রুটির খবর ফাইলের ওপর একবার রাখা হতো, আর পরের fsync সেটি একবার জানাত। তাই PostgreSQL-এর দ্বিতীয় fsync সফল দেখাত, অথচ ডেটার একটি অংশ কখনো ডিস্কে পৌঁছায়নি — আর ডেটাবেস WAL-এর সেই অংশ পুনর্ব্যবহারের জন্য মুছে দিতে পারত।

এ অবস্থায় একমাত্র নিরাপদ জায়গা হলো WAL — যা ডেটা-ফাইলে পৌঁছায়নি, তা WAL-এ এখনো আছে। তাই সমাধান: fsync ব্যর্থ মানেই PANIC, সার্ভার বন্ধ, তারপর recovery শেষ নিশ্চিত checkpoint থেকে WAL replay করে আবার লিখবে। এটি কঠোর মনে হতে পারে, কিন্তু বিকল্প হলো “সব ঠিক আছে” বলে চলতে থাকা যখন কিছু ঠিক নেই — ডেটাবেসের জন্য সবচেয়ে খারাপ ব্যর্থতা হলো নিঃশব্দ ব্যর্থতা। পরে গবেষণায় (২০২০ সালের “Can Applications Recover from fsync Failures?”) দেখা যায়, বিভিন্ন file system fsync-ত্রুটির পর ভিন্ন ভিন্ন আচরণ করে, আর অনেক অ্যাপই কোনো না কোনোভাবে ভুল করে।

মূল উপলব্ধি: ত্রুটি-পথ সবচেয়ে কম পরীক্ষিত পথ। একটি নিশ্চয়তা যখন ভেঙে যায়, তখন “থেমে গিয়ে জানা অবস্থা থেকে আবার শুরু” প্রায়ই “চালিয়ে যাওয়া আর আশা করা”-র চেয়ে নিরাপদ — fail-stop নকশার মূল কথা এটিই।

প্র ০৩ZFS আর Btrfs checksum, snapshot আর fsck-মুক্ত consistency দেয়। তাহলে সব Linux সার্ভার এখনো ext4 বা XFS-এ কেন — COW কেন সবার ডিফল্ট নয়?

প্রথম কারণ কর্মক্ষমতার ধরন। COW-এ একটি ছোট overwrite মানে নতুন জায়গায় লেখা আর root পর্যন্ত পথ বদলানো। ডেটাবেস আর ভার্চুয়াল মেশিনের disk image ক্রমাগত ফাইলের মাঝখানে ছোট ছোট overwrite করে; COW-এ এগুলোর block সময়ের সাথে ডিস্কে ছড়িয়ে পড়ে, পরে ক্রমানুসারে পড়া ধীর হয়, আর প্রতিটি লেখায় metadata-ও লেখা হয়। Btrfs এর জন্য nodatacow বিকল্প দেয়, কিন্তু তখন সেই ফাইলের checksum-ও যায়। ZFS এটি সামলাতে বড় মেমরি-cache আর টিউনিং চায়।

দ্বিতীয় কারণ পরিপক্বতা আর আস্থা। ext4 আর XFS-এর কোড বহু বছর ধরে বিশাল সংখ্যক সার্ভারে পরীক্ষিত, আচরণ অনুমানযোগ্য। Btrfs-এর কিছু বৈশিষ্ট্য — বিশেষত তার নিজস্ব RAID5/6 — দীর্ঘদিন ধরে তার ডকুমেন্টেশনেই অস্থিতিশীল বলে সতর্ক করা হয়েছে। Red Hat ২০১৭ সালে RHEL থেকে Btrfs-এর সমর্থন সরিয়ে নেওয়ার ঘোষণা দেয়, আবার Fedora আর openSUSE একে ডিফল্ট রেখেছে, আর Meta-র মতো কিছু বড় প্রতিষ্ঠান বড় আকারে ব্যবহারের কথা জানিয়েছে — অর্থাৎ মতভেদ এখনো আছে।

তৃতীয় কারণ আইন ও বিতরণ। ZFS-এর লাইসেন্স (CDDL) Linux kernel-এর GPL-এর সাথে সাংঘর্ষিক কি না, তা নিয়ে আইনি মতভেদের কারণে ZFS মূল kernel-এর অংশ নয়; আলাদা module হিসেবে চলে, যা কিছু distro আর প্রতিষ্ঠানের নীতির সাথে মেলে না।

চতুর্থত, অনেক সার্ভারে snapshot আর checksum অন্য স্তরে দেওয়া হয় — LVM-এর snapshot, স্টোরেজ অ্যারে, ক্লাউডের block storage-এর snapshot, বা অ্যাপের নিজস্ব replication. তখন সাধারণ, দ্রুত ext4 বা XFS যথেষ্ট।

মূল উপলব্ধি: “সবচেয়ে বেশি বৈশিষ্ট্য” আর “সবচেয়ে ভালো পছন্দ” এক নয়। Workload-এর ধরন, পরিপক্বতা, লাইসেন্স আর অন্য স্তরে কী আছে — এগুলো মিলিয়েই file system বাছতে হয়।

প্র ০৪Journaling-এর সব যুক্তি দাঁড়িয়ে আছে “ডিস্ক FLUSH মানে” এই বিশ্বাসে। ডিস্ক যদি মিথ্যা বলে, তাহলে OS কী করতে পারে?

OS-এর কাছে ডিস্ক একটি কালো বাক্স: সে আদেশ পাঠায়, উত্তর পায়। ডিস্ক যদি cache-এ রেখেই “লেখা শেষ” বলে, আর FLUSH আদেশেও সত্যিই খালি না করে “শেষ” বলে, তাহলে কোনো file system — journaling, COW বা soft updates — crash-এর পর নিজের নিয়ম রক্ষা করতে পারে না। কারণ সব পদ্ধতিরই মূল অনুমান “X স্থায়ী হওয়ার পরে Y”.

এটি কাল্পনিক ভয় নয়। বিভিন্ন সময়ে প্রকৌশলীরা জানিয়েছেন যে কিছু ভোক্তা-পর্যায়ের ডিস্ক বেঞ্চমার্কে ভালো দেখাতে flush-এর অনুরোধ উপেক্ষা করত বা দেরিতে মানত। ২০১৩ সালের FAST সম্মেলনে Zheng ও সহকর্মীদের একটি গবেষণায় বিভিন্ন SSD-তে হঠাৎ বিদ্যুৎ কেটে পরীক্ষা করে অনেক ডিভাইসে ডেটা-ক্ষতি, এমনকি ইতিমধ্যে টেকসই বলে জানানো ডেটার ক্ষতিও দেখা যায়।

OS আর প্রশাসকের হাতে কয়েকটি উপায় আছে। (১) Checksum: ZFS বা Btrfs-এর মতো end-to-end checksum অন্তত ভুল ডেটাকে নিঃশব্দে ব্যবহার হতে দেয় না — ধরা পড়ে, আর mirror থাকলে অন্য কপি থেকে সারানো যায়। ext4-এর journal checksum journal-এর অসম্পূর্ণ লেখা ধরে। (২) হার্ডওয়্যার বাছাই: enterprise SSD-তে capacitor-ভিত্তিক power-loss protection থাকে। হার্ডওয়্যার RAID কন্ট্রোলারে ব্যাটারি বা flash-সমর্থিত cache. (৩) UPS আর নিয়ন্ত্রিত shutdown — বাংলাদেশের মতো যেখানে বিদ্যুৎ-বিভ্রাট পরিচিত ঘটনা, সেখানে সার্ভার-রুমে UPS কেবল সুবিধা নয়, ডেটা-নিরাপত্তার অংশ। (৪) Replication: একাধিক মেশিনে কপি, যাতে একটি ডিস্কের মিথ্যা পুরো সিস্টেমকে না ডোবায়।

মূল উপলব্ধি: প্রতিটি নিশ্চয়তা নিচের স্তরের নিশ্চয়তার ওপর দাঁড়ায়। নির্ভরযোগ্য সিস্টেম গড়তে প্রতিটি স্তরকে যাচাই করতে হয়, আর অন্তত একটি স্তরে (checksum, replication) এমন ব্যবস্থা রাখতে হয় যা নিচের স্তরের মিথ্যাকে ধরতে পারে।

প্র ০৫১৯৯১ সালের log-structured file system হার্ড ডিস্কের যুগে মূলধারায় আসেনি, অথচ আজ প্রায় প্রতিটি SSD আর অনেক ফোনের ভেতরে তার ধারণা। কী বদলাল?

Rosenblum আর Ousterhout-এর যুক্তি ছিল প্রযুক্তির প্রবণতা থেকে: RAM বাড়ছে বলে পড়া cache থেকে মিটবে, আর ডিস্কের seek-সময় খুব ধীরে কমছে বলে ছোট এলোমেলো লেখাই হবে মূল বাধা। তাই সব লেখাকে বড় ক্রমিক segment-এ রূপান্তর করাই সমাধান। তাঁদের মাপে ছোট-ফাইল-প্রধান কাজে Sprite LFS তখনকার Unix FFS-এর চেয়ে লেখায় অনেক গুণ দ্রুত ছিল।

কিন্তু বিতর্কও হয়েছিল জোরালো। Margo Seltzer ও সহকর্মীরা BSD-র জন্য LFS বাস্তবায়ন করে আর FFS-এর সাথে তুলনা করে (১৯৯৩ ও ১৯৯৫ সালের USENIX প্রবন্ধে) দেখান যে cleaning-এর খরচ বাস্তব কাজে, বিশেষত ডিস্ক বেশ ভরা থাকলে আর ট্রানজ্যাকশন-প্রধান কাজে, সুবিধার অনেকটা খেয়ে ফেলে; আর FFS-কে বড় একটানা লেখার (clustering) উন্নতি দিলে পার্থক্য অনেক কমে যায়। একই সময়ে journaling file system ছোট বদলে fsck-এর সমস্যা মিটিয়ে দিল, পুরোনো বিন্যাস প্রায় না বদলে। ফলে হার্ড ডিস্কের সার্ভারে LFS জায়গা পায়নি।

Flash এসে হিসাব উল্টে দিল। NAND flash-এ একটি page জায়গায় overwrite করা যায় না — আগে একটি বড় erase block পুরো মুছতে হয়। তাই SSD-র firmware (flash translation layer) নিজেই প্রতিটি লেখা নতুন খালি page-এ বসায়, একটি ঠিকানা-ম্যাপ রাখে, আর পটভূমিতে garbage collection করে জীবিত page সরিয়ে block মোছে — এটি হুবহু LFS-এর segment আর cleaning, শুধু ডিভাইসের ভেতরে। এর ওপর ext4-এর মতো জায়গায়-লেখা file system চালালে দুটি log-স্তর পরস্পরকে না জেনে কাজ করে। F2FS (২০১২ সালে Samsung-এর প্রস্তাব, Linux 3.8-এ যুক্ত) এই কারণেই flash-এর গঠন মাথায় রেখে log-structured নকশা নেয়, আর অনেক Android ফোনে ব্যবহৃত হয়েছে।

মূল উপলব্ধি: একটি নকশার মূল্য নির্ভর করে তার সময়ের হার্ডওয়্যারের খরচের অনুপাতের ওপর। অনুপাত বদলালে “ব্যর্থ” ধারণা ফিরে আসে — তাই পুরোনো গবেষণা পড়া মানে ভবিষ্যতের সমাধানের ভাণ্ডার রাখা।

অনুশীলন

খাতা-কলম নিন — একটি হিসাব, একটি লেখা-গোনার ট্রেস, একটি আউটপুট অনুমান আর একটি কমান্ড।

  1. fsck বনাম journal — সময়ের হিসাব: একটি ৪ TB (৪ × ১০১২ বাইট) ext4 ডিস্কে প্রতি ১৬,৩৮৪ বাইটে একটি inode, প্রতিটি inode ২৫৬ বাইট। ডিস্ক থেকে ক্রমানুসারে পড়ার গতি ১৫০ MB/s (১৫০ × ১০৬ বাইট/সেকেন্ড)। (ক) শুধু পুরো inode টেবিল একবার পড়তেই fsck-এর কত সময় লাগবে? (খ) Journal ১২৮ MiB হলে পুরো journal পড়তে কত সময়? (গ) অনুপাত কত?

    (ক) inode-এর সংখ্যা = ৪ × ১০১২ ÷ ১৬,৩৮৪ = ২৪,৪১,৪০,৬২৫টি। inode টেবিল = ২৪,৪১,৪০,৬২৫ × ২৫৬ = ৬২.৫ × ১০৯ বাইট = ৬২.৫ GB. সময় = (৬২.৫ × ১০৯) ÷ (১৫০ × ১০৬) ≈ ৪১৬.৭ সেকেন্ড ≈ ৭ মিনিট — এটি সর্বনিম্ন সীমা; আসল fsck ফোল্ডার আর block-ম্যাপও পড়ে, অনেকটা এলোমেলোভাবে, তাই বাস্তবে অনেক বেশি।

    (খ) ১২৮ MiB = ১৩,৪২,১৭,৭২৮ বাইট; সময় = ১৩,৪২,১৭,৭২৮ ÷ (১৫০ × ১০৬) ≈ ০.৮৯ সেকেন্ড।

    (গ) ৪১৬.৭ ÷ ০.৮৯৫ ≈ ৪৬৬ গুণ। আর গুরুত্বপূর্ণ কথা: ডিস্ক দ্বিগুণ হলে fsck-এর সময়ও প্রায় দ্বিগুণ, journal-এর সময় একই থাকে। (বাস্তবের ext4 অব্যবহৃত inode-টেবিল বাদ দিয়ে fsck কিছুটা দ্রুত করে, কিন্তু অনুপাতের মূল সত্যটি বদলায় না।)

  2. লেখা গোনা — batching-এর প্রভাব: একই ফাইলে পরপর ১০০টি append, প্রতিটিতে একটি নতুন data block; সবগুলোর জন্য একই inode block আর একই bitmap block বদলায়। TxB আর TxE প্রতিটি ১ block. (ক) প্রতিটি append আলাদা transaction হলে ordered মোডে মোট কয়টি block-লেখা? (খ) ১০০টি append একটি transaction-এ batch করলে ordered মোডে কয়টি? (গ) একই batch data=journal মোডে কয়টি?

    (ক) প্রতিটি append: D ১ + journal (TxB, I, B, TxE) ৪ + checkpoint (I, B) ২ = ৭; ১০০টিতে ৭০০.

    (খ) data ১০০টি block আসল জায়গায় = ১০০; journal-এ TxB + I + B + TxE = ৪ (একই I আর B-এর একটিই কপি — শেষ অবস্থা); checkpoint-এ I + B = ২. মোট ১০৬ — প্রায় সাত গুণ কম।

    (গ) journal-এ TxB + I + B + ১০০টি D + TxE = ১০৪; checkpoint-এ I + B + ১০০টি D = ১০২. মোট ২০৬ — ordered-এর প্রায় দ্বিগুণ, কারণ প্রতিটি data block দুবার লেখা। ext4 এই কারণেই commit কয়েক সেকেন্ড পরপর করে।

  3. আউটপুট অনুমান: crash simulator-এর শেষ লাইনে আরেকটি run যোগ করা হলো, যেখানে data সবার শেষে যায় — writeback মোডের একটি খারাপ ক্রম: run("4) writeback-like", ["J", "TxE", "D", "I", "B"]); ছয়টি crash-মুহূর্তের প্রতিটির জন্য কী ছাপবে?
    4) writeback-like
       crash after 0 [-]           discard -> consistent (old file)
       crash after 1 [J]           discard -> consistent (old file)
       crash after 2 [J,TxE]       replay  -> GARBAGE: metadata agrees, block holds old junk
       crash after 3 [J,TxE,D]     replay  -> consistent (new file)
       crash after 4 [J,TxE,D,I]   replay  -> consistent (new file)
       crash after 5 [J,TxE,D,I,B] replay  -> consistent (new file)

    মূল লাইনটি তৃতীয়: commit হয়ে গেছে কিন্তু data এখনো বসেনি। Replay inode আর bitmap বসায়, দুটি পরস্পরের সাথে মেলে — file system consistent, কিন্তু block 5-এ জঞ্জাল। এটিই writeback মোডের পরিচিত ঝুঁকি, আর ordered মোড ঠিক এই ক্রমটি নিষিদ্ধ করে। লক্ষ করুন, D-বিহীন ক্ষেত্রে “new data lost” লেখা নেই, কারণ D ডিস্কে নেই — classify শুধু ডিস্কে যা আছে তাই দেখে।

  4. কমান্ড লিখুন: Linux-এ (ক) root file system কোন ধরনের আর কোন বিকল্পে mount করা, দেখার কমান্ড; (খ) একটি ext4 পার্টিশন sda2-র পূর্ণ বিকল্প-তালিকা (ডিফল্টসহ) থেকে শুধু journaling মোড বের করার কমান্ড; (গ) shell থেকে app.conf নিরাপদে বদলানোর চারটি কমান্ড (temp ফাইল, তার fsync, rename, ফোল্ডারের fsync).
    $ findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS /
    TARGET SOURCE    FSTYPE OPTIONS
    /      /dev/sda2 ext4   rw,relatime
    $ grep -o 'data=[a-z]*' /proc/fs/ext4/sda2/options
    data=ordered
    $ printf 'port=8080\nmode=prod\n' > app.conf.tmp
    $ sync app.conf.tmp
    $ mv app.conf.tmp app.conf
    $ sync .

    findmnt-এ data=ordered দেখা যায় না কারণ এটি ডিফল্ট; /proc/fs/ext4/<device>/options সব দেখায়। GNU coreutils 8.24 থেকে sync-কে ফাইল বা ফোল্ডারের নাম দিলে শুধু সেটিরই fsync করে (নাম না দিলে পুরো সিস্টেমের সব dirty ডেটা)। mv একই file system-এর ভেতরে rename() ডাকে, তাই atomic; ভিন্ন file system-এ এটি কপি করে মুছে দেয় — atomic নয়, তাই temp ফাইল একই ফোল্ডারে রাখাই নিয়ম।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমext4-এর ordered মোডে একটি append-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. নতুন data block তার আসল জায়গায় লেখা
  2. Journal-এ TxB আর inode ও bitmap-এর নতুন কপি লেখা
  3. ডিস্কের cache flush — এ পর্যন্ত সব লেখা স্থায়ী
  4. Commit block TxE লেখা (FUA-সহ)
  5. Checkpoint — inode আর bitmap আসল জায়গায় লেখা
  6. Journal-এর tail এগিয়ে জায়গাটি মুক্ত করা

একটি ফাইলে নতুন block যোগ করার সময় শুধু data bitmap-এর লেখা ডিস্কে পৌঁছাল, inode আর data block-এর লেখা পৌঁছানোর আগেই crash হলো। ফল কী?

  • ফাইলে অন্যের পুরোনো ডেটা (garbage) দেখা যাবে
  • Space leak — block ব্যবহৃত চিহ্নিত, কিন্তু কারও নয়
  • দুটি ফাইল একই block ভাগ করবে
  • কোনো সমস্যা নেই, file system পুরোপুরি consistent

Journaling file system-এ একটি transaction-এর TxB, inode আর bitmap journal-এ স্থায়ীভাবে লেখা হয়েছে, কিন্তু commit block (TxE) লেখার আগেই crash হলো। Recovery কী করবে?

  • journal থেকে inode আর bitmap replay করবে
  • পুরো ডিস্কে fsck চালাবে
  • transaction-টি বাদ দেবে; ডিস্ক আগের consistent অবস্থায় থাকবে
  • শুধু inode replay করবে, bitmap নয়

ext4-এর data=ordered মোডের নিশ্চয়তা কোনটি?

  • শুধু metadata journal হয়, আর নতুন data আসল জায়গায় লেখার পরে metadata commit হয়
  • data আর metadata দুটোই journal-এ লেখা হয়
  • শুধু metadata journal হয়, data-র সাথে কোনো ক্রম নেই
  • ফাইলের প্রতিটি overwrite atomic — হয় পুরো পুরোনো, নয় পুরো নতুন

Linux-এ একটি কনফিগ ফাইল নিরাপদে বদলাতে লেখা হলো: temp ফাইলে লেখা → temp ফাইলের fsync → rename(tmp, real). Crash-এর পরেও নতুন সংস্করণ নিশ্চিত থাকতে আর কী দরকার?

  • rename-এর পর temp ফাইলের আবার fsync
  • আসল ফাইলটি O_TRUNC দিয়ে খুলে আবার লেখা
  • কিছু না, rename নিজেই সঙ্গে সঙ্গে ডিস্কে স্থায়ী
  • যে ফোল্ডারে ফাইলটি আছে, তার fsync

ZFS বা Btrfs-এর মতো copy-on-write file system-এ snapshot প্রায় বিনা খরচে নেওয়া যায় কেন?

  • snapshot-এর সময় সব block দ্রুত অন্য ডিস্কে কপি হয়
  • block কখনো জায়গায় overwrite হয় না, তাই পুরোনো root রেখে দিলেই পুরোনো গাছ থাকে
  • journal-এ পুরো file system-এর কপি থাকে
  • checksum থেকে পুরোনো ডেটা আবার তৈরি করা যায়

একটি block-এর append-এ inode, bitmap আর data block প্রতিটি ১ block, আর journal-এর TxB ও TxE প্রতিটি ১ block. data=journal মোডে (checkpoint-সহ) মোট কয়টি block লেখা হয়?

  • ৩
  • ৭
  • ৮
  • ১০

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. সমস্যা: একটি কাজ (append) = inode + bitmap + data, তিনটি আলাদা লেখা; আংশিক পৌঁছালে leak (B), garbage (I+B), corrupt (I, I+D) — শুধু “কিছুই না” আর “সব” নিরাপদ।
  2. fsck metadata পরস্পরের সাথে মেলায়: superblock, bitmap, inode, link count, duplicate, directory. সময় ডিস্কের আকারের অনুপাতে, আর garbage ধরতে পারে না।
  3. Journaling: TxB + বদল → (FLUSH) → TxE = commit → checkpoint → free; recovery শুধু committed transaction replay করে (idempotent). Batching, circular log, revoke record.
  4. ext4-এর মোড: journal (data দুবার), ordered (ডিফল্ট: আগে data, পরে metadata commit), writeback (ক্রম নেই, জঞ্জাল সম্ভব)। FLUSH আর FUA ছাড়া কোনোটিরই নিশ্চয়তা নেই।
  5. বিকল্প আর অ্যাপের দায়: LFS (segment, cleaning), COW (নতুন জায়গায় লেখা, uberblock বদল, snapshot, checksum), soft updates; অ্যাপে temp → fsync → rename → ফোল্ডারের fsync, আর fsync-এর ত্রুটি গুরুত্বের সাথে।

পরের পর্বে: crash-এ টিকে থাকা গেল, কিন্তু পুরো ডিস্কটাই যদি নষ্ট হয়? একাধিক ডিস্ক মিলিয়ে mirroring, striping আর parity — RAID 0, 1, 5, 6, 10-এর হিসাব, rebuild-এর ঝুঁকি, আর কেন “RAID ব্যাকআপ নয়”।

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

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান