Foundationপ্রথম নীতি থেকে
LEVEL 8লেসন ১০/১৯কঠিন৫০ মিনিট

Transactions ও ACID — all-or-nothing-এর চার প্রতিশ্রুতি

Transactions and ACID

একটা bank transfer দুইটা UPDATE — প্রথমটা হলো, দ্বিতীয়টা constraint ভেঙে মরলে প্রথমটাও ফিরে যায় কীভাবে? BEGIN/COMMIT/ROLLBACK-এর শৃঙ্খলা, ACID-এর চার প্রতিশ্রুতির আসল মেকানিজম, savepoint-এর আংশিক undo, fsync-এর durability story।

এই লেসন শেষে আপনি পারবেন

  • Transaction boundary — BEGIN, COMMIT, ROLLBACK — এর semantic ব্যাখ্যা করে বলতে পারবেন কোন statement-গুলো একই all-or-nothing unit-এ পড়বে, আর autocommit mode নীরবে কী করে তাও ব্যাখ্যা করতে পারবেন
  • Atomicity আর Durability-র পার্থক্য আলাদা করতে পারবেন — crash-এ undo কোথা থেকে হয়, fsync কোথায় ঢোকে, আর OS page cache কেন write() ফিরলেই disk-এ পৌঁছেছে ভাবা যায় না তা ব্যাখ্যা করতে পারবেন
  • Consistency শুধু database-এর দায় নয় — application logic আর DB constraint মিলে কীভাবে যৌথভাবে invariant রক্ষা করে, constraint-গুলোকে C-র হাতিয়ার হিসেবে ব্যবহার করতে পারবেন
  • Constraint violation মাঝপথে ঘটলে আগের সফল statement-গুলোও উল্টে যায় — দুই-session psql experiment-এ প্রমাণ করে SAVEPOINT দিয়ে আংশিক rollback-ও করতে পারবেন
  • Concurrent transaction একে অপরকে কতটা দেখবে — isolation ladder-এর map (পূর্ণ ব্যবচ্ছেদ পরের লেসনে) মাথায় গেঁথে নিতে পারবেন

আগে যা বোঝা থাকা দরকার

আগে এটা বুঝি

আজকের গল্পটা একটা 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মানেভেতরে কী ঘটে
BEGINtransaction শুরুনতুন transaction-id, নতুন private view; change বাকি দুনিয়ার চোখের আড়ালে
COMMITসব finalcommit 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 — DurabilityCOMMIT মানেই বিদ্যুৎ-প্রমাণ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উদাহরণকী আটকায়
CHECKCHECK (balance >= 0)ঋণাত্মক ব্যালেন্স
UNIQUE / PKPRIMARY KEY (id)duplicate entity
FOREIGN KEYdestination 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 থেকে disk পর্যন্ত — প্রতিশ্রুতিটা কোথায় সিল হয়
  1. COMMIT;client command — এখনো কোনো প্রতিশ্রুতি নেই
  2. commit WAL record"transaction N committed" — ছোট log entry, data page নয়
  3. WAL buffer → segment writelog OS-এ গেল (page cache-এ)
  4. fsync(WAL)bytes physical disk-এ — এখানেই D সিল হয়
  5. clog status = COMMITTEDxid-র ভাগ্য লেখা হলো; এবার সবাই দেখবে
  6. 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)   │     অদৃশ্য করে দেবে
              └──────────────┘
Transaction state diagram — জন্ম থেকে commit/abort, savepoint-এর পেছনে-ফেরা loop। PARTIALLY COMMITTED-ই একমাত্র জানালা যেখানে crash প্রতিশ্রুতি ঝুলিয়ে রাখে।

COMMIT টাইপ করা আর commit সম্পন্ন এক নয় — WAL flush শেষ-না- হওয়া পর্যন্ত প্রতিশ্রুতি ঝুলন্ত; এই জানালাটাই Durability-র পরীক্ষা।

নিজে চালিয়ে দেখুন

EXPERIMENT

দুই terminal-এ অদৃশ্যতা, মৃত্যুদণ্ড, আর savepoint — নিজের চোখে

PostgreSQL 14+ (psql)· ২০ মিনিট

ধাপ ০ — 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=200

ROLLBACK TO-র পরেও আপনি একই tx-এ: COMMIT করলে 700→650 টিকবে, 70000 কখনো জন্মই নেয়নি।

এটা কী প্রমাণ করে

Uncommitted change অন্য session-এ অদৃশ্য, মাঝপথে constraint violation আগের সফল statement-গুলোও উল্টে দেয়, আর SAVEPOINT শুধু bookmark-পরবর্তী অংশ undo করে — A আর C হাতে-কলমে।

নিজে বানান

BUILD IT

TransactionManager — undo journal আর failure-injection সহ simulator

Python · ●●○○○
  1. dict store-এর উপর begin/apply/commit/rollback API — apply প্রতিটা write-এর আগে undo entry রাখবে
  2. rollback = journal উল্টো পড়ে old value restore — হুবহু textbook মডেল
  3. run_script(ops, fail_at=k): k-তম mutation-এ simulated crash ছুড়ে দিন
  4. crash-পরবর্তী auto-rollback-এ state হুবহু pre-begin snapshot-এর সমান প্রমাণ করুন
  5. 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-র সংজ্ঞা, ৯০-লাইনের প্রমাণ।

নিজে বাড়ান:

  1. Nested savepoint (একই নাম দুইবার) — stack করে সমাধান
  2. commit()-এর পরে rollback ডাকলে? State machine দিয়ে অবৈধ transition আটকান — diagram-এর প্রোগ্রাম-রূপ
  3. 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: “এখান থেকে পেছনে গেলে এই লাইন পর্যন্তই ঘুরবে।”

বুঝেছেন কি না দেখুন

1

Autocommit mode-এ খালি একটা UPDATE চালালে database-এ আসলে কী ঘটে?

স্মরণ

Client নীরবে BEGIN; UPDATE ...; COMMIT; মুড়ে দেয়। প্রতিটা standalone statement নিজেই এক-line tx — পাশের statement-এর সাথে কোনো ইউনিট নেই।

2

Tx-এ তিনটা UPDATE সফল, চতুর্থটা CHECK ভাঙল। প্রথম তিনটার পরিণতি কী, কেন?

যুক্তি

সবগুলো বাতিল — atomicity unit-স্তরে কাজ করে। ভেতরের “সফল” মানে কেবল private view-তে প্রয়োগ; একটা ভাঙলে undo journal (বা PG-র abort-status) পুরো ইউনিট অদৃশ্য করে।

3

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-এ সীমিত।

5

Atomicity আর Durability দুইটাই crash-সম্পর্কিত — পার্থক্য এক বাক্যে।

যুক্তি

Atomicity: crash-এ অসম্পূর্ণ tx-এর আংশিক কাজ কোথাও দেখা যাবে না। Durability: crash-এ committed tx-এর কাজ হারাবে না। প্রথমটা ভুল মোছে, দ্বিতীয়টা সত্য রাখে।

6

Like-counter app-এ 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 আচরণের প্রামাণ্য বর্ণনা