Foundationপ্রথম নীতি থেকে
LEVEL 8লেসন ১২/১৯অ্যাডভান্সড১ ঘণ্টা

Locking ও 2PL — strict two-phase আর deadlock-এর খেলা

Locking and Two-Phase Locking

Lock-এর শৃঙ্খলা: S/X আর intention-matrix থেকে two-phase rule-এর proof-sketch, strict 2PL-এর cascading-abort নিষেধ, wait-for graph-এ বৃত্ত খুঁজে victim বাছাই — pg_locks-এ সব live।

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

  • Compatibility matrix-এর প্রতিটা ঘর — কোন pair conflict, কোনটা সহাবস্থানযোগ্য — যুক্তি দিয়ে পুনর্গঠন করতে পারবেন
  • Two-phase rule — growing আর shrinking phase — কেন conflict-serializability গ্যারান্টি দেয়, ordering-argument দিয়ে proof-sketch বলতে পারবেন
  • Plain 2PL বনাম Strict 2PL-এর ফারাক — cascading abort সমস্যাটা interleaving আঁকিয়ে দেখাতে পারবেন
  • Intention lock-এর IS/IX/SIX hierarchy কীভাবে table-level O(1) check দিয়ে row-level locking সমর্থন করে, granularity trade-off আর escalation বিশ্লেষণ করতে পারবেন
  • Deadlock handling-এর দুই দর্শন — prevention (ordering/timeout) বনাম detection (wait-for cycle + victim selection) — PostgreSQL-এ দুইটা চালিয়ে আচরণ-ফারাক মেপে দেখাতে পারবেন

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

আগে এটা বুঝি

পরশ লেসনে snapshot — optimistic পথ। আজ classical পথ — pessimistic: আগে permission, তারপর কাজ। Database কড়া librarian: বই নিতে টোকেন; দুইজন একই বইয়ে শুধু পড়লে একসাথে; লিখতে চাইলে বাকি সবাই বাইরে।

কিন্তু lock-এর ছন্দটাই আসল গণিত: যখন-তখন lock নিলে serializable schedule হাতছাড়া; আগ্রহে নিলে দুই tx একে অপরের জিনিসে হাত রেখে চিরতরে আটকে যেতে পারে — deadlock, race-condition-লেসনের database-ভাই।

তিনটা প্রশ্ন: (১) কোন lock কার সাথে compatible? (২) কোন ছন্দে নিলে correctness গ্যারান্টি? (৩) ছন্দ মানলেও আটকে গেলে — কে, কখন, কীভাবে উদ্ধার করবে?

মূল ধারণা

Shared আর Exclusive — দুই ধরনের অনুমতি

Lockঅনুমতিউদাহরণ
Shared (S)পড়া — একসাথে অনেকেSELECT ... FOR SHARE
Exclusive (X)পড়া+লেখা — একাFOR UPDATE, UPDATE, DELETE

Compatibility matrix — মুখস্থ না, যুক্তি:

SX
S✔✗
X✗✗

এক বাক্যে: দুই reader একে অপরকে ক্ষতি করে না; writer সবার একমাত্র। এই ২×২ grid-ই concurrency-র সব রাজনীতির জন্মস্থান — reader-দলবদল, writer-নির্বাসন।

Two-Phase Locking — ছন্দটাই theorem

  1. Growing phase: শুধু lock নেওয়া — ছাড়া নিষেধ
  2. Shrinking phase: একবার একটাও ছাড়লে — নতুন lock নেওয়া চিরতরে বন্ধ

Strict 2PL — commit পর্যন্ত ধরে রাখা

Plain 2PL-এ shrinking phase-এ commit-এর আগেই lock ছাড়া যায়। সমস্যা: cascading abort —

t1   T1: w(A); unlock(A)
t2        T2: read A (T1-এর uncommitted data!); w(A)
t3   T1: ভুল ধরা পড়ল → ROLLBACK
t4        T2-ও ROLLBACK-বাধ্য     ← T2 ছিল T1-এর উপর দাঁড়ানো
t5             T3 যদি T2-এর data পড়ত... ← ধারাবাহিক পতন

ঔষধ: Strict 2PL — write-lock commit/abort পর্যন্ত ধরে রাখা; uncommitted write-এর উপর কেউ দাঁড়াতেই পারে না। Production engine প্রায় সবাই strict।

Intention lock — table-level fast check

Table-X চাওয়া tx কি ১০ কোটি row ঘেঁটে দেখবে কোথাও row-lock আছে কি না? অসম্ভব। সমাধান: row-lock-এর আগে ancestor-এ ইঙ্গিত-চিহ্ন:

Modeমানে
ISভেতরে S-lock নেওয়ার ইচ্ছা আছে
IXভেতরে X-lock নেওয়ার ইচ্ছা আছে
SIXপুরোটা পড়ছি (S), ভেতরে লিখবও (IX)

Multi-level matrix (table↔row সব স্তরে এক):

ISIXSSIXX
IS✔✔✔✔✗
IX✔✔✗✗✗
S✔✗✔✗✗
SIX✔✗✗✗✗
X✗✗✗✗✗

Intention mode data-access-এর অনুমতি দেয় না — “ভেতরে কেউ আছে”-র সংকেত; table-X চাইলে IX দেখেই O(1)-এ ব্লক। PG-র pg_locks-এ এই modes-পরিবার (AccessShareLock…) সরাসরি দেখা যায়।

Granularity — কত মোটা তালাবন্ধ

Granularitylock সংখ্যাconcurrencyoverhead
Table১প্রায় শূন্যসবচেয়ে কম
Pagepage-প্রতিমাঝারিমাঝারি
Rowtuple-প্রতিসর্বোচ্চmemory + bookkeeping সর্বোচ্চ

Coarse = সস্তা-কিন্তু-সব-ব্লক; fine = মুক্ত-কিন্তু-ব্যয়বহুল। Bulk operation মোটা grain, OLTP hot-row fine; tx অতিরিক্ত fine lock জমালে engine মাঝে মাঝে জোর-করে মোটা grain-এ escalate করে — memory বাঁচাতে concurrency বলি দিয়ে।

Deadlock — দুই দল সমাধান

OS-লেসনের four-condition গণিত, নতুন পোশাকে।

Prevention — বৃত্ত-গঠনই অসম্ভব করা:

  • Resource ordering: সব tx একই ক্রমে lock নেবে (account-id ascending?) — circular-wait condition-ই ভাঙে; application-discipline, engine বলিয়ে দেয় না
  • Timeout: সময়সীমায় নিজেই হাল ছাড়ো — সরল, false positive-এ সুস্থ tx-ও মরে

Detection — বৃত্ত হোক, ধরব:

  • Engine wait-for graph রাখে: node = tx, edge = “T1 T2-এর lock- এর জন্য অপেক্ষায়”
  • মাঝে মাঝে graph-এ cycle খোঁজা — cycle = deadlock
  • Cycle-এর একজন victim abort — বাছাই সাধারণত সস্তা-victim heuristic: যার undo-cost কম

PG detection পছন্দ করে; deadlock_timeout default ১ সেকেন্ড — detector-এর খরচ real (waiter-হয়ে lock-table scan), আর বেশিরভাগ block ১s-আগেই মুক্ত; তাড়াহুড়োয় healthy waiting-ও মরত।

ভেতরে কী ঘটছে

pg_locks — lock-table-এর জানালা

SELECT pid, locktype, relation::regclass AS rel, mode,
       granted, waitstart
FROM pg_locks WHERE relation IS NOT NULL
ORDER BY granted, waitstart NULLS FIRST;
  pid  | locktype |  rel    |       mode       | granted
-------+----------+---------+------------------+---------
  4101 | relation | accounts| RowExclusiveLock | t
  4102 | relation | accounts| RowExclusiveLock | f      ← অপেক্ষমাণ!

granted = false লাইনগুলোই live contention; pg_stat_activity join-এ কে কোন query-তে আটকে সব দেখা যায় — production debugging-প্রথম query।

Deadlock detector-এর এক মিনিট

deadlock_timeout (1s default) পার হলে প্রতিটা waiter নিজের হয়ে detector জাগায়: wait-edge থেকে DFS — নিজের কাছে ফিরে এলে cycle, victim-প্রার্থী। Victim-বাছাই PG-তে মোটামুটি সস্তা-victim: যার undo কম। Error message-এ conflicting process-দের pid-সহ বর্ণনা থাকে — experiment-এ দেখবেন।

lock_timeout বনাম deadlock_timeout

lock_timeoutdeadlock_timeout
কার settingআপনার sessionserver global
কী করেএত ms lock না পেলে হাল ছাড়োএত ms পরে detector জাগবে
দর্শনpreventiondetection-সময়সূচি
default0 (অসীম!)1000 ms

প্রথমটা migration-script-এ প্রায় বাধ্যতামূলক — নইলে long transaction- পেছনে DDL দাঁড়িয়ে app-freeze; দ্বিতীয়টা ছোঁয়া প্রায় কখনো দরকার হয় না।

উদাহরণ

পূর্ণ trace — AB-BA deadlock জন্মানো আর কাটা

T1 A→B ক্রমে, T2 B→A ক্রমে:

সময়   T1                          T2                  lock-state
────  ───────────────────────    ─────────────────    ─────────────────
t1    BEGIN;                      BEGIN;
t2    UPDATE a SET v=1            UPDATE b SET v=1
      [X(a) ✓]                    [X(b) ✓]
t3    UPDATE b SET v=2            UPDATE a SET v=2
      [X(b)? BLOCKED]             [X(a)? BLOCKED]
                                  (T2 holds b)         (T1 holds a)
t4    wait-for: T1→T2             wait-for: T2→T1      ← CYCLE!
t5                                                    detector জাগে (≥1s)
t6    ERROR: deadlock detected!                        ← T1 victim
t7                                [X(a) ✓]              ← T2 unblocked
t8                                COMMIT                 ← সম্পূর্ণ

শিক্ষা: (১) কোনো statement “ভুল” ছিল না — দুই বৈধ program-এর interleaving-ই বৃত্ত বানাল; (২) victim engine-এর বাছাই; (৩) T2-এর চোখে কিছুই ঘটেনি — শুধু দেরি। Deadlock victim-চোখে error, অন্যের চোখে delay। জবাব: SQLSTATE 40P01-এ whole-tx retry; root-fix ordering — দুইজনেই a-আগে-b নিলে বৃত্ত-গঠনই অসম্ভব।

ছবিতে

Compatibility আর wait-for — দুইটা map

COMPATIBILITY:
            IS    IX    S    SIX    X        নিয়ম: intention-রা পরস্পর-
    IS      ✔     ✔    ✔     ✔     ✗        বন্ধু; S-এর শত্রু IX/SIX/X;
    IX      ✔     ✔    ✗     ✗     ✗        X-এর শত্রু সবাই
    S       ✔     ✗    ✔     ✗     ✗
    SIX     ✔     ✗    ✗     ✗     ✗
    X       ✗     ✗    ✗     ✗     ✗

WAIT-FOR GRAPH (AB-BA):
        ┌── T1 wants X(a) held by T2 ◄──┐
        │                               │
        └── T2 wants X(b) held by T1 ───┘    ← বৃত্ত = deadlock
   victim=T1 abort → T1-এর lock release → T2 grant → এগোয়
উপরে multi-level compatibility matrix — intention-চিহ্ন ভেতরের কাজের সংকেত। নিচে wait-for graph — তীর-বৃত্তই deadlock; victim abort-এ edge কাটে, পথ খোলে।

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

EXPERIMENT

pg_locks-এ অপেক্ষা দেখা, AB-BA deadlock জন্ম দেওয়া, দুই timeout-এর ফারাক

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

Setup: docker run --rm -d --name pg-lab -e POSTGRES_PASSWORD=lab -p 5432:5432 postgres:16; দুই terminal-এ docker exec -it pg-lab psql -U postgres; Terminal A:

CREATE TABLE accounts (id text PRIMARY KEY, bal numeric);
INSERT INTO accounts VALUES ('a', 1000), ('b', 1000);

অংশ ১ — contention live দেখা:

Terminal A                                 Terminal B
─────────────────────────────────────     ──────────────────────────
BEGIN;
UPDATE accounts SET bal=999 WHERE id='a';
                                          BEGIN;
                                          UPDATE accounts SET bal=555
                                          WHERE id='a';   -- ঝুলে গেল!

তৃতীয় psql session:

SELECT pid, mode, granted FROM pg_locks
WHERE relation = 'accounts'::regclass;
--  pid  |       mode       | granted
-- ------+------------------+---------
--  4101 | RowExclusiveLock | t
--  4102 | RowExclusiveLock | f     ← B অপেক্ষায়!

A-তে ROLLBACK — B সঙ্গে সঙ্গে এগোবে। অপেক্ষা ছিল নীরব, pg_locks-এ পুরোটা লেখা ছিল।

অংশ ২ — AB-BA deadlock জন্ম দেওয়া:

Terminal A                                 Terminal B
─────────────────────────────────────     ──────────────────────────
BEGIN;                                     BEGIN;
UPDATE ... SET bal=bal-10 WHERE id='a';
                                           UPDATE ... SET bal=bal-10
                                           WHERE id='b';
UPDATE ... SET bal=bal-20 WHERE id='b';
-- (BLOCKED)                               UPDATE ... SET bal=bal-20
                                           WHERE id='a';
                                           -- (BLOCKED) ← বৃত্ত!
-- ~১ সেকেন্ড পরে A-তে:
ERROR:  deadlock detected
DETAIL: Process 4101 waits for ... blocked by 4102.
        Process 4102 waits for ... blocked by 4101.
HINT:   See server log for query details.
-- B এখন এগোয়; দুই জায়গায় COMMIT

Error এক জায়গাতেই — victim engine-এর। Retry 40P01 ধরে।

অংশ ৩ — দুই timeout, দুই আচরণ:

Terminal A hold করুক: BEGIN; UPDATE accounts SET bal=42 WHERE id='a'; (COMMIT এখনো না)। Terminal B — প্রথমে prevention-style:

BEGIN;
SET LOCAL lock_timeout = '2s';
UPDATE accounts SET bal=7 WHERE id='a';
-- ঠিক ~2 সেকেন্ডে:
ERROR:  canceling statement due to lock timeout
ROLLBACK;

এবার detection-style: B আবার ব্লক করুন (timeout-set ছাড়া), A-কেও B- এর ধরা row-র পেছনে পাঠিয়ে বৃত্ত বানান — এবার ~1s-এ detector হাল ছাড়ায়, ভিন্ন error (deadlock detected), ভিন্ন mechanism। নোট করুন: 2s-এ নিজের সিদ্ধান্ত বনাম 1s-এ engine-এর রায় — দুই clock, দুই দর্শন।

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

Contention-এ pg_locks granted/waiting live দেখায়, AB-BA pattern নিয়মিতভাবে deadlock-error আর victim তৈরি করে, আর lock_timeout (prevention) আর deadlock_timeout (detection) ভিন্ন সময়ে ভিন্নভাবে হাল ছাড়ায়।

নিজে বানান

BUILD IT

LockManager — intention hierarchy, wait-for graph, victim selection সহ

Python · ●●●●○
  1. Resource tree model করুন: db/table/row — প্রতিটা level-এ lock-table
  2. S/X-এর সাথে IS/IX/SIX mode; acquire-এ ancestors-এ intention auto-grant
  3. Compatibility matrix দিয়ে grant/block; blocked হলে wait-for edge
  4. DFS cycle-detection; victim cost-heuristic-এ (সবচেয়ে কম lock-ধারী)
  5. Interleaved script replay করে grant/block/abort timeline ছাপুন

লক্ষ্য: engine lock-manager-এর মেরুদণ্ড — multi-granularity compatibility + deadlock detection — নিজের হাতে।

from collections import defaultdict

COMPAT = {   # সব স্তরে একই — intention সংকেত, X একা
    'IS': {'IS':True,'IX':True,'S':True,'SIX':True,'X':False},
    'IX': {'IS':True,'IX':True,'S':False,'SIX':False,'X':False},
    'S':  {'IS':True,'IX':False,'S':True,'SIX':False,'X':False},
    'SIX':{'IS':True,'IX':False,'S':False,'SIX':False,'X':False},
    'X':  {'IS':False,'IX':False,'S':False,'SIX':False,'X':False},
}
ANCESTOR = {'S': ['IS'], 'X': ['IX']}   # row-request-এর ইঙ্গিত-chain

class LockManager:
    def __init__(self, log=print):
        self.held = defaultdict(list)   # res -> [(tx, mode)]
        self.waits = {}                 # tx -> (res, mode)
        self.log = log

    def _ok(self, res, tx, mode):
        return all(COMPAT[mode][m] for t, m in self.held[res] if t != tx)

    def _anc(self, res):
        parts = res.split('/')          # যেমন db/accounts/a
        return ['/'.join(parts[:i]) for i in range(1, len(parts))]

    def acquire(self, tx, res, mode):
        hints = dict(zip(self._anc(res),
                         ANCESTOR[mode]*len(self._anc(res))))
        for anc, hint in hints.items():
            if not self._ok(anc, tx, hint):
                self.log(f"  T{tx} blocked on {anc}[{hint}]")
                return False
            if (tx, hint) not in self.held[anc]:
                self.held[anc].append((tx, hint))
                self.log(f"  T{tx} granted {anc}[{hint}] (intention)")
        if self._ok(res, tx, mode):
            self.held[res].append((tx, mode))
            self.log(f"  T{tx} granted {res}[{mode}]")
            return True
        self.waits[tx] = (res, mode)
        self.log(f"  T{tx} WAITS for {res}[{mode}]")
        return False

    def release_all(self, tx):
        n = 0
        for res in list(self.held):
            n += len([1 for t,_ in self.held[res] if t == tx])
            self.held[res] = [(t,m) for t,m in self.held[res] if t != tx]
        self.waits.pop(tx, None)
        self.log(f"  T{tx} released all ({n} locks)")

    def find_cycle(self):
        edge = defaultdict(set)
        for ta, (res, _) in self.waits.items():
            for tb, _m in self.held[res]:
                if tb != ta: edge[ta].add(tb)
        seen = set()
        def dfs(u, path):
            if u in path: return path[path.index(u):]+[u]
            if u in seen: return None
            seen.add(u)
            for v in sorted(edge[u]):
                c = dfs(v, path+[u])
                if c: return c
        for n_ in sorted(set(edge) | set(self.waits)):
            c = dfs(n_, [])
            if c: return c
        return None

    def resolve_deadlocks(self):
        victims = []
        while (cyc := self.find_cycle()):
            members = cyc[:-1]
            # cost-heuristic: সবচেয়ে কম lock-ধারী = সস্তা undo
            victim = min(members, key=lambda t: sum(
                len([1 for tt,_ in self.held[r] if tt==t])
                for r in self.held))
            self.log(f"  DEADLOCK cycle "
                     f"{'->'.join('T%d'%t for t in cyc)} — victim T{victim}")
            self.release_all(victim)
            victims.append(victim)
        return victims


print("AB-BA interleaving:")
lm = LockManager(log=lambda s: print(s))
lm.acquire(1, 'db/accounts/a', 'X')
lm.acquire(2, 'db/accounts/b', 'X')
lm.acquire(1, 'db/accounts/b', 'X')   # blocks (T2 holds b)
lm.acquire(2, 'db/accounts/a', 'X')   # blocks (T1 holds a) — বৃত্ত!
lm.resolve_deadlocks()
lm.acquire(2, 'db/accounts/a', 'X')   # victim গেল — grant
lm.release_all(2)

print("Reader-ভিড় আর table-X:")
lm2 = LockManager(log=lambda s: print(s))
for r in (1, 2, 3): lm2.acquire(r, 'db/reports/r1', 'S')
lm2.acquire(9, 'db/reports', 'X')     # table-X: সবার সাথে conflict
lm2.resolve_deadlocks()               # cycle নেই — শুধু অপেক্ষা
lm2.release_all(1)

প্রত্যাশিত output (সংক্ষেপে):

AB-BA interleaving:
  T1 granted db[a?]... intentions...
  T2 WAITS for db/accounts/a[X]
  DEADLOCK cycle T1->T2->T1 — victim T1
  T1 released all (N locks)
  T2 granted db/accounts/a[X]

Reader-ভিড় আর table-X:
  T9 WAITS for db/reports[X]     ← cycle নেই, detector নীরব

দুই শিক্ষা output-এই: (১) victim সবসময় “সবচেয়ে সস্তা”; (২) অপেক্ষা deadlock না — detector শুধু বৃত্তে হস্তক্ষেপ করে।

নিজে বাড়ান: (১) per-resource FIFO queue + jump-নিষেধ (fairness); (২) victim-heuristic বদল — youngest-tx, কোনটা কখন ভালো? (৩) ৮+ row-lock ধরলে page-level escalation — concurrency-র কী হারাল দেখুন।

বাস্তব সিস্টেমে

Migration-এর ভয়: ACCESS EXCLUSIVE। ALTER TABLE সর্বোচ্চ lock নেয়, আর তার পেছনে দাঁড়ায় আগের সব long query — ৩০s migration app-freeze। ঔষধ: শুরুতেই SET lock_timeout='2s' + retry — hood-জ্ঞানের production-রূপ।

Advisory locks। pg_advisory_lock(42) — engine-grade locking আপনার মনগড়া resource-এ (scheduler leader-election, cache-warmup)। Distributed cron-এ দুই instance এক job না নেওয়ার সস্তাতম সমাধানগুলোর একটা।

InnoDB gap lock। MySQL RR range-query পাশের ফাঁকাও lock করে (next-key locking) — phantom-প্রতিকার, দাম insert-heavy contention। PG snapshot-পথে জটিলতাটাই নেই — দুই design-দর্শনের প্রতিদিন-অনুভূত ফারাক।

Deadlock-বিহীন জীবনের রেসিপি। Consistent access-order, ছোট tx, index-যুক্ত query, 40P01-এ retry — চারটাতেই deadlock production-এ বিরল।

যে ভুলগুলো সবাই করে

“PostgreSQL-এ SELECT সবসময় row-lock নেয়।”

Plain SELECT MVCC snapshot থেকে পড়ে — কোনো row-lock ছাড়াই; readers-never-block-writers promise এখানেই (MVCC লেসনে পূর্ণ)। Row-lock শুধু explicit FOR UPDATE/FOR SHARE বা actual UPDATE/DELETE-এ। Textbook- এর readers-take-S-lock model PG-র বাস্তবতায় খাটে না।

“Deadlock মানে bug — সঠিক কোডে deadlock হয় না।”

দুই বৈধ, স্বাধীনে-সঠিক program-এর interleaving-ই example-এ বৃত্ত বানাল। তাই detector built-in, retry normal flow-র অংশ। Ordering-discipline deadlock বিরল করে — অসম্ভব নয়।

“Intention lock তৃতীয় একটা lock-kind — S/X-এর মতো নতুন semantic।”

Intention mode নিজে data-access অনুমতি দেয়ই না — hierarchy-র সংকেত মাত্র (“নিচে finer-grain lock আসছে”), যাতে table-conflict check O(1) হয়। Multi-level tree-indexing-trick, lock-এর version — নতুন semantics নয়।

“deadlock_timeout কমালেই deadlock দ্রুত সমাধান — 10ms করে দিই।”

Detector জাগানোর খরচ real — প্রতিটা timeout-পার waiter lock-table scan করে; healthy waiting-ও 10ms পার হয়। 1s default measurement-ভিত্তিক compromise; application-side দ্রুত হালছাড়া চাইলে সেটা lock_timeout-এর কাজ — ভিন্ন যন্ত্র।

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

1

S/X matrix-এ একমাত্র compatible pair কোনটা, যুক্তি?

স্মরণ

S–S। পড়া idempotent — দুই reader একে অপরের দেখা ডেটা বদলায় না; লেখা কারো সাথে সহাবস্থান করতে পারে না (stale-read/lost-write)। Readers ভিড় করে, writer একা।

2

Growing/shrinking বিভাজন কেন conflict-serializability দেয় — ordering- argument তিন বাক্যে।

যুক্তি

Conflict-item-এ acquisition definite ক্রম বসায় (আগে-পাওয়া আগে)। Shrinking- phase-এ ফেরত-দরজা বন্ধ — কোনো tx ক্রম উল্টে দিতে পারে না। সব pair-এর ক্রম acyclic precedence-graph দেয় — topological serial order মানেই conflict-equivalent schedule।

3

T1 লিখে lock ছেড়ে দিল, T2 সেই data পড়ে এগোল, তারপর T1 rollback। কী ঘটবে — সমস্যার নাম, ঔষধ?

প্রয়োগ

T2-কেও abort করতে হবে (uncommitted data-র উপর দাঁড়ানো); ধারাবাহিকতায় T3-ও — cascading abort। ঔষধ Strict 2PL: write-lock commit/abort পর্যন্ত ধরা — cascade-ই অসম্ভব।

4

Table-X-চাওয়ার সময় intention lock O-notation-এ কোন খরচ বাঁচায়?

যুক্তি

O(n) row-ঘেঁটে-দেখা → O(1) table-level compatibility-check। Intention-entry “ভেতরে activity আছে”-র summary-bit — constant-time conflict-detection।

5

AB-BA-তে victim T1। T2-এর client কি error দেখবে? Retry কার দায়িত্ব?

প্রয়োগ

T2 কিছুই দেখে না — সামান্য দেরির পরে স্বাভাবিক এগোনো। Retry victim- এর দায়: T1-এর client SQLSTATE 40P01 পেয়ে whole-transaction আবার চালাবে — partial-resume কখনো নয়।

6

Batch-job ১ কোটি row update করে — lock-memory বিস্ফোরক। Granularity- দৃষ্টিতে দুই কৌশল, দাম-সহ।

ডিজাইন

(১) Chunking + per-chunk commit: peak lock-count সীমিত; দাম — chunk-মাঝে partial progress RC-reader-এ দৃশ্যমান। (২) LOCK TABLE EXCLUSIVE: একটাই lock, memory নগণ্য; দাম — পুরো টেবিল বন্ধ। Batch-window থাকলে (২), online- traffic-এ (১)।

এরপর কী

এরপর কী

Classical পথ শিখলাম: lock, ছন্দ, বৃত্তে victim। কিন্তু অস্বস্তি রয়ে গেল — experiment-এ plain SELECT কেন block করলই না? কারণ PG read-path- এ এই lock-jagat ব্যবহারই করে না; সে চালায় আরেকটা ইঞ্জিন।

পরের লেসনে — MVCC: PostgreSQL-এর বাস্তব implementation:

  • xmin/xmax tuple-header আর visibility function-এর পূর্ণ গণিত
  • INSERT/DELETE/UPDATE storage-এ কী রেখে যায় — dead tuple-এর জন্ম
  • Snapshot timing, clog lookup, HOT update-এর index-bypass
  • Long-running tx-এর বিষ — bloat আর xid wraparound; VACUUM-এর অপরিহার্যতা; InnoDB-র undo-log পথ

আজ lock-শৃঙ্খলা; পরের লেসনে version-রাজনীতি — বেছে-নেওয়ার দাম।

আরও পড়ুন

  • Database System Concepts — Chapter 15: Concurrency Control (15.1–15.2) — Abraham Silberschatz, Henry F. Korth, S. Sudarshan · 2PL-এর আনুষ্ঠানিক সংজ্ঞা, serializability-proof, graph-based protocol — theory-র ভিত্তি
  • The notions of consistency and predicate locks in a database system (CACM 1976) — K. P. Eswaran, J. N. Gray, R. A. Lorie, I. L. Traiger · 2PL ও serializability-সম্পর্কের ঐতিহাসিক মূল paper — theorem-টা এখানেই প্রথম
  • PostgreSQL Documentation — 13.3: Explicit Locking / 13.4: Application-Level Data Checking — The PostgreSQL Global Development Group · pg_locks, FOR UPDATE, deadlock_timeout — experiment section-এর সব tool-এর প্রামাণ্য বর্ণনা