Transactions ও ACID — all-or-nothing-এর চার প্রতিশ্রুতি
Transactions and ACID
একটা bank transfer দুইটা UPDATE — প্রথমটা হলো, দ্বিতীয়টা constraint ভেঙে মরলে প্রথমটাও ফিরে যায় কীভাবে? BEGIN/COMMIT/ROLLBACK-এর শৃঙ্খলা, ACID-এর চার প্রতিশ্রুতির আসল মেকানিজম, savepoint-এর আংশিক undo, fsync-এর durability story।
আগে এটা বুঝি
আজকের গল্পটা একটা bank transfer দিয়ে শুরু। Alice-এর account থেকে Bob-এর account-এ ১০০০ টাকা — database-এর ভাষায় দুইটা UPDATE:
UPDATE accounts SET balance = balance - 1000 WHERE id = 'alice';
UPDATE accounts SET balance = balance + 1000 WHERE id = 'bob';ভয়ের দৃশ্যটা কল্পনা করুন: প্রথম UPDATE সফল — Alice-এর টাকা কাটা। ঠিক তখনই বিদ্যুৎ চলে গেল; দ্বিতীয়টা কখনো ঘটলই না। ফলাফল: ১০০০ টাকা বাষ্প — কেটেছে, জমেনি, টাকাটা কোথাও নেই।
Database এই দুনিয়ায় জন্মাল ঠিক এই ভয় থেকে। সমাধানের নাম
ACID transaction: দুইটা UPDATE-কে এক unit-
এ মোড়ানো যেন হয় পুরোটা ঘটবে, নয়তো কিছুই না — মাঝামাঝি
অবস্থা অস্তিত্বহীন। “all-or-nothing” বলা সহজ; বাস্তবায়ন চারটা আলাদা
ইঞ্জিনিয়ারিং সমস্যা, চারটা আলাদা যন্ত্র। আজকের প্রশ্ন: প্রত্যেকটা
প্রতিশ্রুতি ঠিক কী, কে রাখে, আর BEGIN লিখলে আসলে কী ঘটে?
মূল ধারণা
Transaction boundary — ইউনিটের সীমানা
BEGIN; -- সীমানা শুরু: এখন থেকে সব কিছু এক ইউনিটে
UPDATE accounts SET balance = balance - 1000 WHERE id = 'alice';
UPDATE accounts SET balance = balance + 1000 WHERE id = 'bob';
COMMIT; -- শেষ: এখন থেকে সবাই দেখবে, আর উল্টাবে না
| Command | মানে | ভেতরে কী ঘটে |
|---|---|---|
BEGIN | transaction শুরু | নতুন transaction-id, নতুন private view; change বাকি দুনিয়ার চোখের আড়ালে |
COMMIT | সব final | commit record durable storage-এ flush — এর পরে crash-ও undo করতে পারে না |
ROLLBACK | সব বাতিল | শুরুর আগের অবস্থায় ফেরা — যেন transaction-টা কখনো ছিলই না |
Autocommit — যে transaction আপনি লেখেন না
psql-এ খালি একটা UPDATE চালালেও আপনি transaction-এর বাইরে নন। Default হলো autocommit mode — driver প্রতিটা standalone statement- কে নীরবে মুড়ে দেয়:
UPDATE t SET x = 1; -- আপনি লিখলেন এটুকুই
-- database আসলে চালাল: BEGIN; UPDATE t SET x = 1; COMMIT;পরিণতি: প্রতিটা statement নিজেই all-or-nothing; আর বহু-statement
ইউনিটে বাঁধতে স্পষ্টভাবে BEGIN লাগবে — ভুলে গেলে মাঝপথের
বিপর্যয়ে আগেরগুলো থেকে যাবে (“forgot BEGIN” শ্রেণি)।
চারটা প্রতিশ্রুতি, চারটা আলাদা যন্ত্র
| প্রতিশ্রুতি | এক লাইনে | কে রাখে |
|---|---|---|
| A — Atomicity | সব না-হলে কিছুই না | undo log / abort status |
| C — Consistency | নিয়ম-ভাঙা state-এ commit নেই | constraint + application logic যৌথভাবে |
| I — Isolation | চলন্ত tx একে অপরের অসম্পূর্ণ কাজ দেখে না | locking বা MVCC — পরের তিন লেসন |
| D — Durability | COMMIT মানেই বিদ্যুৎ-প্রমাণ | WAL + fsync |
A, D হলো failure-এর গল্প; C, I concurrency-র। মেলানো উচিত না।
Atomicity — অর্ধেক-সম্পন্ন অস্তিত্বহীন
Textbook মডেল: প্রতিটা write-এর আগে (page, offset, old_value) undo
journal-এ ঢোকে; crash-এ recovery manager journal পড়ে অসম্পূর্ণ tx-দের
পরিবর্তন ঘুরিয়ে দেয়। Undo নিজে crash করলে? Undo idempotent লেখা
হয় — কতবার replay হোক, ফল এক।
Consistency — C একা database-এর হাতে নেই
সবচেয়ে বেশি ভুল-বোঝা letter। C মানে valid state থেকে valid state — কিন্তু valid কোনটা, সংজ্ঞায়িত করে আপনার application। Database-এর enforcement tools:
| Tool | উদাহরণ | কী আটকায় |
|---|---|---|
CHECK | CHECK (balance >= 0) | ঋণাত্মক ব্যালেন্স |
UNIQUE / PK | PRIMARY KEY (id) | duplicate entity |
FOREIGN KEY | destination account আছে কি না | orphan record |
“Transfer-এর দুই পাশের যোগফল অপরিবর্তিত” — এটা built-in constraint না; ওটা টেকে আপনার কোড সঠিক transaction-এ মোড়ানোর উপর। C হলো চুক্তি: আপনি invariant লিখবেন, database নিশ্চিত করবে কোনো tx সেটা ভেঙে commit করতে পারবে না।
Isolation — এক পর্দার টিজার
দুই tx একসাথে চললে একজন কি অন্যজনের অসম্পূর্ণ কাজ দেখবে? Perfect isolation মানে সব serial — throughput-মৃত্যু। তাই isolation একটা dial: READ COMMITTED, REPEATABLE READ, SERIALIZABLE। Dial-এর প্রতিটা notch আর ফাঁদ — পরের লেসনের পুরো বিষয়।
Durability — COMMIT-এর পরের প্রতিশ্রুতি
COMMIT ফেরত দিলেই প্রতিশ্রুতি: process crash, reboot, power loss — কিছুই এই tx মুছতে পারবে না। এর দাম disk-এ: commit-এর মুহূর্তে change- এর বর্ণনা physical storage-এ flush। আর এখানেই ফাঁদ:
ভেতরে কী ঘটছে
COMMIT-এর মুহূর্তে PostgreSQL-এর ভেতরে
- COMMIT;client command — এখনো কোনো প্রতিশ্রুতি নেই
- commit WAL record"transaction N committed" — ছোট log entry, data page নয়
- WAL buffer → segment writelog OS-এ গেল (page cache-এ)
- fsync(WAL)bytes physical disk-এ — এখানেই D সিল হয়
- clog status = COMMITTEDxid-র ভাগ্য লেখা হলো; এবার সবাই দেখবে
- client-কে COMMIT সফলdata pages হয়তো memory-তেই — দরকার নেই, WAL-ই প্রমাণ
দুইটা বিস্ময়। এক: COMMIT-এ data page disk-এ যায়ই না — যায় ছোট্ট
log record; page পরে background-এ যায়, crash হলে recovery replay করে
পুনর্গঠন করে। এটাই write-ahead log
philosophy — পূর্ণ গল্প WAL-recovery লেসনে। দুই: এই fsync-ই per-commit
latency-র সিংহভাগ — HDD ~১০ ms, SSD ~০.১–১ ms; এজন্যই group commit
(অনেক tx-এর commit এক fsync-এ) আর synchronous_commit=off production-
এর রোজকার trade-off।
Atomicity-তে PostgreSQL পুরনো পথ নেয় না
PG কোনো undo চালায় না। চলন্ত tx তার লেখা সরাসরি heap-এ রাখে, প্রতিটায় নিজের xid ছাপিয়ে; abort হলে শুধু clog-এ status = ABORTED — লেখা থেকে যায়, visibility rules-এ কেউ কখনো দেখবে না (MVCC লেসনে পূর্ণ); মৃত tuple VACUUM পরে সরায়। PG-তে atomicity = restore নয়, অস্বীকৃতি — আর এই নকশাই MVCC-র ভিত্তি হয়ে ফিরবে।
Aborted transaction-এর জেলখানা
Constraint ভাঙলে PG tx-টা aborted state-এ যায়, তারপর প্রতিটা command ব্যর্থ:
ERROR: new row violates check constraint "accounts_balance_check"
=> SELECT 1;
ERROR: current transaction is aborted, commands ignored until endএটা feature, bug না — ভেতরে statement-গুলোর কোনো বৈধ অর্ধ-অবস্থা নেই। একমাত্র বেরোনোর পথ ROLLBACK, অথবা SAVEPOINT:
BEGIN;
SAVEPOINT before_dangerous;
UPDATE accounts SET balance = balance - 999999 WHERE id='alice'; -- ভাঙল!
ROLLBACK TO SAVEPOINT before_dangerous; -- শুধু এই ভুল undo, BEGIN না
COMMIT; -- বাকি কাজ টিকল, ভুল জন্মই নেয়নিSAVEPOINT-এর ভেতরে যা হয়: nested transaction-এর marker — undo
journal-এ bookmark। ROLLBACK TO মানে bookmark-এর পরের entries
পোড়ানো; আগেরগুলো অক্ষত।
উদাহরণ
পূর্ণ trace — transfer যেটা মাঝপথে মরল
Setup — CHECK constraint ঋণাত্মক ব্যালেন্স আটকায় (C-র হাতিয়ার):
CREATE TABLE accounts (
id text PRIMARY KEY,
balance numeric NOT NULL CHECK (balance >= 0)
);
INSERT INTO accounts VALUES ('alice', 800), ('bob', 200);Alice-এর ব্যালেন্স ৮০০; ভুল করে ১০০০ কাটতে যাচ্ছি:
# Statement ফল কেন
───────────────────────────────────────────────────────────────────
1 BEGIN; active সীমানা শুরু
2 UPDATE alice: 800 → -200 "UPDATE 1" এখনো কেউ দেখেনি
3 UPDATE bob: 200 → 1200 "UPDATE 1" এখনো...
4 INSERT audit_log (transfer রেকর্ড) "INSERT 0 1" এখনো...
5 UPDATE alice: -200 → 300 ERROR! CHECK violated
── transaction এখন aborted ──
6 SELECT balance ... ERROR aborted জেলখানা
7 ROLLBACK ROLLBACK সব উল্টে গেল
8 SELECT balance ... 800, 200 ধাপ ২-৪ কোনোটাই নেই!ধাপ ৭: ROLLBACK শুধু ভাঙা statement-টা (৫) বাতিল করেনি — সফল statement ২, ৩, ৪, audit_log-INSERT-সহ সব অস্তিত্বহীন। Atomicity- র আক্ষরিক প্রমাণ: ইউনিটে “এটা তো হয়ে গেছে” বলে কিছু নেই — একটা মরলে সবাই মরে। ধাপ ৬: ভাঙার পরেও tx বেঁচে আছে, জেলখানায় — শুধু ROLLBACK/savepoint-এর দিকে তাকাতে পারে।
Batch processing-এ আংশিক-বিপর্যয় চাইলে savepoint-ই হাতিয়ার (hood + experiment সেকশনে পূর্ণ): blast-radius-কে chunk-এ সীমিত করে — ১০ লাখ row-এর import-এ একটা খারাপ batch পুরো job মারতে পারে না।
ছবিতে
Transaction-এর জীবনরেখা
BEGIN
│
▼
┌─────────┐ statement fails ┌──────────┐
│ ACTIVE │ ───────────────────► │ ABORTED │
└────┬────┘ └────┬─────┘
SAVEPOINT ──────┤ │
(bookmark; ROLLBACK TO = bookmark-পরবর্তী undo) │
COMMIT │ ROLLBACK │
▼ ▼
┌────────────────┐ ┌──────────────┐
│ PARTIALLY │──fsync OK──► │ TERMINATED │ ← একমাত্র
│ COMMITTED │ │ (COMMITTED) │ ফেরত-অযোগ্য
└───────┬────────┘ └──────────────┘
│ fsync fail / crash
▼
┌──────────────┐
│ TERMINATED │ ← recovery এটাকেও
│ (ABORTED) │ অদৃশ্য করে দেবে
└──────────────┘COMMIT টাইপ করা আর commit সম্পন্ন এক নয় — WAL flush শেষ-না- হওয়া পর্যন্ত প্রতিশ্রুতি ঝুলন্ত; এই জানালাটাই Durability-র পরীক্ষা।
নিজে চালিয়ে দেখুন
দুই terminal-এ অদৃশ্যতা, মৃত্যুদণ্ড, আর savepoint — নিজের চোখে
ধাপ ০ — server: docker run --rm -d --name pg-lab -e POSTGRES_PASSWORD=lab -p 5432:5432 postgres:16;
তারপর দুইটা আলাদা terminal-ে দুইটা psql session (A ও B):
Setup (Terminal A):
CREATE TABLE accounts (
id text PRIMARY KEY,
balance numeric NOT NULL CHECK (balance >= 0)
);
INSERT INTO accounts VALUES ('alice', 800), ('bob', 200);অংশ ১ — uncommitted লেখা অদৃশ্য, commit-এ দৃশ্যমান:
Terminal A Terminal B
───────────────────────────────────── ─────────────────────────────────────
BEGIN;
UPDATE accounts SET balance = 700
WHERE id = 'alice';
SELECT balance FROM accounts
WHERE id = 'alice';
-- 800 ! A-র 700 দেখা যাচ্ছে না
COMMIT;
SELECT balance FROM accounts
WHERE id = 'alice';
-- 700 — এখন হঠাৎ দেখা যাচ্ছেB-র দুই SELECT-এর মাঝে B-র কোডে কিছু বদলায়নি — বদলেছে শুধু A-র tx-এর ভাগ্য। Isolation-এর প্রথম স্বাদ; পরের লেসনে এর নাম READ COMMITTED।
অংশ ২ — মাঝপথে মৃত্যু = পুরো ইউনিটের মৃত্যু:
Terminal A Terminal B
───────────────────────────────────── ─────────────────────────────────────
BEGIN;
UPDATE accounts SET balance = 700
WHERE id = 'alice'; -- (A এখনো commit করেনি)
UPDATE accounts SET balance = 1200
WHERE id = 'bob'; -- B-র চোখে সব পুরনোই থাকে
UPDATE accounts SET balance = -5
WHERE id = 'alice';
-- ERROR: check constraint violated
SELECT * FROM accounts;
-- ERROR: current transaction is aborted
ROLLBACK;
SELECT * FROM accounts;
-- alice=800, bob=200 — ধাপ ১,২ উল্টে গেছেদুইটা সফল UPDATE, তবু ROLLBACK-এ দুটোই মুছে গেল — আগের statement বেঁচে থাকে না। B-র চোখে পুরো নাটকটা ঘটেইনি।
অংশ ৩ — SAVEPOINT: ভুলটা মারুন, কাজটা না:
BEGIN;
UPDATE accounts SET balance = 700 WHERE id = 'alice'; -- ভালো change
SAVEPOINT good_so_far;
UPDATE accounts SET balance = 70000 WHERE id = 'alice'; -- ভুল change
ROLLBACK TO SAVEPOINT good_so_far;
SELECT balance FROM accounts WHERE id = 'alice'; -- 700: ভুল গেছে
UPDATE accounts SET balance = 650 WHERE id = 'alice'; -- ঠিক change
COMMIT;
SELECT * FROM accounts; -- alice=650, bob=200ROLLBACK TO-র পরেও আপনি একই tx-এ: COMMIT করলে 700→650 টিকবে,
70000 কখনো জন্মই নেয়নি।
Uncommitted change অন্য session-এ অদৃশ্য, মাঝপথে constraint violation আগের সফল statement-গুলোও উল্টে দেয়, আর SAVEPOINT শুধু bookmark-পরবর্তী অংশ undo করে — A আর C হাতে-কলমে।
নিজে বানান
TransactionManager — undo journal আর failure-injection সহ simulator
- dict store-এর উপর begin/apply/commit/rollback API — apply প্রতিটা write-এর আগে undo entry রাখবে
- rollback = journal উল্টো পড়ে old value restore — হুবহু textbook মডেল
- run_script(ops, fail_at=k): k-তম mutation-এ simulated crash ছুড়ে দিন
- crash-পরবর্তী auto-rollback-এ state হুবহু pre-begin snapshot-এর সমান প্রমাণ করুন
- SAVEPOINT support: bookmark-এ rollback শুধু পরের entries undo করবে
লক্ষ্য: atomicity-র মূল চালান — undo journal + crash injection — নিজের হাতে, fail-at-any-step-এ ফল হয় all, নয় nothing — প্রমাণসহ।
class TxFailed(Exception):
pass
class TransactionManager:
def __init__(self, store):
self.store = dict(store)
self.journal = [] # undo entries: (key, old_value_or_None)
self.marks = {} # savepoint name -> journal length
def begin(self):
self.journal.clear(); self.marks.clear()
def _record(self, key):
self.journal.append((key, self.store.get(key))) # None = absent
def apply(self, key, value):
self._record(key); self.store[key] = value
def delete(self, key):
self._record(key); del self.store[key]
def savepoint(self, name): self.marks[name] = len(self.journal)
def _undo_one(self):
key, old = self.journal.pop()
if old is None: del self.store[key]
else: self.store[key] = old
def rollback_to(self, name):
while len(self.journal) > self.marks[name]:
self._undo_one()
def commit(self):
self.journal.clear() # প্রতিশ্রুতি সিল — undo আর দরকার নেই
def rollback(self):
while self.journal: self._undo_one()
def run_script(self, script, fail_at=None):
"""script: ('set',k,v)|('del',k)|('sp',name)|('rbto',name);
fail_at=k মানে k-তম mutation-এর ঠিক আগে বিদ্যুৎ চলে যাবে"""
self.begin()
n = 0
try:
for op in script:
if fail_at is not None and n == fail_at:
raise TxFailed(f"CRASH before mutation #{n}")
if op[0] == 'set': self.apply(op[1], op[2])
elif op[0] == 'del': self.delete(op[1])
elif op[0] == 'sp': self.savepoint(op[1])
elif op[0] == 'rbto': self.rollback_to(op[1])
n += 1
except TxFailed as e:
print(f" !! {e} -> auto rollback")
self.rollback()
return False
self.commit()
return True
tm = TransactionManager({"a": 800, "b": 200})
script = [
('set', 'a', 700), # 1: alice কাটা
('set', 'b', 1200), # 2: bob জোড়া
('set', 'audit', 'xfer-ok'), # 3: লগ
('set', 'a', -5), # 4: CHECK-ভাঙা ভুল (simulated)
]
before = dict(tm.store)
print("সব সফল:")
tm.run_script(script); print(" ", tm.store)
print("fail_at=2 (মাঝপথে crash):")
tm.store.update(before); tm.run_script(script, fail_at=2)
assert tm.store == before, "state পুরোপুরি ফেরেনি!"
print(" restored exactly:", tm.store)
print("savepoint দিয়ে ভুলটা শুধু পেছনে:")
tm.store.update(before)
tm.run_script([('set','a',700), ('sp','ok'),
('set','a',-5), ('rbto','ok'),
('set','a',650)])
print(" ", tm.store)প্রত্যাশিত output:
সব সফল:
{'a': -5, 'b': 1200, 'audit': 'xfer-ok'}
fail_at=2 (মাঝপথে crash):
!! CRASH before mutation #2 -> auto rollback
restored exactly: {'a': 800, 'b': 200}
savepoint দিয়ে ভুলটা শুধু পেছনে:
{'a': 650, 'b': 200}fail_at-এর প্রতিটা মান (০..৩) চালিয়ে দেখুন — assert কোনোদিন ভাঙবে
না: crash যেখানেই হোক, ফল all নয় nothing — atomicity-র সংজ্ঞা,
৯০-লাইনের প্রমাণ।
নিজে বাড়ান:
- Nested savepoint (একই নাম দুইবার) — stack করে সমাধান
commit()-এর পরে rollback ডাকলে? State machine দিয়ে অবৈধ transition আটকান — diagram-এর প্রোগ্রাম-রূপ- Undo entry নিজেই হারালে? বাস্তব engine undo log-ও WAL-protected রাখে কেন — উত্তর লিখুন
বাস্তব সিস্টেমে
Autocommit-এর ব্যয়। ORM-loop-এ প্রতিটা save() আলাদা transaction —
প্রতিটায় আলাদা fsync; হাজার insert শতগুণ ধীর শুধু মোড়ানো-ভুলে।
সমাধান এক: batch এক BEGIN-এ।
Group commit। অপেক্ষমাণ সব tx-এর commit record একটাই fsync-এ বহন — SSD-তে হাজার→লক্ষ commit/sec-লাফের মূল কৌশল = amortization, জাদু নয়।
Booking থেকে saga পর্যন্ত। Seat hold + payment + confirmation তিন service-এ গেলে এটা distributed transaction — Level 9-এর saga pattern পর্যন্ত পথ এই all-or-nothing unit-এর থেকেই শুরু।
যে ভুলগুলো সবাই করে
“ACID-এর C মানে database নিজেই আমার business rules রক্ষা করবে।”
Database কেবল ঘোষিত নিয়ম জানে — CHECK, UNIQUE, FK। “Transfer-এর দুই পাশ যোগফল সংরক্ষিত” declarative feature না — ওটা টেকে সঠিক transaction-মোড়ানোর উপর। C-র চুক্তি যৌথ: আপনি লিখবেন, database লঙ্ঘন-প্রমাণ করবে; একজনও অনুপস্থিত থাকলে চুক্তি নেই।
“ROLLBACK মানে database প্রতিটা statement উল্টা-পথে চালিয়ে ফেরত নেয়।”
Classic ডিজাইনেও উল্টানো হয় না — রাখা old value restore হয়। PG-তে তো উল্টানো-ই হয় না: aborted tx-এর লেখা heap-এ থেকে যায়, শুধু সব পাঠকের কাছে অদৃশ্য; VACUUM পরে সরায়। ফল এক, পথ আলাদা — how জানলে performance-আচরণও অনুমান করা যায়।
“COMMIT ফিরলেই আমার data disk-এ লেখা হয়ে গেছে।”
Disk-এ লেখা হয়েছে log, data page নয় — page হয়তো buffer-এই আছে।
আর log-ও তভা fsync সফল হলে; write() শুধু page cache ছোঁয়। সঠিক
প্রতিশ্রুতি: “replay-যোগ্য প্রমাণ durable হয়েছে” — “data নিজে disk-
এ” নয়। এই পার্থক্যই WAL-architecture-এর ভিত্তি।
“Savepoint আসলে ছোট transaction — ভেতরেরটা আলাদাভাবে commit করা যায়।”
Savepoint nested transaction-এর marker — বাইরের tx ROLLBACK হলে savepoint-এর পরের সবকিছুও যায়। Savepoint শুধু undo journal-এর bookmark: “এখান থেকে পেছনে গেলে এই লাইন পর্যন্তই ঘুরবে।”
বুঝেছেন কি না দেখুন
1Autocommit mode-এ খালি একটা UPDATE চালালে database-এ আসলে কী ঘটে?
স্মরণ
UPDATE চালালে database-এ আসলে কী ঘটে?Client নীরবে BEGIN; UPDATE ...; COMMIT; মুড়ে দেয়। প্রতিটা standalone
statement নিজেই এক-line tx — পাশের statement-এর সাথে কোনো ইউনিট নেই।
2Tx-এ তিনটা UPDATE সফল, চতুর্থটা CHECK ভাঙল। প্রথম তিনটার পরিণতি কী,
কেন?
যুক্তি
সবগুলো বাতিল — atomicity unit-স্তরে কাজ করে। ভেতরের “সফল” মানে কেবল private view-তে প্রয়োগ; একটা ভাঙলে undo journal (বা PG-র abort-status) পুরো ইউনিট অদৃশ্য করে।
3write() আর fsync()-এর পার্থক্য durability-র জন্য কেন অতিকথ্য?
OS page cache কোথায় ঢোকে?
যুক্তি
write() আর fsync()-এর পার্থক্য durability-র জন্য কেন অতিকথ্য?
OS page cache কোথায় ঢোকে?write() data page cache-এ (RAM) পৌঁছে ফেরত দেয় — power-loss-এ cache
বাষ্প। fsync() bytes physical device-এ বাধ্য করে। Durability টিকে আছে
শুধু fsync-সম্পন্ন WAL-এর উপর — শেষ মাইলটাই পুরো পার্থক্য।
4১০ লাখ row-এর import-এ প্রতি ১০,০০০ row-এ SAVEPOINT। Row 45,001-এ
bad data পেলে কী হারাবে, কী বাঁচবে?
প্রয়োগ
শেষ savepoint (row 40,000)-এর পরের ৫,০০১ row ROLLBACK TO-তে যাবে;
আগের ৪৫,০০০ row আর tx বাঁচবে — blast-radius chunk-size-এ সীমিত।
5Atomicity আর Durability দুইটাই crash-সম্পর্কিত — পার্থক্য এক বাক্যে।
যুক্তি
Atomicity: crash-এ অসম্পূর্ণ tx-এর আংশিক কাজ কোথাও দেখা যাবে না। Durability: crash-এ committed tx-এর কাজ হারাবে না। প্রথমটা ভুল মোছে, দ্বিতীয়টা সত্য রাখে।
6Like-counter app-এ synchronous_commit = off করা হলো। কী লাভ, ঠিক
কী ক্ষতি — আর কোনটা হবে না?
ডিজাইন
synchronous_commit = off করা হলো। কী লাভ, ঠিক
কী ক্ষতি — আর কোনটা হবে না?লাভ: fsync-wait বাদ — throughput শতগুণ। ক্ষতি: crash-এ শেষ কিছু ms- এর committed tx হারাতে পারে — D-র দুর্বল রূপ। যা হবে না: corruption — WAL ordered flush থাকে, শুধু শেষ টুকরো হারায়। Like-এ trade ঠিক, payment-এ না।
এরপর কী
এরপর কী
ইউনিটের সীমানা শিখলাম — কিন্তু ইউনিটগুলো কি একা চলে? Experiment- এর অংশ ১-এ B ততক্ষণ A-র কাজ দেখেনি, commit-এর পরেই দেখল। সেই দেখা-না-দেখার নিয়মটা আরও সূক্ষ্ম, আর ভুল setting-এ নীরব ভুল উত্তর দেয়।
পরের লেসনে — Isolation Levels ও Anomalies:
- Anomaly catalog: dirty read, non-repeatable read, phantom, lost update, write skew — প্রতিটার দুই-session timeline
- SQL-এর চার-ধাপ ladder কোন anomaly কোথায় আটকায়
- Standard বনাম বাস্তবতা: PG dirty read দেখায়ই না, আর তার REPEATABLE READ lock নয় — snapshot!
- MySQL-এর RR-default বনাম PG-র RC-default বিতর্ক
আজকের দুই-SELECT-নাটকটাই পরের লেসনে নাম-সহ ফিরবে।
আরও পড়ুন
- Database System Concepts — Chapter 14: Transactions (7th edition) — Abraham Silberschatz, Henry F. Korth, S. Sudarshan · ACID-এর আনুষ্ঠানিক সংজ্ঞা, transaction state diagram, atomicity-র undo-based implementation — এই লেসনের কাঠামোর ভিত্তি
- Principles of Transaction Processing — Philip A. Bernstein, Eric Newcomer · Transaction-কে business abstraction হিসেবে দেখার ক্লাসিক রেফারেন্স — consistency-র অ্যাপ্লিকেশন-পাশের অংশটা এখান থেকে
- PostgreSQL Documentation — Chapter 13: Concurrency Control — The PostgreSQL Global Development Group · Transaction isolation, explicit locking, PG-র aborted-transaction আচরণের প্রামাণ্য বর্ণনা