WAL ও ARIES Recovery — crash-এর পরে হিসাব
Write-Ahead Log and ARIES Recovery
Crash যেকোনো মুহূর্তে আসতে পারে, আর buffer pool disk-এ যা-ই লিখে ফেলতে পারে — WAL আর ARIES-এর তিন-pass recovery কীভাবে সব committed কাজ ফিরিয়ে আনে আর সব uncommitted কাজ নীরবে মুছে দেয়।
আগে এটা বুঝি
Buffer pool লেসনে দেখেছি — database-এর সব read/write আসলে memory-তে ঘটে; dirty page background-এ নিজস্ব সময়সূচিতে disk-এ যায়। দারুণ দ্রুত। কিন্তু অশুভ প্রশ্নটা ঝুলে আছে: ঠিক যেই মুহূর্তে disk-এর হাফ লেখা শেষ, তখনই power chale jabe?
সেই মুহূর্তে disk-এ পড়বে এক অদ্ভুত মিশ্রণ: কিছু transaction-এর লেখা পৌঁছেছে, কিছুর পৌঁছায়নি; কোনো page-এ এমন transaction-এর অর্ধেক কাজও থাকতে পারে যেটা কখনো commit-ই হয়নি। ACID-এর promise — committed-দের প্রভাব টিকবে, uncommitted-দের চিহ্ন মুছে যাবে। এই দুই দিকের মেরামতই crash recovery, এই লেসনের বিষয়।
আর recovery-র পুরো নকশা ঠিক হয়ে যায় normal operation-এর দুই সিদ্ধান্তে — buffer manager কি dirty page ইচ্ছেমতো disk-এ লিখতে পারবে (steal), commit-এ কি সব page flush করতে হবে (force)। দুটো উত্তরই “না” — আর সেখান থেকেই WAL-এর নিয়ম আর ARIES-এর তিন pass-এর জন্ম।
মূল ধারণা
Steal আর no-force — দুটো “না” যেগুলো সব বদলে দেয়
ধরুন একটা transaction ১০০০টা page বদলেছে, তারপর commit করল। Force policy মানে commit-এর মুহূর্তে সেই ১০০০টা page disk-এ flush — হবুল-ফবুল খরচ; hot page প্রতি commit-এ আবার লেখা। বাস্তবে কেউ করে না। No-force: commit মানেই শুধু commit log record durable — data page যাবে buffer manager-এর সুবিধামতো সময়ে।
উল্টো দিকে: pool ভরে গেছে, victim হয়েছে dirty page যেটা একটা চলমান transaction লিখেছে। Steal-no policy বলত ওটা ফেলা যাবে না — তাহলে uncommitted কাজের page memory আটকে রাখবে, pool ভরে সিস্টেম থেমে যাবে। তাই steal: victim যা-ই হোক, dirty হলেও disk-এ লেখা চলে।
| Policy | মানে | না মানলে যা হতো |
|---|---|---|
| No-force | commit-এ data page flush বাধ্যতামূলক নয় | প্রতিটা commit-এ write burst |
| Steal | uncommitted txn-এর dirty page-ও disk-এ যায় | pool ভরে deadlock |
এই দুটো স্বাধীনতার দাম — crash-পরবর্তী disk-অবস্থা দুই দিকেই “ভুল”: committed কাজ হারাতে পারে (no-force: page পৌঁছায়নি), আর uncommitted কাজ disk-এ জমা থাকতে পারে (steal: লেখা already গেছে)।
তাই recovery-রও দুই কাজ: redo (committed ফিরিয়ে আনা) আর undo (uncommitted মুছে ফেলা)। উপকরণ — log।
WAL rule — একটা মাত্র ordering contract
পুরো ব্যবস্থার ভিত্তি একটা নিয়ম — Write-Ahead Logging:
data page-এর log record নিজে stable storage-এ পৌঁছানোর আগে সেই page কখনো stable storage-এ লেখা হবে না।
এটা একটা fsync ordering contract — “কী লিখব” নয়, “কোনটা আগে লিখব”। Dirty page flush-এর আগে সেই page-এর নতুনতম log record পর্যন্ত log fsync বাধ্যতামূলক; আর client-কে “committed” উত্তরের আগেই commit record fsync হতে হয়। Commit point আসলে এই মুহূর্তটাই — data page disk-এ গেছে কি না তাতে কিছু যায় আসে না; commit record durable হওয়াটাই সংজ্ঞা।
Log record-এর anatomy — LSN, redo আর undo
প্রতিটা log record-এর একটা LSN (Log Sequence Number) — log-এ byte-position, কেবল বাড়ন্ত। Record-এর ভেতরটা:
| Field | কাজ |
|---|---|
| LSN | এই record-এর অনন্য, বাড়ন্ত অবস্থান |
| type | BEGIN / UPDATE / COMMIT / ABORT / CLR / checkpoint |
| txid | কোন transaction-এর কাজ |
| page ID | কোন page বদলাল |
| redo info | আবার করলে একই ফল হয় — সাধারণত physical: page, offset, new value |
| undo info | ফেরত নিতে যা লাগে — সাধারণত old value |
| prev LSN | একই transaction-এর আগের record — undo-chain-এর সূতা |
আর সবচেয়ে সূক্ষ্ম অংশ: প্রতিটা page-এর header-এও একটা LSN জমা থাকে — page LSN = “এই page-এ সর্বশেষ কোন log record প্রয়োগ হয়েছে”। Page আর log আলাদা schedule-এ disk-এ গেলেও এই সংখ্যা বলে দেয় page-টা log-এর কোথা পর্যন্ত আপডেট।
Idempotent redo — page_LSN \< rec.LSN শর্তটা
Redo pass একটা update record apply করবে কি না — সিদ্ধান্ত এক তুলনায়: disk-copy-র page LSN যদি record-এর LSN-এর আগের হয়, তবেই apply, নাহলে skip। এতে redo idempotent: একবার apply হয়ে গেলে শর্তটা আর সত্যি হয় না, তাই একই record recovery যতবার চলুক ঠিক একবারই কার্যকর। Recovery-র মাঝেই crash হলেও রক্ষা — পরের recovery নিঃশঙ্কচিত্তে সব আবার করতে পারে।
ভেতরে কী ঘটছে
ARIES — তিনটা pass-এর নাটক
IBM Almaden-এর ARIES (Mohan ও সহযোগীদের 1992 paper) এই জগতের canonical নকশা — PostgreSQL-সহ বেশিরভাগ database এই চিন্তায় চলে। তিন pass, ঠিক এই ক্রমে:
- Analysiscrash-এর মুহূর্তে কোন txns চলছিল, কোন pages dirty — তালিকা বানানো
- Redoforward scan — history repeat, uncommitted-ও
- Undobackward scan — losers ফেরত, CLR লেখা
Pass 1 — Analysis: অবস্থা জানা
Checkpoint থেকে দুই তালিকা: active txns আর dirty pages। Log scan-এ
হিসাব টাটকা: commit/abort পেলে বাদ, BEGIN পেলে যোগ। শেষে losers
— crash-মুহূর্তে commit/abort-হীন — আর dirty pages-এর পুরনোতম
recLSN, redo-র শুরুর-রেখা।
Pass 2 — Redo: repeating history
ARIES-এর সবচেয়ে counter-intuitive সিদ্ধান্ত: redo pass সব
update প্রয়োগ করে — uncommitted-দেরটাও। steal-policy-তে disk-
এ কী পৌঁছেছে জানার উপায় নেই; নির্ভরযোগ্য পথ একটাই — history
পুরোটা disk-এ আবার চালিয়ে দেওয়া, যাতে disk crash-মুহূর্তের
memory-অবস্থায় ফেরে। সুবিধা: undo deterministic অবস্থা থেকে শুরু
করে; page_LSN শর্ত up-to-date page-এর খরচ বাঁচায়।
Pass 3 — Undo: backward + CLR
Losers ফেরত: scan উল্টো দিকে — পরের লেখা আগেরটা overwrite
করেছে, শেষ-লেখা-আগে-ফেরত না করলে ভুল old value বসে। প্রতিটা
loser-update-এর undo info প্রয়োগ করে CLR (Compensation Log
Record) লেখা হয়, ভেতরে undo_next_lsn — undo-chain-এর পরের stop।
CLR দুই সমস্যা একসাথে মেটায়:
- Recovery-র মাঝে আরেকবার crash: CLR durable হওয়ার পরে দ্বিতীয় recovery-র redo pass CLR replay করে — অর্ধেক-done rollback টিকে থাকে; CLR কখনো undo হয় না (redo-only) — এটাই idempotence।
- Progress tracking:
undo_next_lsnalready-compensated update-এর ঠিকানা — একই কাজ দুবার নয়।
Fuzzy checkpoint — teaser
থামা-flush checkpoint production-এ অসম্ভব। ARIES-এর উত্তর: fuzzy checkpoint — চলন্ত অবস্থাতেই active-txn আর dirty-page তালিকা log-এ লেখা, flush নিজের মতো; Analysis সেই তালিকা থেকেই শুরু। কম্পাঙ্ক-খরচ-recovery-time হিসাবটাই পরের লেসন।
উদাহরণ
একটা crash, ধাপে ধাপে
তিনটা transaction, একটা page P (শুরুতে A), আর অমার্জন্ময় crash:
T1: BEGIN; P:=B; COMMIT <- committed; P disk-এ যায়নি (no-force)
T2: BEGIN; P:=C <- in-flight; steal করে P flush হয়েছে!
T3: BEGIN; P:=D <- in-flight, শুধু log-এ
*** CRASH ***Log: 1:T1-BEGIN 2:T1-P:=B 3:T1-COMMIT 4:T2-BEGIN 5:T2-P:=C 6:T3-BEGIN 7:T3-P:=D। Disk-এ P: steal-এ LSN 5 পর্যন্ত আপডেট —
uncommitted C বসে আছে, committed B-ও পৌঁছায়নি।
Analysis: losers = {T2, T3}।
Redo (forward) — disk crash-মুহূর্তের হুবহু অবস্থায় (D, uncommitted
হলেও):
rec 2: page_LSN(-1) \< 2 -> P:=B, page_LSN=2
rec 5: page_LSN(2) \< 5 -> P:=C, page_LSN=5
rec 7: page_LSN(5) \< 7 -> P:=D, page_LSN=7Undo (backward) — T3-এর D ফেরত, তারপর T2-এর C → CLR-সহ:
rec 7 (T3): P:=old-of-T3, CLR(undo_next=nil)
rec 5 (T2): P:=B (T2-এর old value), CLR(undo_next=...)শেষ ফল: P = B — T1-এর committed মান। Committed কাজ ফিরে পেল,
uncommitted-দের চিহ্ন নেই — durability আর atomicity দুটোই রক্ষা।
ছবিতে
পুরো pipeline এক ছবিতে
NORMAL OPERATION CRASH RECOVERY
┌─────────────────────┐ ┌───────────────────────────┐
│ txn write │ │ ANALYSIS (last ckpt থেকে) │
│ ▼ │ │ losers + dirty pages │
│ [1] UPDATE record │ │ ▼ │
│ ▼ │ │ REDO (forward scan) │
│ [2] log fsync │ │ history repeat — │
│ │ ┌────────┐ │ │ uncommitted-ও; শর্ত: │
│ └───►│ │ │ │ page_LSN \< rec.LSN │
│ ▼ ▼ │ │ ▼ │
│ [3] data page flush │ │ UNDO (backward scan) │
│ (শুধু [2]-এর │ │ losers + CLR (redo-only, │
│ পরে!) │ │ idempotent) │
│ COMMIT = commit- │ └───────────────────────────┘
│ record fsync│
└─────────────────────┘আর undo-chain এক লাইনে: U1 ── U2 ── U3 (এক txn-এর updates,
prev-LSN-সূতায় বাঁধা) → backward undo প্রতিটার জন্য old ফেরত +
CLR(undo_next→পরেরটা) লেখে, শেষ CLR-এর undo_next=nil। দ্বিতীয়
crash হলে redo pass CLR replay করে অর্ধেক-done rollback ধরে
রাখে; undo pass বাকিটা শেষ করে — কোনো কাজ দুবার নয়, বাদও নয়।
নিজে চালিয়ে দেখুন
বাস্তব WAL খুলে পড়ুন — pg_waldump আর pg_current_wal_lsn
ধাপ ১ — ছোট scratch cluster, checksum-সহ:
initdb -D /tmp/waldemo --data-checksums
pg_ctl -D /tmp/waldemo -o "-p 5544" -l /tmp/waldemo.log start
export PGPORT=5544
psql -c "CREATE TABLE t(id int, note text);"ধাপ ২ — LSN দেখা আর WAL ঘোরানো:
psql -c "SELECT pg_current_wal_lsn();" # যেমন: 0/181D2A0
psql -c "INSERT INTO t VALUES (1,'wal rules');"
psql -c "SELECT pg_current_wal_lsn();" # বেড়েছে — প্রতিটা লেখার দাম
psql -c "SELECT pg_switch_wal();"pg_current_wal_lsn হুবহু আমাদের LSN ধারণা — log-এ বর্তমান
অবস্থান। pg_switch_wal segment (16 MB ফাইল) ঘুরিয়ে দেয়।
ধাপ ৩ — record decode:
pg_waldump -p /tmp/waldemo/pg_wal -s 0/01000000 -n 25Annotated নমুনা:
rmgr: Heap len (rec/tot): 58/ 1178, tx: 742,
desc: INSERT off 1 flags 0x00, blkref #0: rel 1663/5/16384 blk 1 FPW
rmgr: Transaction len (rec/tot): 72/ 72, tx: 742,
desc: COMMIT @ 2026-08-24 12:00:00
rmgr: XLOG desc: SWITCHHeap ... rel .../16384 blk 1—t-table-এর update record;FPW= checkpoint-পরব প্রথম ছোঁয়ায় page-image (পরের লেসনের নায়ক)Transaction ... COMMIT— commit point;SWITCH—pg_switch_wal()-এর record-এর record
ধাপ ৪ — torn-page আলোচনার read-only প্রদর্শনী:
oid=$(psql -Atc "SELECT relfilenode FROM pg_class WHERE relname='t'")
dd if=/tmp/waldemo/base/5/$oid bs=8192 count=1 status=none | od -A d -t x1 | head -3
pg_controldata /tmp/waldemo | grep -i checksumপ্রতিটা 8 KB page disk-এ ১৬টা 512-byte sector-এ ছড়িয়ে — sector-
লেখার মাঝে power গেলে page torn: অর্ধেক পুরনো, অর্ধেক নতুন।
Data page checksum version: 1 — এই checksum-ই বিকৃত page ধরে;
চিকিৎসা একটাই: log থেকে page আবার গড়া। এই লেসনের যন্ত্রটা
এই মুহূর্তের জন্যই।
pg_ctl -D /tmp/waldemo stop && rm -rf /tmp/waldemoWAL কোনো abstract ধারণা নয় — আসল byte-sequence, যাতে প্রতিটা INSERT/COMMIT-এর নাম-ধারী record আছে; LSN সত্যিই বাড়ন্ত অবস্থান, আর torn-page ঝুঁকিটা checksum দিয়ে শনাক্তযোগ্য।
নিজে বানান
ARIES-lite — log, steal, crash, recover, আবার crash
- Append-only log — list append-ই stable-storage অভিনয়
- Buffer pool + steal eviction; flush-এর আগে WAL rule
- crash()-এ pool-এর volatile state ফেলে দেওয়া
- recover(): analysis → redo (page_LSN শর্তে) → undo (backward + CLR)
- Random crash-point যাচাই: final == committed-prefix, recover() দুবার idempotent
লক্ষ্য: example section-এর হাতে-trace-টা স্বয়ংক্রিয় — crash যেকোনো মুহূর্তে হলেও ফল এক থাকে তা প্রমাণসহ।
import random
from dataclasses import dataclass
NIL = -1
@dataclass
class Rec:
lsn: int
kind: str # 'begin'|'update'|'commit'|'clr'
txid: int
page: str = ''
old: object = None
new: object = None
undo_next: int = NIL # শুধু CLR ব্যবহার করে
class Node:
"""WAL + buffer pool + disk — steal/no-force, ARIES-lite recovery"""
def __init__(self):
self.log = [] # stable storage — crash-proof
self.pool = {} # volatile buffer pool
self.pool_lsn = {} # volatile
self.disk = {} # survives
self.disk_lsn = {} # page header-এর page_LSN
self.holds = {} # strict-2PL অভিনয়: page -> txid
def append(self, **kw) -> int:
rec = Rec(len(self.log), **kw) # list append == instant durable
self.log.append(rec)
return rec.lsn
def current(self, page):
return self.pool.get(page, self.disk.get(page))
def begin(self, tx):
self.append(kind='begin', txid=tx)
def write(self, tx, page, new):
if self.holds.get(page, tx) != tx:
raise RuntimeError("strict-2PL: page অন্য active txn-এর হাতে")
self.holds[page] = tx
old = self.current(page)
lsn = self.append(kind='update', txid=tx, page=page, old=old, new=new)
self.pool[page] = new # WAL-first: log-এর *পরে* dirty
self.pool_lsn[page] = lsn
def commit(self, tx):
self.append(kind='commit', txid=tx) # এই LSN durable == commit point
for p in [p for p, o in self.holds.items() if o == tx]:
del self.holds[p]
def steal_somewhere(self):
"""buffer manager-এর স্বাধীনতা: যেকোনো dirty page disk-এ"""
if not self.pool:
return
page = random.choice(list(self.pool))
self.disk[page] = self.pool.pop(page) # WAL rule: log already durable
self.disk_lsn[page] = self.pool_lsn.pop(page)
def crash(self):
self.pool.clear() # সব volatile state শেষ
self.pool_lsn.clear()
def recover(self):
# PASS 1: analysis — losers বের করা
losers = set()
for r in self.log:
if r.kind == 'begin':
losers.add(r.txid)
elif r.kind == 'commit':
losers.discard(r.txid)
# PASS 2: redo — history repeat (uncommitted-সহ), idempotent শর্তে
for r in self.log:
if r.kind not in ('update', 'clr'):
continue
if self.disk_lsn.get(r.page, NIL) < r.lsn:
self.disk[r.page] = r.new
self.disk_lsn[r.page] = r.lsn
# PASS 3: undo — backward, losers-এর জন্য CLR emission
pending = {}
for i, r in enumerate(self.log): # প্রতিটা loser-এর শেষ update
if r.kind == 'update' and r.txid in losers:
pending[r.txid] = i
for i in range(len(self.log) - 1, -1, -1):
r = self.log[i]
if r.kind == 'update' and pending.get(r.txid) == i:
self.disk[r.page] = r.old
self.disk_lsn[r.page] = self.append(
kind='clr', txid=r.txid, page=r.page,
new=r.old, undo_next=pending[r.txid])
prev = NIL
for j in range(i - 1, -1, -1):
if self.log[j].kind == 'update' and \
self.log[j].txid == r.txid:
prev = j
break
pending[r.txid] = prev
def committed_prefix(work):
"""স্বাধীন যাচাই: committed-prefix অনুযায়ী প্রত্যাশিত অবস্থা"""
expect, staged, holds = {}, {}, {}
for op in work:
if op[0] == 'w':
_, tx, page, val = op
if holds.get(page, tx) == tx:
holds[page] = tx
staged.setdefault(tx, {})[page] = val
elif op[0] == 'c':
expect.update(staged.get(op[1], {}))
holds = {p: o for p, o in holds.items() if o != op[1]}
return expect
def trial(seed):
rng = random.Random(seed)
node, work, active, tx = Node(), [], [], 0
for step in range(rng.randint(15, 45)):
act = rng.random()
if act < 0.25 or not active:
tx += 1
node.begin(tx)
active.append(tx)
work.append(('b', tx))
elif act < 0.8:
t = rng.choice(active)
page, val = f"P{rng.randint(1, 4)}", rng.randint(0, 99)
try:
node.write(t, page, val)
work.append(('w', t, page, val))
except RuntimeError:
pass # lock-conflict — বাদ
else:
t = rng.choice(active)
active.remove(t)
node.commit(t)
work.append(('c', t)) if rng.random() < 0.3:
node.steal_somewhere()
node.crash()
node.recover()
once = dict(node.disk)
node.recover() # idempotence: দ্বিতীয়বার
assert dict(node.disk) == once, "recovery idempotent নয়!"
# শুধু loser-রা ছোঁয়া pages None-এ ফেরে — prefix-এ ওরা নেই
final = {p: v for p, v in once.items() if v is not None}
assert final == committed_prefix(work), f"{final} != প্রত্যাশিত"
return True
for s in range(300):
trial(s)
print("300 crash-point সবই committed-prefix-এ; recovery idempotent।")নিজে বাড়ান:
- Checkpoint — প্রতি N record-এ dirty-list + active-set snapshot; redo সেখান থেকে
steal_somewhereadversarial — recent-est page flush; ফল একই দেখান
বাস্তব সিস্টেমে
বাস্তব জগতে WAL
PostgreSQL। pg_waldump-এর record-গুলোই এর log; wal_level,
full_page_writes, synchronous_commit — এই লেসনের knob। PG-ও
history repeat করে; aborted txn-দের টুপল MVCC-visibility-তে
নিষ্ক্রিয় থাকে (vacuum সাফ করে) — ARIES flavor, মূলনীতি এক।
Oracle আর MySQL InnoDB। Redo log আর redo+undo segment — ARIES-পরিবারের সরাসরি উত্তরাধিকার; InnoDB rollback segment-এ CLR-সমতুল্য undo record-ও আছে।
SQLite আর Kafka। SQLite-এর rollback journal (undo-ঘেঁষা) বনাম WAL mode (redo-ঘেঁষা) — journal_mode switch-ই strategy বাছাই। Kafka-র পুরো broker এক commit log; ext4-journal, APFS copy-on-write — crash-consistency-র একই ordering-contract-চিন্তা সর্বত্র।
যে ভুলগুলো সবাই করে
“Commit হলে ডেটা disk-এ চলে যায়।”
না — commit মানে শুধু commit log record durable; data page তখনো memory-তে থাকতে পারে (no-force)। Durability আসে WAL rule থেকে: পরিবর্তনের পূর্ণ বিবরণ log-এ আছে, page পৌঁছানো-না-পৌঁছানো সমস্যাই নয়। “saved” indicator-এর পেছনের সত্যটা এটাই।
“Redo pass শুধু committed transaction-গুলো replay করে।”
ARIES-এর সবচেয়ে বিখ্যাত বিভ্রান্তি! Redo সব update replay করে — uncommitted-ও — steal-policy-তে কোনটা disk-এ ছিল জানার অন্য উপায় নেই। বাছাইটা undo pass-এ, পরে।
“Crash-এর আগে কোনো uncommitted লেখা disk-এ যায়নি, তাই undo দরকারই নেই।”
Steal-policy-তে uncommitted লেখা নিয়মিত-ই disk-এ যায় — pool-এ জায়গা দরকার হলেই। ধারণাটা মানলে uncommitted page memory-তে আটকে থাকবে, আর একটাই লম্বা transaction পুরো pool দখল করে সিস্টেম আটকে দিতে পারে।
“WAL বন্ধ করলে (`synchronous_commit=off` বা fsync off) বিনামূল্যে গতি পাওয়া যায়।”
গতি পাবেন — দাম crash-window: synchronous_commit=off-এ crash-এ
সদ্য-”confirmed” transaction হারাতে পারে (durability delay,
corruption নয়); fsync=off পুরো contract ভাঙে — corruption পর্যন্ত।
প্রথমটা মাঝে মাঝে গ্রহণযোগ্য, দ্বিতীয়টা কখনোই না।
বুঝেছেন কি না দেখুন
1Page LSN কেন page-এর header-এ রাখা হয়, log-এ আলাদা কোনো table-এ
নয়?
যুক্তি
প্রশ্নটা ওঠে specific page-এর সাথে: “এই page-এর disk-copy কোন
পরিবর্তন পর্যন্ত ধারণ করে?” উত্তরটা তাই page-এর সাথেই travel করে
— header-এ। আলাদা table crash-এ volatile হতো বা sync-সমস্যা হতো।
Header-এ থাকলে redo এক তুলনায় (page_LSN \< rec.LSN) decide করে
— আর সেই তুলনাই idempotence।
2Undo pass backward scan করে কেন — forward scan করলে ঠিক কোন ভুল
হতো? একটা দুই-update transaction দিয়ে দেখান।
যুক্তি
txn লিখেছে P: A→B, তারপর P: B→C। Forward undo: A→B-এর undo
(P=B), তারপর B→C-এর undo (old=B) — শেষে P=B, ফেরতই হয়নি!
Backward: শেষ-লেখা ফেরত (P=B), তারপর আগেরটা (P=A) — LIFO-ই
একমাত্র সঠিক ক্রম।
3Recovery-র undo pass-এর মাঝখানে আবার crash। তৃতীয় startup-এ
সিস্টেম কীভাবে বুঝবে কোন rollback half-done? CLR-এর undo_next_lsn
ছাড়া কী ভুল হতে পারত?
প্রয়োগ
undo_next_lsn
ছাড়া কী ভুল হতে পারত?CLR-রা নিজেই log-এ durable — redo pass replay করে half-done
rollback ফিরিয়ে আনে, undo pass বাকিটা পায়। undo_next_lsn chain-
অবস্থান বলে — না থাকলে already-compensated update আবার compensate
হতো (double-undo), old value-গুলো ভুল অবস্থা লিখে দিত।
4আপনার team-এর analytics database-এ প্রতি সকালে ২০ মিনিট ধরে
recovery লাগে। তিনটা আলাদা দিক থেকে diagnosis আর fix-এর প্রস্তাব
দিন — সবগুলোই এই লেসনের ধারণায় দাঁড়িয়ে।
ডিজাইন
(১) Recovery window বড় — শেষ checkpoint থেকে crash দূরে ছিল:
checkpoint frequency বাড়ান (checkpoint_timeout, max_wal_size)
— পরের লেসন। (২) Redo খরচ — FPW-heavy workload মানে replay-ও
heavy; WAL-volume দেখুন। (৩) Hardware সীমা — recovery
fundamentally log-replay; log device-এর fsync latency আর sequential-
read throughput-ই ceiling। প্রতিটা fix-ই trade-off বহন করে।
এরপর কী
এরপর কী
Recovery-র খরচ নির্ধারণ করে একটাই সংখ্যা: শেষ checkpoint থেকে
crash পর্যন্ত কতটা log। Checkpoint ঘন হলে recovery দ্রুত কিন্তু
normal-operation-এ IO spike — PG এই দো-টানায় ভারসাম্য খোঁজে
(checkpoint_completion_target)। আর পাশে MVCC-জগতের housekeeping —
dead tuple সাফানো VACUUM, xid wraparound-রক্ষা। পরের লেসনে এই
দুই “জমাট বাঁধা” কাজের অন্দরমহল।
আরও পড়ুন
- ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Partial Rollbacks Using Write-Ahead Logging — C. Mohan, D. Haderle, B. Lindsay, H. Pirahesh, P. Schwarz — ACM TODS 17(1), 1992 · মূল ARIES paper — এই লেসনের তিন-pass design-এর প্রামাণ্য উৎস
- Database System Concepts — Recovery System chapter — A. Silberschatz, H. Korth, S. Sudarshan · Log-based recovery, steal/no-force, checkpoint-এর পাঠ্যপুস্তক আলোচনা
- Designing Data-Intensive Applications, Chapter 7 — Transactions: Durability and the Write-Ahead Log — Martin Kleppmann · WAL আর crash recovery-র practitioner-দৃষ্টিভঙ্গি