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

Replication — streaming আর logical-এর দুই পথ

Replication: Streaming and Logical

WAL stream অন্য machine-এ গেলেই replica — কিন্তু কাঁচা byte না decoded row? Streaming আর logical পথের দুই জগৎ, lag-এর গণিত, আর failover-এর রক্তক্ষয়ী সত্যগুলো।

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

  • Replication-এর তিনটা উদ্দেশ্য — high availability, read scaling, geo-latency — আলাদা করে ব্যাখ্যা করে বলতে পারবেন কোন workload-এ কোনটা মূল চালিকা
  • Physical streaming replication-এর যন্ত্রটা বোঝাতে পারবেন — WAL byte ship, hot standby-তে read-only query, আর `pg_stat_replication`-এর sent/write/flush/replay LSN পরিবার থেকে lag পড়া
  • `synchronous_commit`-এর levels — local, remote_write, remote_apply — একেকটার durability-vs-latency trade-off ব্যাখ্যা করে বলতে পারবেন কোন level-এ failover-এ কী হারাতে পারে
  • Logical replication-এর ভিন্নতা চিনতে পারবেন — publication/subscription, replication slot, selective table, cross-version upgrade — আর conflict manual-resolution-এর কারণ
  • Slot stall করলে disk কীভাবে ভরে যায়, RPO/RTO দিয়ে failover হিসাব করা যায়, আর split-brain ঠেকাতে fencing কেন অপরিহার্য — scenario ধরে বলতে পারবেন

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

আগে এটা বুঝি

এখন পর্যন্ত আমাদের database একটা মাত্র machine — crash হলে recovery ফিরিয়ে আনে, কিন্তু machine-টাই না বেঁচে থাকলে? Disk পুড়ে গেলে? আর ঢাকার user যদি Virginia-র datacenter-এ ৩০০ms পিং-এ টানে?

সমাধানের নাম replication: একই data ক’টা machine-এ রাখা। আর আমাদের ভাগ্য ভালো — আগের দুই লেসনে আমরা ইতিমধ্যে সেই যন্ত্রটা তৈরি করে ফেলেছি যেটা replication চায়। WAL কী? Data-র প্রতিটা পরিবর্তনের পূর্ণ বিবরণ, কঠোর ক্রমে। সেই log টাই যদি অন্য machine-এ পাঠিয়ে সেখানেও replay করা যায়? দ্বিতীয় machine হুবহু একই অবস্থায় পৌঁছাবে।

কিন্তু দুইটা আলাদা পথ আছে: streaming — কাঁচা WAL byte শিপিং, হুবহু একই cluster-copy গড়া; আর logical — log থেকে row-level event decode করে ভিন্ন structure-এও প্রয়োগ করা। আজকে দুই পথই হাঁটব, lag-এর গণিত মাপব, আর failover-এর জঘন্য বাস্তবতা — split-brain — দেখব।

মূল ধারণা

কেন replicate — তিনটা আলাদা উদ্দেশ্য

উদ্দেশ্যযন্ত্রখরচ
High availabilityprimary মরলে standby promoteবাড়তি hardware + failover-complexity
Read scalingread-query standby-এ ছড়ানোlag-এর stale-read সমস্যা
Geo-latencyregion-local read replicaদূরত্ব = বড় lag

লক্ষণীয়: write-scaling এই তালিকায় নেই — single-leader design-এ সব write leader-এই যায়। Write-scale-এর পথ শarding (দুই লেসন পরে), replication নয়।

Physical streaming — WAL byte সমুদ্রযাত্রা

Primary-র TCP connection-এ walsender process WAL-এর কাঁচা byte stream করে; standby-র startup process সেটা receive করে local WAL-এ লেখে, তারপর apply করে — একই block-layout, একই page, হুবহু clone। এজন্যই একে বলে physical: byte-for-byte অভিন্ন cluster।

Standby যদি hot_standby=on থাকে, apply চলতে চলতেই সেখানে read-only query চালানো যায় — এটাই hot standby। Query-র snapshot standby-র apply-progress অনুসরণ করে, তাই primary-র long transaction থাকলে standby-তে query-cancel-ও ঘটতে পারে (replay-কে অপেক্ষা করতে হয়) — এই সূক্ষ্মতা production-এর পরিচিত ব্যথা।

Lag — চারটা LSN দিয়ে পড়া

pg_stat_replication view-তে চারটা position জমা হয়, আর এদের ব্যবধানেই lag-এর পুরো গল্প:

sent_lsn ──► write_lsn ──► flush_lsn ──► replay_lsn
primary     standby-র      standby-র     standby-র
পাঠিয়েছে   OS buffer-এ   disk-এ fsync  apply করেছে
  • sent_lsn − replay_lsn = total lag, standby-র চোখ কতটা পিছিয়ে
  • write_lag / flush_lag / replay_lag = ধাপ-ভেদে সময় — network slow, না standby-disk slow, না apply slow, আলাদা করে বলে দেয়

synchronous_commit — কোন মুহূর্তে উত্তর দেব

Commit-ack কোন বাধার পরে দেব — এই switch-টাই durability-র দাম-তালিকা:

LevelAck দেওয়া হয় যখনCrash/failover-এ হারায়
offlocal WAL buffer-এইসদ্য-commit হারাতে পারে (corruption নয়)
localprimary-র local disk fsyncprimary মরলে টিকে, কিন্তু standby-তে পৌঁছায়নি হলে হারায়
remote_writestandby-র OS-এ পৌঁছেছে (disk নয়)standby OS-crash-এ হারাতে পারে
on (remote_flush)standby-র disk fsyncপ্রায় কিছুই না — কিন্তু apply-র আগে হলে read পুরনো
remote_applystandby apply করেছেকিছুই না — read-after-write পর্যন্ত গ্যারান্টি

দাম ঊর্ধ্বমুখী: off → sub-ms, remote_apply → full network round-trip + apply। Default on।

ভেতরে কী ঘটছে

Logical replication — byte নয়, অর্থ

Streaming-এর সীমা: destination হুবহু একই version-এর একই layout হতে হয়। কিন্তু যদি চাই — শুধু ক’টা table, PG-14 থেকে PG-17-তে upgrade-এর মাঝে zero-downtime migration, বা অন্য কোনো system- এ feed? তখন দরকার log-এর অর্থ: row-level INSERT/UPDATE/ DELETE event-এ decode করা।

যন্ত্রটা:

  • Publication (primary-তে): কোন tables publish হবে
  • Subscription (subscriber-এ): কোন publication consume হবে
  • Replication slot (primary-তে): consumer কোথায় আছে তার bookmark — এবং এর ছায়া-দায়িত্ব: bookmark-এর পেছনের WAL মুছে ফেলা যায় না

Decode-কৃত event subscriber-এ logical worker প্রয়োগ করে — destination-এর schema ভিন্ন হতে পারে (column বাড়তি থাকতে পারে), version ভিন্ন হতে পারে। Logical replication DDL বহন করে না — table দুই পাশে ম্যানুয়ালি মেলাতে হয়; আর দুই পাশেই write চালু করলে conflict — কোনো auto-resolution নেই, মানুষ বসে ঠিক করবে। এই সীমাগুলো feature নয়, দর্শন: logical layer সরল রাখা।

Failover — RPO, RTO আর split-brain

Failover = primary মরে গেলে standby-কে নতুন primary বানানো। দুইটা সংখ্যায় এর গুণমান মাপা হয়:

  • RPO (Recovery Point Objective): কতটা data হারাবেন সর্বোচ্চ — async replication-এ lag-এর সমান; remote_apply-এ প্রায় শূন্য
  • RTO (Recovery Time Objective): service down কতক্ষণ — detection + promotion time

আর সবচেয়ে বিপজ্জনক ব্যর্থতা: split-brain। Network ভাঙল (primary আসলে জীবিত, কিন্তু বাকিরা ভাবে মরা) — দুই node-ই নিজেকে primary ভেবে write নিতে শুরু করল। দুই শাখায় ভিন্ন history — merge-এর উপায় নেই। প্রতিরোধের নাম fencing: পুরনো primary- কে প্রমাণিতভাবে নিষ্ক্রিয় করা (STONITH — “shoot the other node in the head”: power-cut, firewall-block, বা সরাসরি pg_ctl stop -m immediate)। নিয়ম কঠোর: promote-এর আগে fence — ব্যতিক্রম নেই।

Consensus-based HA tool-রা — Patroni, repmgr, Stolon — এই দুর্বল মানুষ-চালিত ধাপগুলো automate করে: etcd/ZooKeeper-এর leader-election দিয়ে একমাত্র-প্রমাণযোগ্য primary, সাথে fencing built-in। ভেতরের consensus গণিত distributed-systems module-এর বিষয় — এখানে নাম-পরিচয়টুকুই যথেষ্ট।

উদাহরণ

একটা দিনের lag-এর ডায়েরি

১০:০০  স্বাভাবিক রান্নাবাজার
       lag ≈ 64KB ≈ 40ms — অদৃশ্য, অসংখ্যানুভূত

১০:১৫  বিশাল COPY: ২ GB import শুরু
       primary এক নিঃশ্বাসে WAL ২ GB; network 1Gbps — ≥১৬ সে
       lag spike: replay 0/50A00000 বনাম sent 0/CE000000
       standby-র read-query-রা এখন ১৬+ সেকেন্ড পুরনো snapshot!

১০:২২  import শেষ -> burst-apply, ৯০ সেকেন্ডে catch-up

১১:৪৫  primary-র disk controller মরল (remote_write mode)
       ১১:৪৫:০৭ detection | ১১:৪৫:২২ fence + promote
       RPO: ০-৬ সেকেন্ডের commit | RTO: ২২ সেকেন্ড

লক্ষ্য করুন:

  • Lag spike primary-র write-rate-এর ফাংশন — bulk-import মানেই lag-এর ঋতু
  • Catch-up burst-এ standby apply দ্রুত, কিন্তু CPU/I/O তখন standby-র read-workload-ও ধীর করে
  • RPO আর RTO আলাদা axis — sync level RPO, automation RTO ঠিক করে

ছবিতে

দুই পথের মানচিত্র

PRIMARY                          WIRE              STANDBY
┌──────────────┐          ┌──────────────┐   ┌──────────────────┐
│ txn COMMIT   │          │  WAL bytes   │   │ walreceiver      │
│ WAL append   │──walsender──►(TCP)──────►│   │  write→OS buf    │
│  ▲           │          │              │   │  flush→fsync     │
│ sent_lsn     │          │              │   │  replay→apply    │
└──────────────┘          └──────────────┘   │  ▲               │
                                             │ flush_lsn replay │
                     write_lsn ──────────────┘  │                 │
                             (OS-buffer mark)   └──────────────────┘
lag = sent_lsn − replay_lsn
sync ack levels ঠিক করে: কোন মুহূর্তে client উত্তর পাবে —
off / local / remote_write / remote_flush / remote_apply
Streaming path — কাঁচা WAL byte, হুবহু clone; চার LSN-position আর তিনটা lag মাপার জায়গা।
PRIMARY                                    SUBSCRIBER
┌──────────────┐    ┌───────────────┐   ┌──────────────────┐
│ WAL          │    │ logical       │   │ logical worker   │
│ Heap INSERT  │───►│ decoding      │──►│  INSERT row      │
│ txid 742     │    │ (slot: pos)   │   │  UPDATE row      │
│ Heap UPDATE  │    └───────────────┘   │  DELETE row      │
└──────┬───────┘                        └──────────────────┘
       │         publication: কোন tables
       │         subscription: কোন publication
       ▼
  slot retains WAL ←── consumer stalled হলে এখানেই জমে disk-fill
Logical path — decode স্তর byte-কে row-event-এ ভাঙে, তাই destination ভিন্ন version/structure হতে পারে; slot হলো progress-bookmark + WAL-retention বাঁধন।

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

EXPERIMENT

Standby দাঁড় করান, lag পড়ুন, sync-এর দাম মাপুন

macOS / Linux (PostgreSQL + docker optional)· ২৫ মিনিট

ধাপ ১ — দুই-node lab (native, docker-ও চলে):

initdb -D /tmp/pgp -A trust -U admin
initdb -D /tmp/pgs -A trust -U admin
printf "port=6001\nwal_level=replica\nmax_wal_senders=5\n" >> /tmp/pgp/postgresql.conf
echo "host replication repl 127.0.0.1/32 trust" >> /tmp/pgp/pg_hba.conf
pg_ctl -D /tmp/pgp -l /tmp/pgp.log start
psql -p 6001 -c "CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'r';"
pg_basebackup -h 127.0.0.1 -p 6001 -U repl -D /tmp/pgs -R -X stream
printf "port=6002\nhot_standby=on\n" >> /tmp/pgs/postgresql.conf
pg_ctl -D /tmp/pgs -l /tmp/pgs.log start

(-R standby.signal + primary_conninfo লিখে দেয় — standby নিজেই জুড়ে নেবে।)

psql -p 6002 -c "SELECT pg_is_in_recovery();"   # t — এটা standby

ধাপ ২ — lag-এর চার মুখ:

-- primary-তে:
CREATE TABLE beat(n int);
INSERT INTO beat VALUES (generate_series(1,1000));
SELECT application_name, state,
       sent_lsn, write_lsn, flush_lsn, replay_lsn,
       write_lag, flush_lag, replay_lag
FROM pg_stat_replication;

নমুনা (annotated):

application_name | walreceiver
state            | streaming
sent_lsn         | 0/50123456   <- primary পাঠিয়েছে
flush_lsn        | 0/50123456   <- standby disk-এ — প্রায় ধরা
replay_lsn       | 0/50121000   <- apply সামান্য পিছিয়ে
replay_lag       | 00:00:00.08  <- মোট ~৮০ms

এবার pressure-test:

INSERT INTO beat SELECT generate_series(1, 3000000);
SELECT pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes,
       replay_lag FROM pg_stat_replication;

Import চলাকালীন বারবার চালান — lag বেড়ে-কমে দেখুন (example- section-এর ডায়েরিটা চোখে)।

ধাপ ৩ — slot-এর দাঁড়িপাথর:

SELECT slot_name, active, restart_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),
                                     restart_lsn)) AS retained
FROM pg_replication_slots;

এবার standby ঘুমান করুন: pg_ctl -D /tmp/pgs stop, তারপর primary-তে কিছু write করে আবার এই query — retained বেড়েছে। Standby ফিরিয়ে আনুন — retained কমে শূন্য-দিকে। এই retention-ই stalled-consumer-এর অভিশাপ; বাস্তবে max_slot_wal_keep_size-ই সেতু-ভাঙা safety।

ধাপ ৪ — sync-level-এর দাম:

\timing on
CREATE TABLE bench(g int);
SET synchronous_commit = off;
INSERT INTO bench SELECT g FROM generate_series(1,500) g;
SET synchronous_commit = local;
INSERT INTO bench SELECT g FROM generate_series(1,500) g;
SET synchronous_commit = remote_apply;   -- standby apply পর্যন্ত অপেক্ষা
INSERT INTO bench SELECT g FROM generate_series(1,500) g;
DROP TABLE bench;

off প্রায়-মুহূর্তের, local fsync-ভারী, remote_apply প্রতি statement-এ network round-trip + apply। Batch-size বাড়ালে per-row amortize — production-এ batch-write-এর অন্যতম কারণ।

pg_ctl -D /tmp/pgs stop && pg_ctl -D /tmp/pgp stop && rm -rf /tmp/pgp /tmp/pgs
এটা কী প্রমাণ করে

Lag কোনো abstract সংখ্যা নয় — চার LSN-field-এ জীবন্ত; slot-এর WAL-retention চোখে দেখা যায়; আর sync-level flip করলে commit-latency হাতে-কলমে বদলে যায়।

নিজে বানান

BUILD IT

LogShipper — lag curve, partition, আর catch-up burst

Python · ●●○○○
  1. Primary প্রতি tick-এ কয়েকটা frame emit করুন — numbered WAL
  2. Network delay model — frame arrival_tick = emit_tick + jitter
  3. Partition injection — সময়সীমায় frames retain হোক, না হারাক
  4. Standby capped-rate-এ apply করুন — lag curve স্যাম্পল করুন
  5. Heal-এর পরে catch-up burst মাপুন — divergence window হিসাব

লক্ষ্য: replication-lag-এর পুরো জীবনচক্র simulation — স্বাভাবিক ঢেউ, partition-এর প্রশস্ত উপত্যকা, heal-এর পরে খাড়া পুনরুদ্ধার।

import random

APPLY_RATE = 8          # standby প্রতি tick-এ সর্বোচ্চ এত frame apply

def simulate(delay_max=2, partition=(40, 71), horizon=140, seed=11):
    rng = random.Random(seed)
    lsn = applied = 0
    wire = []                       # (arrive_tick, frame)
    held = []                       # partition-এ আটকে থাকা frames
    lag = {}
    phase = {}
    healed_at = None
    caught_up_tick = None

    for t in range(1, horizon + 1):
        in_partition = partition[0] <= t <= partition[1]

        # --- primary: ~3 frames/tick emit ---
        for _ in range(rng.randint(2, 4)):
            lsn += 1
            if in_partition:
                held.append(lsn)             # WAL retained — হারায় না
            else:
                wire.append((t + rng.randint(1, delay_max), lsn))

        # --- heal: retained frames এখন পথে ---
        if healed_at is None and t == partition[1] + 1:
            healed_at = t
            for f in held:
                wire.append((t + rng.randint(1, delay_max), f))
            held.clear()

        # --- standby: পৌঁছানো frames, capped-rate apply ---
        arrived = sorted(fr for at, fr in wire if at <= t)
        wire = [(at, fr) for at, fr in wire if at > t]
        for fr in arrived[:APPLY_RATE]:
            applied = max(applied, fr)
        # cap ছাড়ানো frames হারায় না — পরের tick-এর queue-তে ফেরে
        wire.extend((t + 1, fr) for fr in arrived[APPLY_RATE:])
        lag[t] = lsn - applied
        if healed_at and caught_up_tick is None and lag[t] == 0:
            caught_up_tick = t
        phase[t] = ("PARTITIONED" if in_partition else
                    "CATCHING UP" if healed_at and lag[t] else "normal")

    return lag, phase, healed_at, caught_up_tick


lag, phase, healed, done = simulate()
print("tick  lag  অবস্থা")
for t in range(10, len(lag) + 1, 10):
    print(f"{t:>4} {lag[t]:>4}  {phase[t]}")

worst_t = max(lag, key=lag.get)
divergence = sum(1 for v in lag.values() if v > 0)
print(f"\nmax lag            : {max(lag.values())} frames "
      f"(tick {worst_t})")
print(f"divergence window  : {divergence} ticks")
print(f"heal -> caught up  : tick {healed} -> {done} "
      f"({done - healed} ticks)")

প্রত্যাশিত output-এর আকার:

tick  lag  অবস্থা
  10    2  normal
  20    3  normal
  30    2  normal
  40   14  PARTITIONED
  50   74  PARTITIONED
  60  134  PARTITIONED
  70  194  PARTITIONED
  80  120  CATCHING UP
  90   40  CATCHING UP
 100    0  normal
 ...

max lag            : 196 frames (tick 71)
divergence window  : 62 ticks
heal -> caught up  : tick 71 -> 97 (26 ticks)

তিনটা পাঠ:

  • Partition-এ lag প্রায় linearly বাড়ে — write-rate × সময়, এটাই async replication-এর RPO-র গাণিতিক ভিত্তি
  • Heal-এর পরে সোজা শূন্যে না — APPLY_RATE-cap-এর কারণে catch-up-ও সময় নেয়; বাস্তব standby-র replay I/O-ও এমনি capped
  • held frames হারায়নি — WAL-retention-এর মডেল; কিন্তু simulation-এ disk-unlimited, বাস্তবে সেটাই slot-এর বিল

নিজে বাড়ান:

  1. held-এর জন্য disk-budget যোগ করুন — overflow হলে frames drop (slot-invalid) — আর দেখুন divergence permanent হয়ে যায়
  2. Sync-mode অনুকরণ — client-ack শুধু তখনই যখন applied >= lsn; partition-এ availability কী হয়, গুনুন (CAP-এর প্রিভিউ!)
  3. Apply-rate-কে lag-dependent বানান — বড় backlog মানে তুলনায় দ্রুত drain (বাস্তব restore_command/walreceiver-এর আচরণ)

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

বাস্তব জগতে replication

Cloud-managed HA-র ভিত্তি। RDS Multi-AZ, Cloud SQL HA, Azure Database flexible-server — সবগুলোর ভেতরেই এই লেসনের যন্ত্র: WAL-ship + automatic failover + fence। আপনি button টেপেন, ভেতরে walsender চলে।

Read-replica fleet। Analytics-heavy product-গুলোতে BI-query standby-pool-এ, OLTP primary-তে — lag-sensitive report-এর জন্য remote_apply session-level, বাকি সব async — এই mixed-pattern সাধারণ practice।

Zero-downtime major-version upgrade। Logical replication- ই আজ PostgreSQL-এর official পথ: নতুন-version cluster পাশে দাঁড় করানো, publication-subscription-এ initial sync + ongoing catch-up, তারপর এক মুহূর্তে app-cutover — পুরনো যুগের dump-restore downtime আর নেই।

Change-data-capture শিল্প। Debezium-জাতীয় CDC platform logical decoding-কেই Kafka-topic-এ রূপান্তর করে — cache-invalidate, search-index (Elasticsearch), warehouse-feed সব WAL-এর উপজাত। “Log is the truth”-দর্শনের শিল্প-রূপ।

Split-brain-এর বাস্তব কবরস্থান। নিজের-হাতে failover-script লেখা team-দের ক্লাসিক দুর্ঘটনা: network-flap-এ দুই primary, দুই শাখার data — merge অসম্ভব, ম্যানুয়াল triage-এ ঘণ্টা। এজন্যই Patroni-জাতীয় consensus-tool-ই de-facto standard — fencing-কে amateur-এর হাত থেকে বাঁচানো।

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

“Replication backup-এর বিকল্প।”

না — replication logical দুর্ঘটনাও হুবহু copy করে: ভুলে DELETE FROM orders (WHERE clause ভুলে!) চালালে সেটা সব replica- তেও faithful-ভাবে পৌঁছাবে। Backup-এর point-in-time restore সেই ভুল থেকে ফিরিয়ে আনে; replication availability দেয়, ভুল-সুরক্ষা নয়। দুটো আলাদা insurance, দুটোই লাগে।

“Async replication-এ committed data কখনো হারায় না।”

Committed মানে local-WAL durable — primary-র disk-এ। Async-এ standby-তে পৌঁছানো এখনো বাকি থাকতে পারে; primary-র disk পুড়ে গেলে সেই সদ্য-committed transactions হারায় — ঠিক lag-bytes ততটুকু। এই জানালাই synchronous_commit=remote_write/on-এর কেনার জিনিস।

“Hot standby-তে query "প্রায় real-time" — সব report ওখানেই ফেলে দিই।”

Lag-spike-এর সময় standby-র snapshot সেকেন্ড-মিনিট পুরনো — inventory-check বা balance-report যদি exactness চায়, ভুল উত্তর। আর long standby-query replay-কে আটকালে query-cancel-ও খাবে (max_standby_streaming_delay-এর বিচার)। Stale-tolerance workload-ভেদে decide হয়, default-এ নয়।

“Split-brain মানে দুটো primary — সমস্যাটা তখনই ধরা পড়বে, সহজে fix হবে।”

সবচেয়ে ভয়ের কথা: split-brain নীরব হতে পারে — দুই শাখাই traffic serve করছে, error কোথাও নেই, যতক্ষণ না দুই শাখার ভিন্ন-history data মেলাতে যান। Detection hard, merge impossible, fix মানে এক শাখার লেখা ম্যানুয়ালি বাছাই-করে হারানো। এজন্যই preventive fencing-ই একমাত্র সঠিক strategy।

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

1

pg_stat_replication-এর চারটা LSN field আর তাদের ক্রম — এক line-এ।

স্মরণ

sent_lsn (primary পাঠিয়েছে) → write_lsn (standby OS-buffer) → flush_lsn (standby disk fsync) → replay_lsn (apply শেষ)। Total lag = sent − replay; মাঝের ব্যবধানগুলো বলে দেয় বোতলনেক network-এ, standby-disk-এ, না apply-তে।

2

remote_write আর remote_flush (on)-এর মধ্যে ঠিক কী পার্থক্য? কোন দুর্যোগে প্রথমটা data হারায় কিন্তু দ্বিতীয়টা হারায় না?

যুক্তি

remote_write: standby-র OS page-cache-এ পৌঁছালেই ack — disk ছোঁয়নি। remote_flush: fsync হয়েছে। দুর্যোগ: standby- এর machine-এ power-loss/kernel-panic — page-cache volatile, fsync’d data নয়। তাই remote_write নেটওয়ার্ক-বিপর্যয় থেকে বাঁচায়, host-crash থেকে নয়।

3

Write-rate গড়ে 2 MB/s, peak-এ 20 MB/s। Async standby-র network 10 MB/s। Peak ৩০ সেকেন্ড চললে সর্বোচ্চ lag কত? এই সিস্টেমের theoretical best-RPO কত?

প্রয়োগ

Deficit: 20−10 = 10 MB/s × ৩০ সে = ৩০০ MB backlog — peak-শেষে lag। তারপর 10 MB/s surplus-rate-এ (গড় write 2 মানে drain-rate 8 MB/s) প্রায় ৩৮ সেকেন্ডে catch-up। Best-RPO = peak-শেষ মুহূর্তে primary মরলে হারানো ডেটা = ~৩০০ MB-র WAL — এটাই async-এর দাম।

4

Stalled consumer-এর slot কেন primary-কে মেরে ফেলে — যেখানে dead standby-র streaming connection তেমন ক্ষতি করে না?

যুক্তি

দুটোই slot ব্যবহার করে — পার্থক্য visibility-তে: dead connection monitoring-এ চোখে পড়ে (state != streaming), alert হয়। কিন্তু মূল ক্ষতি দুটোতেই এক: slot restart_lsn-এর পেছনের WAL retention — consumer এগোবে না, checkpoint WAL মুছতে পারবে না, pg_wal ফুলবে। Silent-stall (connection alive, apply stuck) তাই worst — চোখে দেখা যায় না, বিলটা তবু জমে। Monitoring: retained-bytes alert।

5

Team-এর failover runbook-এ লেখা: “primary unresponsive 60s দেখলে standby promote করো।” এই rule-এ split-brain-এর ফাঁক কোথায়? Fence-step যোগ করলে ঠিক কী করতে হবে?

ডিজাইন

ফাঁক: primary আসলে alive কিন্তু network-isolated (monitoring-দের কাছে unresponsive) — promote-এর পরে দুই primary, দুই শাখা write। Fence-step: promote-এর আগে পুরনো primary-কে প্রমাণিত নিষ্ক্রিয় করা — reachable হলে pg_ctl stop -m fast/immediate, না-reachable হলে infrastructure-level (VM power-off, firewall drop 5432)। আর promote-করা node নিজেও নিশ্চিত হবে পুরনো primary- এর সাথে কোনো live connection নেই। Rule-এর নতুন রূপ: detect → fence → verify-dead → promote।

এরপর কী

এরপর কী

Replication এক data-কে ক’টা machine-এ নকল করে — কিন্তু একটা table-ই যদি কোটি row-এর, এক machine-এর memory/disk/index-ও যদি যথেষ্ট না হয়? তখন data-কে ভাঙতে হয়। পরের লেসনে: single- machine partitioning (RANGE/LIST/HASH, pruning), তারপর আসল sharding — consistent hashing ring-এর গণিত, যেখানে দেখব কীভাবে node যোগ করলে মাত্র 1/n ভগ্নাংশ key সরে।

আরও পড়ুন

  • PostgreSQL Documentation — High Availability, Load Balancing, and Replication — PostgreSQL Global Development Group · Streaming/logical replication, sync levels, slots — লেসনের PostgreSQL-specific উৎস
  • Designing Data-Intensive Applications, Chapter 5 — Replication — Martin Kleppmann · Leader-follower, replication lag, multi-leader-এর প্রামাণ্য ওভারভিউ
  • Database System Concepts — Distributed Databases chapters — A. Silberschatz, H. Korth, S. Sudarshan · Replication আর consistency-র পাঠ্যপুস্তক ভিত্তি