এই পর্বে যা শিখবেন
- ডেটা কীভাবে হারায়, আর কোন বিপদে backup, কোনটিতে replication কাজে লাগে
- RPO আর RTO ঠিক করা, আর backup-এর ধরন (logical/physical, full/incremental/differential, snapshot) বাছাই
- Base backup আর WAL archive দিয়ে “ভুল DELETE-এর ঠিক আগের মুহূর্তে” ফেরা — আর SQL-এ history টেবিল থেকে অতীতের অবস্থা দেখা
- Streaming বনাম logical replication, sync বনাম async, replication lag আর read-your-own-writes সমস্যা — লাইভ SQL-এ lag মাপা
- Failover, split-brain, fencing আর consensus; read replica-য় পড়া পাঠানো; availability-র “nine” হিসাব করা
১ · ডেটা কীভাবে হারায় — আর backup কেন আলাদা জিনিস
পর্ব ৪৮-এ (Recovery) দেখেছেন, বিদ্যুৎ গেলে DBMS নিজের WAL পড়ে নিজেকে সারায়। কিন্তু দুটি বিপদে সে অসহায়: ডিস্কটিই নষ্ট হলে log-ও হয়তো গেছে; আর কেউ ভুল করে DROP TABLE orders চালালে সেটি একটি বৈধ, commit হওয়া কাজ — recovery একে “সারাবে” না, বরং টেকসই রাখবে। এই পর্বের বিষয় তাই দুটি প্রশ্ন: ডেটা হারালে ফেরত আনব কীভাবে (backup আর restore), আর একটি সার্ভার বন্ধ হলেও সেবা চালু রাখব কীভাবে (replication আর high availability).
মুদি দোকানের বাকির খাতার কথা ভাবুন। দোকানি প্রতি রাতে খাতার পাতার ছবি তুলে বাড়ির কম্পিউটারে রাখেন — এটি backup. দোকানে তাঁর ছেলে আরেকটি খাতায় একই সময়ে একই হিসাব লিখে যায় — এটি replication. বাবা অসুস্থ হলে ছেলে খাতা চালিয়ে যায়, দোকান বন্ধ হয় না। কিন্তু দোকানে আগুন লাগলে দুটি খাতাই পোড়ে; আর বাবা ভুল করে কারও বাকি কেটে দিলে ছেলেও কেটে দেয়। দুই ক্ষেত্রেই আসল হিসাব মেলে শুধু গত রাতের ছবিতে।
হার্ডওয়্যার নষ্ট
ডিস্ক, RAID controller বা পুরো সার্ভার মরে যায়; একই ব্যাচের ডিস্ক প্রায় একই সময়ে মরতে পারে।
মানুষের ভুল
WHERE ছাড়া UPDATE বা DELETE, ভুল সার্ভারে DROP TABLE, ভুল migration — ডেটা হারানোর খুব সাধারণ কারণ।
সফটওয়্যারের bug
অ্যাপের ভুল কোড সপ্তাহের পর সপ্তাহ চুপচাপ ভুল মান লেখে; ধরা পড়লে দরকার পুরোনো, সঠিক অবস্থা।
Ransomware আর দুর্যোগ
Ransomware ডেটা আর হাতের কাছের backup দুটোই encrypt করে মুক্তিপণ চায়। বন্যা, আগুন বা দীর্ঘ লোডশেডিং পুরো ভবন অচল করতে পারে।
Replication আর RAID রক্ষা করে যন্ত্রের মৃত্যু থেকে। কিন্তু ভুল কমান্ড, bug বা ransomware-এর লেখাও তারা বিশ্বস্তভাবে কপি করে। সময়ে পেছনে ফেরা যায় শুধু backup দিয়ে — যা অন্য জায়গায়, অন্য নিয়ন্ত্রণে, অতীতের একটি মুহূর্তের কপি।
২ · RPO আর RTO — কতটা হারানো চলে, কতক্ষণ বন্ধ থাকা চলে
backup-এর পরিকল্পনা শুরু হয় প্রযুক্তি দিয়ে নয়, দুটি ব্যবসায়িক প্রশ্ন দিয়ে।
RPO · Recovery Point Objective
বিপর্যয়ের পর সর্বোচ্চ কত সময়ের ডেটা হারানো মেনে নেওয়া যায়। রাতে একবার backup হলে খারাপ দিনে প্রায় ২৪ ঘণ্টার কাজ হারায়, অর্থাৎ RPO ২৪ ঘণ্টা।
RTO · Recovery Time Objective
বিপর্যয় থেকে সেবা আবার চালু হওয়া পর্যন্ত সর্বোচ্চ কত সময় লাগতে পারে। এর মধ্যে ধরা পড়া, সিদ্ধান্ত, restore, যাচাই আর অ্যাপ ফেরানো — সব।
শেষ ভালো কপি বিপর্যয় সেবা আবার চালু
│◄──── RPO: হারানো ডেটা ───►│◄──── RTO: বন্ধ থাকা ────►│
─────●──────────────────────────✗──────────────────────────●────► সময়
সংখ্যা ঠিক হয় কাজের ধরন দেখে। মোবাইলে টাকা পাঠানোর সেবায় একটি নিশ্চিত লেনদেনও হারানো চলে না (RPO শূন্য), আর কয়েক মিনিট বন্ধ থাকলেই লাখো মানুষ আটকে যান। বিশ্ববিদ্যালয়ের ফল প্রকাশের সাইটে আগের রাতের backup হয়তো যথেষ্ট, কারণ ফল আবার upload করা যায়। নিয়ম: RPO আর RTO যত ছোট, ব্যবস্থা তত দামি। রাতের backup সস্তা; সারাক্ষণ WAL archive করলে RPO কয়েক মিনিট; synchronous replica-তে প্রায় শূন্য, কিন্তু দ্বিতীয় সার্ভার আর ধীর commit-এর দামে। লক্ষ্য ঠিক করা তাই দর-কষাকষি: এক ঘণ্টা বন্ধে কত ক্ষতি, ঠেকাতে কত খরচ।
একটি সূক্ষ্ম কথা: RPO নির্ভর করে সফল backup-এর মাঝের ফাঁকের ওপর, সময়সূচির ওপর নয়। কোনো রাতে backup চুপচাপ ব্যর্থ হলে আসল RPO দ্বিগুণ হয়ে যায়। নিচের এডিটর দোকানের নমুনা ডেটাবেসে চলে, তবে এখানে শুধু একটি বাড়তি টেবিল লাগবে: backup_runs (run_id, kind — full না incremental, finished_at — কখন শেষ, status — ফল)। LAG window function (পর্ব ২৬) দিয়ে পরপর দুটি সফল backup-এর ফাঁক মাপি:
SELECT run_id, kind, finished_at,
ROUND((julianday(finished_at)
- julianday(LAG(finished_at) OVER (ORDER BY finished_at))) * 24, 1)
AS hours_since_prev_ok
FROM backup_runs
WHERE status = 'ok'
ORDER BY finished_at;
SELECT run_id, finished_at, status FROM backup_runs WHERE status <> 'ok';প্রথম ফলের পাঁচ সারি: run 1-এর ফাঁক NULL (আগে কিছু নেই), তারপর 23.7, 24, 48.2 আর 24.5 ঘণ্টা। দ্বিতীয় ফলে এক সারি: 4 | 2025-10-08 02:05 | failed: disk full. WHERE আগে ব্যর্থ run বাদ দিয়েছে, তারপর LAG চলেছে — তাই run 5-এর আগের সফল backup run 3, ফাঁক ৪৮.২ ঘণ্টা। ৯ অক্টোবর রাত ২টার ঠিক আগে ডিস্ক নষ্ট হলে হারাত প্রায় দুই দিনের কাজ, যদিও সময়সূচি বলছে “প্রতি রাতে”। (julianday() দিনে হিসাব দেয়, তাই ×২৪.) শিক্ষা: backup-এরও monitoring লাগে — ব্যর্থ run-এ সতর্কবার্তা, আর ফাঁক RPO ছাড়ালে অ্যালার্ম।
৩ · Backup-এর ধরন — logical, physical, full, incremental আর snapshot
প্রথম ভাগ: কী কপি করা হচ্ছে। Logical backup ডেটাকে আবার বানানোর নির্দেশ লেখে — CREATE TABLE আর INSERT বা COPY-এর একটি ফাইল। Physical backup ডেটাবেসের আসল ফাইলগুলো (data page, WAL) বাইট ধরে কপি করে।
| বিষয় | Logical (dump) | Physical (ফাইল) |
|---|---|---|
| উদাহরণ | pg_dump, mysqldump, SQLite .dump | pg_basebackup, Percona XtraBackup, ডিস্ক snapshot |
| বহনযোগ্যতা | অন্য সংস্করণ, অন্য OS, এমনকি অন্য DBMS-এ (কিছু বদলে) | সাধারণত একই major সংস্করণ আর একই ধরনের প্ল্যাটফর্ম |
| বাছাই করে নেওয়া | একটি টেবিল বা schema সহজে | প্রায় সবসময় পুরো cluster |
| restore-এর গতি | ধীর — সব সারি আবার ঢোকানো, সব index আবার বানানো, constraint আবার যাচাই | দ্রুত — ফাইল কপি করে বাকিটা WAL replay |
| PITR | না — শুধু dump-এর মুহূর্ত | হ্যাঁ, WAL archive-এর সাথে |
কয়েক GB-তে logical dump চমৎকার; TB-তে restore-এ ঘণ্টার পর ঘণ্টা লাগে (প্রতিটি index শূন্য থেকে গড়তে হয়) — তখন RTO-ই physical backup বাধ্য করে। dump টুল ব্যস্ত ডেটাবেসেও সংগত কপি দেয়: pg_dump একটি transaction-এর snapshot-এ (পর্ব ৪৭-এর MVCC) সব পড়ে, আর InnoDB-তে mysqldump-কে --single- দিলে একই কাজ হয়।
# PostgreSQL: custom format (-Fc) — সংকুচিত, pg_restore দিয়ে বাছাই আর parallel restore
pg_dump -h db1 -U backup_user -d shop -Fc -f shop_2025-10-10.dump
pg_restore -h db-test -U postgres -d shop_check -j 4 shop_2025-10-10.dump
pg_dumpall -h db1 -U postgres --globals-only > globals.sql # role আর ব্যবহারকারী আলাদা
# MySQL/InnoDB: lock ছাড়া সংগত dump, সাথে procedure, trigger আর event
mysqldump --single-transaction --routines --triggers --events shop > shop.sql
mysql shop_check < shop.sql
# SQLite: পুরো ডেটাবেস SQL লেখা হিসেবে
sqlite3 shop.db .dump > shop.sql# PostgreSQL: চলমান সার্ভারের base backup, সাথে দরকারি WAL (-X stream)
pg_basebackup -h db1 -U replicator -D /backup/base_2025-10-05 -Ft -z -X stream -Pদ্বিতীয় ভাগ: প্রতিবার কতটা কপি।
Full
সবকিছু। restore সবচেয়ে সরল — একটি ফাইলই যথেষ্ট — কিন্তু সময় আর জায়গা সবচেয়ে বেশি।
Incremental
আগের যেকোনো backup-এর পর যা বদলেছে। প্রতিদিনের কপি ছোট, কিন্তু restore-এ full আর তার পরের সব incremental ক্রমানুসারে লাগে; শিকলের একটি হারালে তার পরেরগুলো অচল।
Differential
শেষ full-এর পর যা বদলেছে। দিন যত যায় কপি তত বড় হয়, কিন্তু restore-এ লাগে শুধু full আর শেষ differential.
ধরুন রবিবার full, আর বৃহস্পতিবার সকালে বিপদ। incremental-এ লাগবে রবি + সোম + মঙ্গল + বুধ — চারটি ফাইল। differential-এ রবি + বুধ — দুটি। PostgreSQL ১৭ থেকে pg_basebackup নিজেই incremental backup নিতে পারে, আর pg_combinebackup সেগুলো জুড়ে দেয়; MySQL-এ Percona XtraBackup-এর মতো টুল অনেক দিন ধরে এটি করে।
তৃতীয় পথ storage snapshot: LVM, ZFS বা ক্লাউডের ডিস্ক (যেমন একটি volume-এর snapshot) এক মুহূর্তে পুরো ডিস্কের ছবি রাখে, copy-on-write কৌশলে প্রায় সঙ্গে সঙ্গে। এটি “crash-consistent” — যেন ঠিক ওই মুহূর্তে বিদ্যুৎ গিয়েছিল; restore-এর পর DBMS পর্ব ৪৮-এর crash recovery চালিয়ে সংগত হয়, যদি data আর WAL একই মুহূর্তের snapshot-এ থাকে (নইলে PostgreSQL-এর pg_/pg_ দিয়ে সীমা চিহ্নিত করতে হয়)। আর একই ডিস্ক-ব্যবস্থার snapshot সেই ব্যবস্থার সাথেই মরে — অন্য জায়গায় কপি না করলে এটি পুরো backup নয়।
৪ · Continuous archiving আর point-in-time recovery
রাতের backup-এর RPO ২৪ ঘণ্টা। কমানোর উপায় পর্ব ৪৮-এর WAL-এই লুকিয়ে আছে: প্রতিটি commit হওয়া পরিবর্তন তো WAL-এ লেখা হয়ই। পূর্ণ হওয়া প্রতিটি WAL segment ফাইল অন্য জায়গায় কপি করে রাখলে — continuous archiving — হাতে থাকে একটি base backup আর তার পরের পুরো ইতিহাস। restore মানে base backup ফেরানো, তারপর archived WAL redo করে যেকোনো মুহূর্তে থামা: point-in-time recovery (PITR). RPO তখন শুধু শেষ archive না হওয়া WAL-টুকু — কয়েক মিনিট বা তারও কম। PostgreSQL-এ সেটিং:
# মূল সার্ভারে: প্রতিটি পূর্ণ WAL segment archive-এ কপি
archive_mode = on
archive_command = 'test ! -f /mnt/archive/%f && cp %p /mnt/archive/%f'
archive_timeout = 300 # কম লেখার সময়ও অন্তত ৫ মিনিটে একবার segment বদল
# restore-এর সার্ভারে (PostgreSQL 12+; সাথে data ফোল্ডারে খালি recovery.signal ফাইল)
restore_command = 'cp /mnt/archive/%f %p'
recovery_target_time = '2025-10-07 15:04:59+06'
recovery_target_action = 'pause' # লক্ষ্যে পৌঁছে থামো, আগে যাচাই করিMySQL-এ একই ভূমিকা binary log-এর (binlog): full backup ফেরানোর পর mysqlbinlog --stop- দিয়ে binlog একটি মুহূর্ত পর্যন্ত চালানো হয়। এবার পুরো ঘটনাটি ধাপে ধাপে। মঙ্গলবার বিকেল ৩:০৫-এ একজন ডেভেলপার পরীক্ষার সার্ভার ভেবে আসল সার্ভারে পুরোনো অর্ডার মোছার একটি DELETE চালালেন:
| লাইভ সার্ভারের orders | |
| সার্ভার R-এর ঘড়ি | |
| ঘটনার পর কত সময় | |
| হারানোর ঝুঁকি |
- প্রস্তুতি আগে থেকেই। রবিবার রাত ২টায় base backup B, তারপর থেকে প্রতিটি WAL segment archive-এ। এই প্রস্তুতি না থাকলে PITR অসম্ভব — বিপদের দিনে এটি চালু করা যায় না।
- ভুল কাজ commit হলো। DBMS-এর কাছে এটি বৈধ, তাই WAL-এ লেখা হলো, replica-তেও গেল। ধরা পড়ল ১৫ মিনিট পরে। প্রথম কাজ: সঠিক সময় বের করা — অ্যাপের log, statement log বা WAL পড়ার টুল (PostgreSQL-এ pg_waldump) থেকে।
- আলাদা সার্ভারে restore. লাইভ সার্ভারের ওপর restore করলে ৩:০৫-এর পরের ভালো লেখাও মুছে যাবে, তাই নতুন সার্ভার R-এ B বসানো হলো। R এখন রবিবার রাত ২টায় আটকে।
- লক্ষ্য ঠিক করা। restore_command বলে archive থেকে segment কীভাবে আনতে হবে, recovery_target_time বলে কোথায় থামতে হবে।
+06বাংলাদেশের সময়-অঞ্চল — ভুল হলে ছয় ঘণ্টা আগে বা পরে থামবে! সময়ের বদলে transaction id বা LSN-ও লক্ষ্য দেওয়া যায়। - ইতিহাস আবার চালানো। R একটির পর একটি segment এনে redo করে, প্রতিটি commit record-এর সময় দেখে। ৩:০৫:০০-এর commit লক্ষ্যের পরে, তাই তার আগেই থামে — ভুল DELETE কখনো প্রয়োগ হয় না। base backup যত পুরোনো, replay তত লম্বা।
- যাচাই আর ফেরানো। দুটি পথ: মুছে যাওয়া সারি R থেকে লাইভে কপি করা (পরের ভালো লেখা থাকে), অথবা R-কে নতুন primary বানানো (৩:০৫-এর পরের সব লেখা হারায়)। মোট ৯০ মিনিট — এটি RTO-র মধ্যে কি না, মহড়া দিয়ে আগেই জানা উচিত।
SQL-এর ভেতরে সময়ভ্রমণ — history টেবিল
PITR পুরো ডেটাবেসকে অতীতে নেয়। অনেক সময় দরকার শুধু একটি টেবিলের একটি column — আর তখন ভালো উপায় ডেটাবেসের ভেতরেই একটি history রাখা। SQL:2011 standard-এ এর নাম temporal table; SQL Server আর MariaDB-তে system-versioned টেবিল আছে, Oracle-এ Flashback Query, আর অন্যত্র trigger দিয়ে বানানো audit log (পর্ব ২৯)। আমাদের দোকানে stock_history টেবিল প্রতিটি পণ্যের মজুদ (stock) কখন থেকে কত ছিল তা রাখে: product_id, stock, valid_from (কখন থেকে এই মান), changed_by (কে বা কী বদলাল)। ঘটনা: সকালে খোলার মান, দিনের বেলা তিনটি বিক্রি, তারপর ৩:০৫-এ কেউ WHERE ছাড়া UPDATE products SET stock = 0 চালিয়েছেন, আর ৩:১০-এ Power Bank (103)-এর নতুন চালান এসে মজুদ ২০ হয়েছে। এডিটর খোলার সময় এসব আগেই ঘটে গেছে। ৩:০৪:৫৯-এ মজুদ কত ছিল?
SELECT p.product_id, p.name,
(SELECT h.stock FROM stock_history h
WHERE h.product_id = p.product_id
AND h.valid_from <= '2025-10-10 15:04:59'
ORDER BY h.valid_from DESC LIMIT 1) AS stock_at_1504,
p.stock AS stock_now
FROM products p
ORDER BY p.product_id;দশ সারি। stock_at_1504: 11, 40, 0, 25, 30, 15, 8, 97, 60, 4; stock_now: 103-এ 20, বাকি সবার 0. correlated subquery (পর্ব ২১) প্রতিটি পণ্যের জন্য ৩:০৪:৫৯-এর আগের সবচেয়ে নতুন history সারিটি নেয় — “as of” প্রশ্নের সাধারণ রূপ। 101-এ সকালের ১২ নয়, ১১:২০-এর ১১ — ভালো বিক্রিগুলো হিসাবে আছে। আর 103: আগে ০, এখন ২০ — ভুলের পরের একটি সঠিক পরিবর্তন।
এবার শুধু ভুলটুকু উল্টাই: যে পণ্যগুলোর মজুদ ৩:০৫-এর পরে আর বদলায়নি, তাদের ৩:০৪:৫৯-এর মানে ফেরানো। ৩:১০-এর চালান যেন না মোছে:
UPDATE products
SET stock = (SELECT h.stock FROM stock_history h
WHERE h.product_id = products.product_id
AND h.valid_from <= '2025-10-10 15:04:59'
ORDER BY h.valid_from DESC LIMIT 1)
WHERE product_id NOT IN (SELECT product_id FROM stock_history
WHERE valid_from > '2025-10-10 15:05:00');
SELECT product_id, stock FROM products ORDER BY product_id;
SELECT SUM(stock) AS total_stock FROM products;UPDATE-এ “৯টি সারি বদলেছে” — 103 বাদ, কারণ ৩:১০-এ তার নতুন history সারি আছে। তারপর মজুদ: 11, 40, 20, 25, 30, 15, 8, 97, 60, 4, মোট 310. পুরো ডেটাবেস PITR-এ ৩:০৪:৫৯-এ ফিরলে 103-এর চালান (০ → ২০) হারিয়ে যেত; এখানে তা টিকে আছে। তবে এটি কাজ করেছে কারণ history টেবিল আগে থেকে ছিল আর ভুল কমান্ড সেটিকে ছোঁয়নি; DROP TABLE বা ডিস্ক নষ্টে history-ও যায় — তখন আবার PITR. বাস্তবে মেরামত আগে একটি transaction-এ চালিয়ে ফল মিলিয়ে তারপর COMMIT করুন (পর্ব ৪৪)।
৫ · 3-2-1, encryption, retention — আর restore-এর মহড়া
backup কোথায়, কয়টি, কতদিন — এর সবচেয়ে প্রচলিত নিয়ম 3-2-1, যা ফটোগ্রাফার Peter Krogh-এর লেখার মাধ্যমে জনপ্রিয় হয়েছে বলে ধরা হয়:
অর্থাৎ মূল ডেটা ছাড়াও দুটি backup; একই ডিস্ক-ব্যবস্থায় সব নয় (যেমন একটি ডিস্কে, একটি object storage-এ); আর একটি অন্য শহরে বা অন্য ক্লাউড region-এ — ঢাকার অফিসে আগুন বা বন্যা হলে যেন চট্টগ্রাম বা অন্য কোথাও একটি কপি বাঁচে। ransomware-এর যুগে অনেকে যোগ করেন আরও দুটি অঙ্ক: একটি কপি immutable বা অফলাইন (নির্দিষ্ট সময় পর্যন্ত কেউ মুছতে বা বদলাতে পারে না, এমনকি admin-এর পাসওয়ার্ড চুরি হলেও), আর restore-যাচাইয়ে শূন্য ত্রুটি।
- Encryption: backup-এ পুরো ডেটাবেস থাকে — গ্রাহকের ফোন নম্বর, ঠিকানা, লেনদেন। পথে (TLS) আর জমায় দুই জায়গাতেই encrypt করুন (পর্ব ৫১), চাবি রাখুন আলাদা জায়গায়। চাবি হারালে backup-ও হারাল।
- Retention: প্রচলিত ধাঁচ: প্রতিদিনেরটি ১৪ দিন, সাপ্তাহিকটি ৮ সপ্তাহ, মাসিকটি ১২ মাস। নীরব bug ধরা পড়ে সপ্তাহ পরে, তাই খুব ছোট retention বিপজ্জনক; আবার গোপনীয়তার নিয়মে পুরোনো ব্যক্তিগত ডেটা মোছার দায় backup-এও পড়ে।
- যাচাই: নিয়মিত অন্য সার্ভারে পুরো restore করে সারি গোনা, পরিচিত কোয়েরি চালানো আর সময় মাপা। মাপা সময়টিই আপনার আসল RTO.
GitLab, ৩১ জানুয়ারি ২০১৭। কোম্পানির নিজের প্রকাশিত postmortem অনুযায়ী, পিছিয়ে পড়া একটি PostgreSQL replica আবার গড়তে গিয়ে একজন প্রকৌশলী ভুল টার্মিনালে — replica-র বদলে primary-তে — data ফোল্ডার মোছার কমান্ড চালান। তারপর দেখা গেল, কয়েক রকম backup ও replication ব্যবস্থার কোনোটিই নির্ভরযোগ্যভাবে কাজ করছিল না; যেমন নিয়মিত pg_dump চুপচাপ ব্যর্থ হচ্ছিল। বাঁচায় প্রায় ছয় ঘণ্টা আগে অন্য কাজে নেওয়া একটি snapshot, আর প্রায় ছয় ঘণ্টার database ডেটা হারায় (Git repository-গুলো অক্ষত ছিল)। শিক্ষাটি এখন প্রবাদ: যে backup restore করে দেখেননি, তা backup নয়। বিস্তারিত বিশ্লেষণ ভাবনার প্রশ্ন ১-এ।
SQLite-এর backup
SQLite একটিমাত্র ফাইল, তাই লোভ হয় cp করতে। কিন্তু লেখার মাঝে কপি করলে অর্ধেক-বদলানো ফাইল আসতে পারে, আর WAL মোডে commit হওয়া ডেটার একটি অংশ থাকে আলাদা -wal ফাইলে। নিরাপদ পথ তিনটি: online backup API (CLI-তে .backup), VACUUM INTO (৩.২৭.০ সংস্করণ, ২০১৯ থেকে — একটি সংকুচিত, সংগত কপি নতুন ফাইলে), আর ক্রমাগত কপির জন্য Litestream-এর মতো বাইরের টুল, যা WAL-এর পরিবর্তন object storage-এ পাঠিয়ে SQLite-এও PITR-ধাঁচের restore দেয়।
sqlite3 shop.db ".backup 'backup_2025-10-10.db'"
sqlite3 shop.db "VACUUM INTO 'copy_2025-10-10.db'"
sqlite3 backup_2025-10-10.db "PRAGMA integrity_check" # ok আসা চাইনিচের Python প্রোগ্রাম (stdlib-এর sqlite3, আসল ফাইলে) পুরো চক্রটি দেখায়: ৩,০০০ অর্ডারের একটি ডেটাবেসের online backup, তারপর যাচাই — integrity check আর প্রতিটি সারির hash মিলিয়ে — তারপর একটি “ভুল DELETE” আর backup থেকে restore:
import hashlib, os, sqlite3, tempfile
d = tempfile.mkdtemp()
live = sqlite3.connect(os.path.join(d, "shop.db"))
live.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, customer TEXT, amount INTEGER)")
live.executemany("INSERT INTO orders (customer, amount) VALUES (?, ?)",
[(f"cust{i % 40}", i * 10) for i in range(1, 3001)]); live.commit()
def fingerprint(con): # row count + hash of every row, in key order
rows = con.execute("SELECT * FROM orders ORDER BY id").fetchall()
return len(rows), hashlib.sha256(repr(rows).encode()).hexdigest()[:12]
def progress(status, remaining, total):
print(f" copied {total - remaining} of {total} pages")
backup = sqlite3.connect(os.path.join(d, "backup.db"))
live.backup(backup, pages=5, progress=progress) # 5 pages per step; writers may run in between
print("integrity:", backup.execute("PRAGMA integrity_check").fetchone()[0])
print("live:", fingerprint(live), " backup:", fingerprint(backup))
live.execute("DELETE FROM orders WHERE amount > 100"); live.commit() # the bad DELETE
print("after bad DELETE:", fingerprint(live)[0], "rows")
backup.backup(live) # restore: copy the backup over the live db
print("after restore :", fingerprint(live))
live.execute(f"VACUUM INTO '{os.path.join(d, 'copy2.db')}'") # another consistent copy
print("VACUUM INTO file bytes:", os.path.getsize(os.path.join(d, "copy2.db")))আউটপুট:
copied 5 of 15 pages copied 10 of 15 pages copied 15 of 15 pages integrity: ok live: (3000, '79061664e07a') backup: (3000, '79061664e07a') after bad DELETE: 10 rows after restore : (3000, '79061664e07a') VACUUM INTO file bytes: 61440
backup API page ধরে কপি করে — ১৫টি page, প্রতি ধাপে ৫টি। ধাপের মাঝে অন্য সংযোগ লিখলে API কপি আবার শুরু করে, তাই ফল সবসময় একটি মুহূর্তের সংগত ছবি। integrity_check শুধু ফাইলের কাঠামো যাচাই করে; ডেটা ঠিক আছে কি না বলে hash-এর মিল — দুই দিকে একই ৩,০০০ সারি, একই hash. ভুল DELETE-এর পর ১০টি সারি, restore-এর পর হুবহু আগের hash. VACUUM INTO-এর ফাইল ৬১৪৪০ বাইট = ১৫ × ৪০৯৬. মহড়া এভাবেই হওয়া উচিত: সংখ্যা আর hash মিলিয়ে, শুধু “ফাইল আছে” দেখে নয়।
৬ · Replication — কেন, আর কীভাবে
Replication মানে একই ডেটার একাধিক জীবন্ত কপি, যা সারাক্ষণ হালনাগাদ থাকে। কারণ তিনটি: high availability (একটি সার্ভার মরলে আরেকটি সেকেন্ডে দায়িত্ব নেয়), পড়ার চাপ ভাগ (রিপোর্টের কোয়েরি কপিতে), আর ভৌগোলিক দূরত্ব (দূরের ব্যবহারকারীর কাছে কপি, শহরের বিপর্যয়ে অন্য শহরের কপি)। backup নেওয়াও replica-তে চালানো যায়।
সবচেয়ে প্রচলিত গঠন primary–replica (leader–follower; পুরোনো লেখায় master–slave). সব লেখা যায় একটি primary-তে। primary নিজের log replica-দের পাঠায়, আর replica সেই log নিজের কপিতে প্রয়োগ করে — পর্ব ৪৮-এর redo-ই, শুধু অন্য যন্ত্রে, সারাক্ষণ। replica-গুলো পড়ার কোয়েরি নিতে পারে। log পাঠানোর ধরনে দুটি পরিবার:
Streaming replication (PostgreSQL ৯.০, ২০১০ থেকে) WAL record-ই পাঠায় — কোন page-এর কোন বাইট বদলাল। replica primary-র বাইট-ধরে-হুবহু কপি: সব ডেটাবেস, সব index, সব schema বদল। সরল আর দ্রুত, কিন্তু একই major সংস্করণ লাগে, আর একটি টেবিল বাছাই করে পাঠানো যায় না। replica-তে পড়ার কোয়েরি চলতে দেওয়াকে PostgreSQL বলে hot standby.
Logical replication সারির স্তরের পরিবর্তন পাঠায়: “orders টেবিলে এই সারি ঢুকল, এই সারির status বদলাল”। PostgreSQL ১০ (২০১৭) থেকে publication/subscription. টেবিল বাছাই করা যায়, আলাদা major সংস্করণে পাঠানো যায় — তাই প্রায় শূন্য বিরতিতে সংস্করণ বদলে কাজে লাগে। দাম: schema বদল (DDL) নিজে থেকে যায় না। একই ধারণার আত্মীয় CDC.
MySQL-এর replication চলে binlog দিয়ে, যা InnoDB-র redo log থেকে আলাদা (পর্ব ৪৮)। binlog-এর ধরন statement, row বা mixed; ৫.৭.৭ থেকে default row — অর্থাৎ ভেতরে এটি logical ধাঁচের। replica binlog নিজের relay log-এ নামিয়ে আবার চালায়। GTID প্রতিটি transaction-কে একটি বৈশ্বিক পরিচয় দেয়, যাতে failover-এর পর “কে কতদূর পেয়েছে” সহজে মেলে।
replica কতটা পিছিয়ে আছে — replication lag — সেটিই replication-এর প্রধান স্বাস্থ্য-সংকেত। নিচের এডিটরে দুটি বাড়তি টেবিল। replication_log হলো primary-র commit-এর ধারা: lsn (ক্রমিক অবস্থান), committed_at (কখন), tbl (কোন টেবিল), summary (কী বদলাল)। replicas-এ তিনটি কপি: name, mode (sync বা async), city আর replayed_lsn — কোন lsn পর্যন্ত নিজের কপিতে প্রয়োগ করেছে। sessions টেবিলটি পরের অংশে লাগবে।
SELECT r.name, r.mode, r.replayed_lsn,
(SELECT MAX(lsn) FROM replication_log) - r.replayed_lsn AS commits_behind,
CAST(ROUND((julianday((SELECT MAX(committed_at) FROM replication_log))
- julianday(l.committed_at)) * 86400) AS INTEGER) AS lag_seconds
FROM replicas r
JOIN replication_log l ON l.lsn = r.replayed_lsn
ORDER BY lag_seconds;তিন সারি: dhaka-sync | sync | 508 | 0 | 0, dhaka-read | async | 506 | 2 | 5, ctg-dr | async | 502 | 6 | 16. lag দুইভাবে মাপা: কত commit পিছিয়ে (৫০৮ − ৫০২ = ৬), আর সময়ে — primary-র শেষ commit (১৫:০০:২০) আর replica-র শেষ প্রয়োগ করা commit-এর (১৫:০০:০৪) ফারাক ১৬ সেকেন্ড (julianday দিনে গোনে, তাই ×৮৬৪০০)। বাস্তবে PostgreSQL-এর pg_ view প্রতিটি replica-র sent, flush আর replay LSN ও lag দেখায়; MySQL-এ SHOW REPLICA STATUS. ফাঁদ: primary-তে অনেকক্ষণ লেখা না এলে “শেষ commit কত আগের” ধাঁচের সময়-হিসাব ভুয়া বড় lag দেখাতে পারে — LSN-এর হিসাবও পাশে রাখুন।
ঠিক এই মুহূর্তে primary মরে গেলে কোন replica-কে primary বানালে কী হারাবে? replica যে commit-গুলো দেখেনি সেগুলোই ঝুঁকিতে:
SELECT r.name, l.lsn, l.committed_at, l.summary
FROM replicas r
JOIN replication_log l ON l.lsn > r.replayed_lsn
WHERE r.name = 'ctg-dr'
ORDER BY l.lsn;
SELECT r.name, COUNT(l.lsn) AS unseen_commits
FROM replicas r
LEFT JOIN replication_log l ON l.lsn > r.replayed_lsn
GROUP BY r.name
ORDER BY unseen_commits;প্রথম ফল ছয় সারি, lsn ৫০৩ থেকে ৫০৮: customer 3-এর নতুন অর্ডার 1011, পণ্য 104-এর মজুদ কমা, অর্ডার 1009 বাতিল, নতুন customer 9, তার অর্ডার 1012, আর পণ্য 110-এর মজুদ। দ্বিতীয় ফল: dhaka-sync | 0, dhaka-read | 2, ctg-dr | 6. JOIN-এর শর্তটি সমতা নয়, অসমতা (>). দ্বিতীয়টিতে LEFT JOIN আর COUNT(l.lsn) জরুরি: সাধারণ JOIN-এ dhaka-sync-এর সারিটিই উধাও হতো, আর COUNT(*) শূন্যের বদলে ১ দিত (পর্ব ১৮)। সিদ্ধান্ত: failover-এ dhaka-sync; ctg-dr বাছলে ছয়টি নিশ্চিত হওয়া commit হারায়।
৭ · Synchronous বনাম asynchronous — durability বনাম গতি
মূল প্রশ্ন: primary কখন অ্যাপকে “commit সফল” বলবে? Asynchronous-এ নিজের ডিস্কে WAL লিখেই; log পরে replica-তে যায়। Synchronous-এ অন্তত একটি replica “পেয়েছি” বলার পর। পার্থক্যটি একটি bKash-ধাঁচের লেনদেনে দেখুন — primary ঢাকায়, replica চট্টগ্রামে, এক দিকে নেটওয়ার্কে ধরুন ৪ ms:
| async অ্যাপ | |
| sync অ্যাপ | |
| replica-তে আছে? |
- COMMIT এল। দুই ব্যবস্থাতেই primary প্রথমে নিজের WAL-এ commit record লেখে — পর্ব ৪৮-এর নিয়ম ২.
- স্থানীয় fsync শেষ। async এখানেই অ্যাপকে “সফল” বলে দেয় — খরচ শুধু নিজের ডিস্ক। sync অপেক্ষা করে।
- WAL পথে। async-এ log পাঠানো চলে পেছনে। কাচের তারে আলো সেকেন্ডে প্রায় দুই লাখ কিলোমিটার যায় — দূরত্বের এই সময় কোনো কৌশলে কমে না।
- ack ফিরল। replica নিজের ডিস্কে লিখে জানাল, primary তা পেল t=৯-এ। এবার sync অ্যাপ “সফল” শোনে — round trip মিলে প্রতিটি commit-এ ৮ ms বাড়তি।
- Crash-এর পরীক্ষা। t=২-এ primary মরলে async-এর অ্যাপ জানে টাকা গেছে, অথচ replica-তে লেনদেনটি নেই — promote করলে চিরতরে হারায়। sync-এর অ্যাপ উত্তর পায়নি, তাই জানে ফল অনিশ্চিত; replica হয়তো log পেয়েছিল। তাই আবার চেষ্টায় একটি idempotency key দরকার, যাতে লেনদেন দুবার না হয়।
- দাম-লাভ। async: দ্রুত, কিন্তু RPO শূন্য নয়। sync: RPO শূন্য, কিন্তু প্রতিটি commit-এ round trip, আর একমাত্র sync replica বন্ধ হলে সব commit আটকে থাকে — durability কিনতে গিয়ে availability কমে।
বাস্তব সিস্টেমে মাঝামাঝি অনেক বিকল্প। PostgreSQL-এ synchronous_ ঠিক করে কোন replica sync, আর synchronous_commit ঠিক করে কতটা অপেক্ষা:
| মান | commit সফল হয় যখন | মানে |
|---|---|---|
remote_ | sync replica WAL পেয়ে OS-কে লিখতে দিয়েছে | replica-র OS crash করলে হারাতে পারে |
on (default) | sync replica WAL নিজের ডিস্কে flush করেছে | দুই যন্ত্রে টেকসই |
remote_ | sync replica WAL প্রয়োগও করেছে | replica-তে পড়লেও নতুন লেখা দেখা যাবে — সবচেয়ে ধীর |
একটি মাত্র sync replica-র ঝুঁকি কমাতে quorum ধাঁচ: ANY 1 (dhaka_b, ctg_c) মানে দুটির যেকোনো একটির ack যথেষ্ট। MySQL-এর semi-synchronous replication অন্তত একটি replica-র “পেয়েছি” (relay log-এ লেখা) পর্যন্ত অপেক্ষা করে, আর নির্দিষ্ট সময়ে (default ১০ সেকেন্ড) উত্তর না এলে async-এ নেমে যায় — অর্থাৎ চাপের মুহূর্তে নিঃশব্দে RPO শূন্য থাকে না।
Lag-এর দুই ফাঁদ — নিজের লেখা না দেখা, আর সময় পেছনে যাওয়া
async replica-তে পড়া পাঠালে আরেক সমস্যা: ব্যবহারকারী নিজের করা কাজই দেখতে পান না। Read-your-own-writes নিশ্চয়তা বলে — আপনি যা লিখেছেন, পরে পড়লে অন্তত তা দেখবেন। Monotonic reads বলে — একবার নতুন অবস্থা দেখলে পরে পুরোনোটি দেখবেন না। আগের এডিটরের তিন replica দিয়ে:
| পড়া/লেখা কোথায় | |
| Sabbir যা দেখলেন | |
| কোন নিশ্চয়তা ভাঙল |
- লেখা primary-তে। অর্ডার 1011 commit হলো lsn ৫০৩-এ।
- পড়া গেল পিছিয়ে থাকা replica-তে। ctg-dr এখনো ৫০২-এ — তার কাছে অর্ডারটি এখনো ঘটেনি। কয়েক সেকেন্ড পর এটি নিজেই দেখা যাবে: eventual consistency.
- যুক্তিসংগত ভুল। “অর্ডার যায়নি” ভেবে Sabbir আবার অর্ডার দিলেন। lag-এর ক্ষতি তাই আসল টাকা আর মজুদের।
- Monotonic reads ভাঙা। দুবার refresh-এ দুটি আলাদা replica: প্রথমে অর্ডার আছে, তারপর নেই। ব্যবহারকারীকে একটি replica-তে বেঁধে রাখলে (sticky session) এটি এড়ানো যায়।
- LSN মনে রাখা। লেখার পর session-এ commit-এর LSN রাখুন, আর শুধু সেই replica-তে পড়ুন যে অন্তত ততদূর প্রয়োগ করেছে (PostgreSQL-এ
pg_, MySQL-এ GTID). সরল বিকল্প: লেখার পর কয়েক সেকেন্ড সেই ব্যবহারকারীর সব পড়া primary-তে।last_ wal_ replay_ lsn()
শেষ ধাপের নিয়মটি SQL-এ একটি CROSS JOIN আর CASE — প্রতিটি session আর প্রতিটি async replica-র জোড়ায় সিদ্ধান্ত (customer 1 কখনো লেখেননি, তাই তাঁর last_write_lsn NULL):
SELECT s.customer_id, r.name, r.replayed_lsn, s.last_write_lsn,
CASE WHEN r.replayed_lsn >= COALESCE(s.last_write_lsn, 0)
THEN 'ok: read here'
ELSE 'stale: go to primary' END AS route
FROM sessions s
CROSS JOIN replicas r
WHERE r.mode = 'async'
ORDER BY s.customer_id, r.name;ছয় সারি। customer 1: দুই replica-ই ok: read here. customer 3 (lsn ৫০৩): ctg-dr stale: go to primary, dhaka-read ok. customer 9 (lsn ৫০৭): দুটিই stale — তাঁর পড়া যাবে primary-তে (বা sync replica-তে)। COALESCE (পর্ব ১৬) ছাড়া customer 1-এর তুলনা হতো 506 >= NULL, যার ফল NULL — CASE সেটিকে মিথ্যা ধরে ELSE-এ পাঠাত, আর যে কখনো লেখেনি তাকেও অকারণে primary-তে পাঠাত।
এবার পুরো ছবি একটি ছোট simulation-এ: দুটি আলাদা SQLite ডেটাবেস primary আর replica, মাঝে log পাঠানো। async replica log পায় দুটি commit-এর একটি দলে (দেরির অনুকরণ); তৃতীয় commit-এর পর primary মারা যায় আর replica-কে promote করা হয়। ফল নির্দিষ্ট — কোনো এলোমেলো সংখ্যা নেই:
import sqlite3
def scenario(mode, rtt_ms=8):
primary, replica = sqlite3.connect(":memory:"), sqlite3.connect(":memory:")
for db in (primary, replica):
db.execute("CREATE TABLE payments (id INTEGER PRIMARY KEY, amount INTEGER)")
log, shipped, latency = [], 0, 0
for pid, amount in [(1, 500), (2, 1200), (3, 300)]:
primary.execute("INSERT INTO payments VALUES (?, ?)", (pid, amount))
primary.commit(); log.append((pid, amount)); latency += 1 # 1 ms local WAL flush
if mode == "sync" or len(log) % 2 == 0: # async: log reaches replica in batches of 2
replica.executemany("INSERT INTO payments VALUES (?, ?)", log[shipped:])
replica.commit(); shipped = len(log)
latency += rtt_ms if mode == "sync" else 0 # sync waits for the replica's reply
print(f"{mode:5} commit {pid} -> OK to client | replica has {shipped}/{len(log)}")
seen = replica.execute("SELECT COUNT(*) FROM payments WHERE id = 3").fetchone()[0]
print(f"{mode:5} read payment 3 from replica: {'found' if seen else 'NOT FOUND (lag)'}")
primary.close() # primary dies; promote the replica
n, total = replica.execute("SELECT COUNT(*), SUM(amount) FROM payments").fetchone()
print(f"{mode:5} after failover: {n} of 3 payments, total {total}, latency {latency} ms\n")
for m in ("async", "sync"):
scenario(m)আউটপুট:
async commit 1 -> OK to client | replica has 0/1 async commit 2 -> OK to client | replica has 2/2 async commit 3 -> OK to client | replica has 2/3 async read payment 3 from replica: NOT FOUND (lag) async after failover: 2 of 3 payments, total 1700, latency 3 ms sync commit 1 -> OK to client | replica has 1/1 sync commit 2 -> OK to client | replica has 2/2 sync commit 3 -> OK to client | replica has 3/3 sync read payment 3 from replica: found sync after failover: 3 of 3 payments, total 2000, latency 27 ms
async-এ তিনটি commit-ই অ্যাপকে “OK” বলেছে, অথচ replica-র কাছে তৃতীয়টি নেই: নিজের payment 3 replica থেকে পড়া যায় না (lag), আর failover-এর পর ৩০০ টাকার লেনদেনটি চিরতরে হারায় (মোট ১৭০০, হওয়া উচিত ২০০০)। sync-এ কিছুই হারায় না, কিন্তু মোট অপেক্ষা ৩ ms থেকে ২৭ ms. প্রোগ্রামটি সারির মান পাঠায় — logical replication-এর খেলনা রূপ; streaming replication পাঠায় WAL-এর বাইট।
Multi-primary, সংক্ষেপে। একাধিক নোডে একসাথে লেখা নিলে (multi-primary) একই সারি দুই জায়গায় একসাথে বদলালে কোনটি টিকবে? “শেষ লেখক জেতে” নিঃশব্দে একটি লেখা ফেলে দেয়; commit-এর আগে দ্বন্দ্ব যাচাই (MySQL Group Replication বা Galera-র ধাঁচ) একটিকে বাতিল করে। তাই বেশিরভাগ ব্যবস্থা একটি primary-তেই লেখে, আর লেখার চাপ বেশি হলে ডেটা ভাগ করে (sharding, পর্ব ৫৪)।
৮ · Failover, split-brain আর consensus
primary মরলে একটি replica-কে primary বানিয়ে (promote) অ্যাপকে তার দিকে ঘোরানো — failover. পরিকল্পিত, শান্ত অবস্থায় একই কাজ (যেমন সার্ভার আপডেটের জন্য) হলো switchover — এতে আগে সব log পাঠিয়ে নেওয়া যায়, তাই কিছু হারায় না। হাতে (manual) failover-এ মানুষ বুঝে সিদ্ধান্ত নেন, কিন্তু রাত তিনটায় কাউকে জাগিয়ে কমান্ড চালাতে মিনিট থেকে ঘণ্টা লাগে। স্বয়ংক্রিয় failover সেকেন্ডে কাজ করে, কিন্তু কঠিন প্রশ্নের মুখে পড়ে: primary কি সত্যিই মরেছে, নাকি শুধু আমি তাকে দেখতে পাচ্ছি না?
| কে লেখা নিচ্ছে | |
| leader key | |
| ঝুঁকি |
- স্বাভাবিক অবস্থা। Patroni-ধাঁচের ব্যবস্থায় (PostgreSQL) নেতৃত্ব একটি “leader key”, যা থাকে etcd নামের তিন সদস্যের consensus সেবায়। key-এর মেয়াদ ৩০ সেকেন্ড, A প্রতি ১০ সেকেন্ডে নবায়ন করে। যার হাতে key, শুধু সে-ই primary.
- নেটওয়ার্ক ভাগ। A চালু, কিন্তু বাকি সবার থেকে বিচ্ছিন্ন। বাইরে থেকে “মৃত” আর “বিচ্ছিন্ন” আলাদা করা যায় না — এটিই failover-এর মূল সমস্যা।
- ভুল পথ — split-brain. একটি সরল script শুধু ping দেখে B-কে promote করল, অথচ A-ও নিজেকে primary ভাবছে। দুই দিকে লেখা চলছে, একই order_id দুজনকে দেওয়া হলো। জোড়া লাগার পর দুই ইতিহাস মেলানো প্রায়ই হাতের কাজ। এই অবস্থার নাম split-brain.
- সঠিক পথ — fencing. etcd-এর majority নাগালে নেই, তাই A key নবায়ন করতে পারে না; মেয়াদ শেষে নিজেই লেখা বন্ধ করে (demote). আটকে গিয়ে থামতে না পারলে watchdog তাকে রিবুট করে। পুরোনো primary-কে নিশ্চিতভাবে থামানোর এই কৌশল fencing.
- Majority-র সিদ্ধান্ত। majority-র দিকের B পায় key — দুই replica-র মধ্যে যার WAL বেশি এগিয়ে, যাতে কম হারায়। C এখন B-কে follow করে; অ্যাপ নতুন primary খুঁজে পায় proxy, ভার্চুয়াল IP বা DNS দিয়ে। এই কয়েক সেকেন্ড থেকে এক-আধ মিনিটই এই ব্যবস্থার RTO.
- ফিরে আসা। A আর primary নয়। ভাগের সময় A যা লিখেছিল, তা তার WAL-এ আলাদা একটি শাখা — pg_rewind A-কে B-এর ইতিহাসে ফিরিয়ে replica বানায়। ওই বিচ্ছিন্ন লেখাগুলো দরকার হলে পুরোনো log থেকে খুঁজে হাতে মেলাতে হয়।
এখানে মূল যন্ত্র quorum: n সদস্যের দলে সিদ্ধান্তে লাগে ⌊n/২⌋ + ১ জনের সম্মতি। দুটি majority সবসময় অন্তত একজন সদস্যে মেলে, তাই একই সময়ে দুই দিকে দুটি “বৈধ” সিদ্ধান্ত হতে পারে না। ৩ সদস্য ১টি, ৫ সদস্য ২টি ব্যর্থতা সইতে পারে; আর ৪ সদস্য ৩-এর চেয়ে বেশি কিছু সয় না (majority ৩), তাই সংখ্যাটি সাধারণত বিজোড়। etcd এর জন্য Raft নামের consensus অ্যালগরিদম ব্যবহার করে। প্রচলিত টুল: PostgreSQL-এ Patroni (etcd, Consul, ZooKeeper বা Kubernetes-এর ওপর); MySQL-এ orchestrator বা InnoDB Cluster; আর ক্লাউডের managed database-এ (পর্ব ৬১) প্রদানকারী নিজেই standby আর failover চালায় — তবে কতটা হারাতে পারে আর কতক্ষণ লাগে, ডকুমেন্টেশনে পড়ে নেওয়া আপনার দায়িত্ব।
Read replica-য় পড়া পাঠানো
Read replica-র সুবিধা পেতে কোয়েরিকে ঠিক জায়গায় পাঠাতে হয়। পথ তিনটি: অ্যাপে দুটি সংযোগ (লেখায় primary, পড়ায় replica); ProxySQL (MySQL) বা HAProxy, Pgpool-II-এর মতো proxy; আর ড্রাইভারের সুবিধা — PostgreSQL-এর libpq একাধিক host থেকে target_ দিয়ে লেখা-যোগ্য সার্ভারটি খুঁজে নেয়। নিয়ম: transaction-এর ভেতরের আর লেখার পরপর একই ব্যবহারকারীর পড়া primary-তে; রিপোর্টের মতো কিছুটা পুরোনো ডেটা চলে এমন পড়া replica-তে।
# প্রথমে db1, না পেলে db2 — যেটি লেখা নিতে পারে (primary) সেটিতে যুক্ত হও
postgresql://app@db1:5432,db2:5432/shop?target_session_attrs=read-write| High availability (HA) | Disaster recovery (DR) | |
|---|---|---|
| কী থেকে রক্ষা | একটি সার্ভার, ডিস্ক বা র্যাকের মৃত্যু | পুরো ডেটা সেন্টার বা শহরের বিপর্যয়, ভুল কমান্ড, ransomware |
| সাধারণ ব্যবস্থা | একই এলাকায় sync বা দ্রুত replica + স্বয়ংক্রিয় failover | অন্য শহরে async replica, অন্য জায়গায় backup আর WAL archive |
| সাধারণ RPO/RTO | প্রায় শূন্য / সেকেন্ড থেকে মিনিট | মিনিট থেকে ঘণ্টা / ঘণ্টা |
| ভুল DELETE? | বাঁচায় না — সাথে সাথে কপি হয় | backup + PITR বাঁচায় |
৯ · Availability মাপা — “কয়টি nine”
Availability = সেবা চালু থাকার সময় ÷ মোট সময়। চুক্তিতে (SLA) প্রায়ই লেখা থাকে “৯৯.৯%” বা “তিনটি nine”. শুনতে কাছাকাছি, কিন্তু প্রতিটি বাড়তি nine অনুমোদিত বন্ধ-সময় দশ ভাগের এক ভাগে নামায়। এক বছর = ৩৬৫ × ২৪ = ৮,৭৬০ ঘণ্টা; ৯৯.৯% মানে ০.১% বন্ধ = ৮.৭৬ ঘণ্টা। পুরো সারণি SQL-এ (এটি নমুনা ডেটাবেস ছাড়াই চলে):
WITH targets(label, a) AS (
VALUES ('99%', 0.99), ('99.9%', 0.999), ('99.99%', 0.9999), ('99.999%', 0.99999))
SELECT label,
ROUND((1 - a) * 365 * 24, 2) AS hours_per_year,
ROUND((1 - a) * 365 * 24 * 60, 1) AS minutes_per_year,
ROUND((1 - a) * 30 * 24 * 60, 1) AS minutes_per_30_days
FROM targets;
SELECT ROUND(0.999 * 0.999 * 100, 3) AS app_then_db_percent,
ROUND((1 - (1 - 0.999) * (1 - 0.999)) * 100, 4) AS two_db_copies_percent;প্রথম ফল: 99% | 87.6 | 5256 | 432, 99.9% | 8.76 | 525.6 | 43.2, 99.99% | 0.88 | 52.6 | 4.3, 99.999% | 0.09 | 5.3 | 0.4. ৯৯% মানে বছরে সাড়ে তিন দিনের বেশি বন্ধ; ৯৯.৯% মানে মাসে ৪৩ মিনিট; পাঁচটি nine মানে বছরে ৫ মিনিটের একটু বেশি — একটি সার্ভার রিবুটের সময়! দ্বিতীয় ফলে দুটি নিয়ম। পরপর নির্ভরতায় গুণ: অ্যাপ (৯৯.৯%) আর ডেটাবেস (৯৯.৯%) দুটিই লাগলে মোট 99.8 — প্রতিটির চেয়ে খারাপ। সমান্তরাল কপিতে ব্যর্থতার গুণ: যেকোনো একটি কপি যথেষ্ট হলে দুটিই বন্ধ থাকার সম্ভাবনা ০.০০১ × ০.০০১, তাই 99.9999 — তবে শুধু যদি দুটির ব্যর্থতা স্বাধীন হয়। একই বিদ্যুৎ লাইন, একই ভুল কনফিগ বা একই bug দুটিকে একসাথে ফেললে হিসাব ভাঙে।
আরেকটি দৃষ্টিভঙ্গি: availability = MTBF ÷ (MTBF + MTTR) — গড়ে কতদিন পরপর ব্যর্থতা, আর সারাতে গড়ে কত সময়। ব্যর্থতা কমানো কঠিন, কিন্তু MTTR কমানো প্রায়ই সহজ: স্বয়ংক্রিয় failover, অনুশীলন করা runbook, মহড়া দেওয়া restore. বছরে চারবার ব্যর্থ হয়ে প্রতিবার ২ মিনিটে ফেরা (মোট ৮ মিনিট) বছরে একবার ৬ ঘণ্টা বন্ধ থাকার চেয়ে অনেক ভালো।
আরও গভীরে: PostgreSQL-এর synchronous replication নিজে consensus নয় — কে primary, তা ঠিক করে বাইরের Patroni/etcd, আর দুই অংশের সমন্বয়ে ফাঁক থাকতে পারে। Google Spanner (Paxos) বা CockroachDB, YugabyteDB, TiDB-র মতো distributed SQL ডেটাবেস (Raft) প্রতিটি ডেটা-খণ্ডের লেখা commit করে majority replica-র সম্মতিতে, তাই leader বাছাই আর durability একই প্রোটোকলের অংশ — RPO শূন্য আর স্বয়ংক্রিয় failover, কিন্তু প্রতিটি commit-এ majority-র round trip. পর্ব ৫৪-এর CAP আলোচনায় দেখবেন, নেটওয়ার্ক ভাগ হলে এরা সংখ্যালঘু দিকের লেখা থামিয়ে consistency বেছে নেয়।
১০ · AI-এর যুগে backup আর replication-এর পরিকল্পনা
AI মিনিটে একটি backup script বা replication-এর কনফিগ লিখে দেয়। সমস্যা হলো, সেগুলো প্রায়ই চলে — আর ভুলটি ধরা পড়ে বিপদের দিনে। সাধারণ ভুল:
| AI-এর ভুল | ফল | কীভাবে ধরবেন |
|---|---|---|
| “দুটি replica আছে, আলাদা backup লাগবে না” | ভুল DELETE বা ransomware-এ সব কপি একসাথে নষ্ট | প্রশ্ন করুন: গতকালের অবস্থায় কীভাবে ফিরব? |
পুরোনো সংস্করণের নাম (recovery.conf, যা PostgreSQL ১২-তে উঠে গেছে; MySQL-এর পুরোনো MASTER কমান্ড) | কনফিগ উপেক্ষিত বা সার্ভার চালু হয় না | নিজের সংস্করণের ডকুমেন্টেশনে প্রতিটি প্যারামিটার মিলান |
| ২ TB ডেটাবেসে শুধু রাতের pg_dump | RPO ২৪ ঘণ্টা, restore-এ RTO-র বহু গুণ সময় | restore-এর সময় মেপে RTO-র সাথে তুলনা |
চলমান SQLite ফাইল cp করা | অসংগত কপি, -wal-এর ডেটা বাদ | .backup বা VACUUM INTO, তারপর integrity_check |
| ping-ভিত্তিক স্বয়ংক্রিয় failover script | split-brain | quorum আর fencing কোথায়, জিজ্ঞেস করুন |
AI-কে প্রযুক্তি নয়, লক্ষ্য আর সীমাবদ্ধতা দিন — আর যাচাইয়ের পরিকল্পনাও চান:
AI-এর পরিকল্পনা মানার আগে — চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- RPO আর RTO-র পার্থক্য? — RPO: সর্বোচ্চ কত সময়ের ডেটা হারানো চলে; RTO: সেবা ফিরতে সর্বোচ্চ কত সময় লাগতে পারে।
- Incremental আর differential backup-এর পার্থক্য? — incremental শেষ যেকোনো backup-এর পরের বদল (restore-এ পুরো শিকল), differential শেষ full-এর পরের বদল (restore-এ full + শেষটি)।
- Replication কি backup-এর বিকল্প? — না; replication ভুল কমান্ডও কপি করে, অতীতে ফেরা যায় শুধু backup + log দিয়ে।
- Synchronous আর asynchronous replication-এর দাম কী? — sync: RPO শূন্য কিন্তু প্রতিটি commit-এ round trip আর replica বন্ধ হলে commit আটকে যায়; async: দ্রুত, কিন্তু lag আর failover-এ ডেটা হারানোর ঝুঁকি।
- Split-brain কী, কীভাবে ঠেকায়? — দুই নোড একসাথে primary; majority quorum (consensus) দিয়ে নেতা বাছাই আর fencing দিয়ে পুরোনো primary-কে থামানো।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১GitLab-এর ২০১৭ সালের ঘটনায় পাঁচ রকম backup ও replication থাকা সত্ত্বেও কেন প্রায় ছয় ঘণ্টার ডেটা হারাল? প্রতিষ্ঠানগুলো এ থেকে কী বদলায়?
GitLab-এর প্রকাশিত postmortem পড়লে মনে হয় দুর্ঘটনাটি একটি টাইপের ভুল, কিন্তু আসল কারণ ছিল কয়েক স্তরের নীরব ব্যর্থতা। প্রথম স্তর: replica পিছিয়ে পড়েছিল, আর তাকে আবার গড়ার কাজ চলছিল গভীর রাতে, ক্লান্ত অবস্থায়, দুটি প্রায় একই রকম দেখতে টার্মিনালে। কোন সার্ভারে আছেন তা স্পষ্ট না হলে ধ্বংসাত্মক কমান্ড ভুল জায়গায় যাওয়া সময়ের ব্যাপার মাত্র। দ্বিতীয় স্তর: নিয়মিত pg_dump ভুল সংস্করণের টুল দিয়ে চলছিল আর ব্যর্থ হচ্ছিল, অথচ কেউ জানতেন না — কারণ ব্যর্থতার ইমেইল পৌঁছাচ্ছিল না। backup ছিল কাগজে, বাস্তবে নয়।
তৃতীয় স্তর: replication ছিল, কিন্তু replication backup নয় — primary-র মুছে যাওয়া ঠেকানোর ক্ষমতা তার নেই, আর সেই মুহূর্তে replica নিজেই ভাঙা ছিল। শেষ পর্যন্ত বাঁচাল অন্য কাজে প্রায় ছয় ঘণ্টা আগে নেওয়া একটি snapshot — পরিকল্পনার ফল নয়, ভাগ্য। আর সেটি থেকে ফেরাতেও অনেক ঘণ্টা লাগল, কারণ এমন restore আগে কখনো মহড়া হয়নি।
শিল্পজুড়ে এর প্রভাব কয়েকটি অভ্যাসে। backup-এর সফলতা ও আকার monitoring-এ রাখা, আর ব্যর্থ হলে এমন চ্যানেলে সতর্কবার্তা যা মানুষ সত্যিই দেখে। নিয়মিত স্বয়ংক্রিয় restore — নতুন সার্ভারে ফিরিয়ে কোয়েরি চালিয়ে ফল মেলানো। প্রোডাকশন টার্মিনালের prompt আলাদা করা, ধ্বংসাত্মক কাজ চেকলিস্ট ধরে দুজন মিলে করা। আর postmortem-এ ব্যক্তিকে দোষ না দিয়ে ব্যবস্থার ফাঁক খোঁজা — GitLab-এর খোলামেলা প্রকাশ, এমনকি উদ্ধারকাজের সরাসরি সম্প্রচার, এই সংস্কৃতির পরিচিত উদাহরণ।
মূল উপলব্ধি: backup-এর মূল্য মাপা হয় restore দিয়ে। যা নিয়মিত restore করে যাচাই হয় না, তা আসলে একটি অনুমান — আর বিপদের দিনে অনুমান প্রায়ই ভুল প্রমাণিত হয়।
প্র ০২২০১৮ সালের অক্টোবরে GitHub-এ মাত্র ৪৩ সেকেন্ডের একটি নেটওয়ার্ক বিচ্ছিন্নতা প্রায় এক দিনের সমস্যায় পরিণত হয়েছিল। স্বয়ংক্রিয় failover কীভাবে এতে ভূমিকা রাখে?
GitHub-এর প্রকাশিত বিশ্লেষণ অনুযায়ী, ২১ অক্টোবর ২০১৮-তে নেটওয়ার্ক রক্ষণাবেক্ষণের সময় যুক্তরাষ্ট্রের পূর্ব উপকূলের একটি নেটওয়ার্ক হাব আর প্রধান ডেটা সেন্টারের মধ্যে সংযোগ প্রায় ৪৩ সেকেন্ড বিচ্ছিন্ন থাকে। তাদের MySQL ক্লাস্টারগুলো চালাত orchestrator নামের failover টুল, যা নিজে consensus (Raft) ব্যবহার করে সিদ্ধান্ত নেয়। বিচ্ছিন্নতার মধ্যে orchestrator-এর majority দেখল পূর্বের primary-গুলো নাগালের বাইরে, আর পশ্চিম উপকূলের ডেটা সেন্টারের replica-গুলোকে primary বানাল। টুলটি নিজের নিয়ম মেনেই কাজ করেছিল।
সমস্যা হলো, ওই কয়েক সেকেন্ডে পূর্বের primary-গুলো কিছু লেখা নিয়েছিল যা পশ্চিমে পৌঁছায়নি (async replication), আর পশ্চিমের নতুন primary-রাও নতুন লেখা নিতে শুরু করে। দুই জায়গায় দুই রকম ইতিহাস। তার ওপর অ্যাপগুলো এখন দেশের অন্য প্রান্তের ডেটাবেসে কথা বলছিল, যার latency-র জন্য তারা তৈরি ছিল না। দলটি সচেতনভাবে কোনো লেখা ফেলে না দেওয়ার সিদ্ধান্ত নিল: backup থেকে restore, replica আবার গড়া, আর দুই দিকের লেখা মিলিয়ে নেওয়া। প্রায় ২৪ ঘণ্টা সেবা আংশিক অচল ছিল।
শিক্ষাটি সূক্ষ্ম। consensus ছিল, তাই এটি split-brain-এর ক্লাসিক রূপ নয়। সমস্যা ছিল কোন failover কাম্য, তা টুলকে বলা হয়নি: অঞ্চল পেরোনো failover-এর দাম — latency আর async-এর হারানো লেখা — কয়েক সেকেন্ডের বিচ্ছিন্নতায় দেওয়ার মতো নয়। স্বয়ংক্রিয় failover-এর তাই একটি “কখন করব না” তালিকাও লাগে, আর ক্ষণস্থায়ী সমস্যা থেকে সত্যিকারের মৃত্যু আলাদা করতে কিছুটা অপেক্ষা যুক্তিসংগত।
মূল উপলব্ধি: failover নিজেই একটি ঝুঁকিপূর্ণ কাজ। দ্রুত সিদ্ধান্তে RTO কমে, কিন্তু ভুল সময়ের সিদ্ধান্তে RPO আর পুরো সিস্টেমের আচরণ দুটোই বদলে যায় — তাই কখন, কোথায়, কী শর্তে failover হবে, তা ব্যবসার লক্ষ্যের সাথে মিলিয়ে লিখে রাখতে হয়।
প্র ০৩“Replication backup নয়” — তবু অনেক প্রতিষ্ঠান ইচ্ছা করে কয়েক ঘণ্টা পিছিয়ে থাকা একটি replica রাখে। কেন? আর ransomware-এর যুগে backup-এর নকশা কীভাবে বদলেছে?
সাধারণ replica-র লক্ষ্য যত কম lag সম্ভব — তাই ভুল কমান্ড কয়েক সেকেন্ডে সেখানে পৌঁছায়। Delayed replica উল্টো কাজ করে: log পায় সঙ্গে সঙ্গে, কিন্তু প্রয়োগ করে নির্দিষ্ট সময় পরে। PostgreSQL-এ recovery_, MySQL-এ SOURCE_DELAY দিয়ে এটি করা যায়। দেরি চার ঘণ্টা হলে ৩:০৫-এর ভুল DELETE এখানে প্রয়োগ হবে ৭:০৫-এ। এর আগে ধরা পড়লে প্রয়োগ থামিয়ে ৩:০৪:৫৯ পর্যন্ত এগোনো যায় — base backup ফেরানো আর দীর্ঘ replay-এর চেয়ে অনেক দ্রুত: একটি “চালু থাকা PITR”.
তবে এটি backup-এর বদলি নয়। চার ঘণ্টার পরে ধরা পড়লে কাজে লাগে না, একই ডেটা সেন্টারে থাকলে আগুনে যায়, আর একই পাসওয়ার্ড বা নেটওয়ার্কে থাকলে আক্রমণকারী সেটিও নষ্ট করতে পারে।
শেষ কথাটিই ransomware-এর যুগের মূল পরিবর্তন। আক্রমণকারীরা প্রায়ই আগে backup খুঁজে মোছে বা encrypt করে, তারপর মূল ডেটা আক্রমণ করে — যাতে মুক্তিপণ ছাড়া পথ না থাকে। জবাবে তিনটি ধারণা। এক, immutable স্টোরেজ: object lock বা WORM (একবার লেখা, বহুবার পড়া), যেখানে মেয়াদের আগে admin-ও মুছতে পারে না। দুই, আলাদা পরিচয়: backup-এর অ্যাকাউন্ট মূল সিস্টেমের admin থেকে আলাদা, আলাদা multi-factor সুরক্ষায়। তিন, অফলাইন বা “air-gapped” কপি। আর নতুন প্রশ্ন: আক্রমণ কবে শুরু হয়েছিল জানা না থাকলে কোন backup “পরিষ্কার”? তাই দীর্ঘ retention-ও জরুরি।
মূল উপলব্ধি: প্রতিটি কপি একটি নির্দিষ্ট বিপদ থেকে রক্ষা করে — replica যন্ত্রের মৃত্যু থেকে, delayed replica দ্রুত ধরা পড়া ভুল থেকে, immutable অফসাইট backup আক্রমণ আর দুর্যোগ থেকে। নকশার কাজ হলো প্রতিটি বিপদের জন্য অন্তত একটি কপি নিশ্চিত করা।
প্র ০৪Synchronous replication-এ ডেটা হারায় না। তাহলে সবাই সব জায়গায় sync replication ব্যবহার করে না কেন? দূরত্ব, পদার্থবিজ্ঞান আর availability-র হিসাবটি কী?
প্রথম বাধা পদার্থবিজ্ঞান। কাচের তারে আলো চলে শূন্যস্থানের প্রায় দুই-তৃতীয়াংশ গতিতে — সেকেন্ডে মোটামুটি দুই লাখ কিলোমিটার, অর্থাৎ প্রতি মিলিসেকেন্ডে প্রায় ২০০ কিলোমিটার। তার সোজা পথে যায় না, আর রাউটার-সুইচ সময় যোগ করে। একই শহরে round trip এক মিলিসেকেন্ডের কাছাকাছি হতে পারে; দেশের দুই প্রান্তে কয়েক মিলিসেকেন্ড; মহাদেশ পেরোলে কয়েক দশ থেকে শতাধিক। sync-এ প্রতিটি commit-এ অন্তত একটি round trip যোগ হয়: যে অ্যাপ একটি অনুরোধে পরপর দশটি transaction করে, ৫০ ms-এর round trip-এ তার অর্ধেক সেকেন্ড বাড়তি লাগে। একটি সংযোগ পরপর commit করলে সেকেন্ডে সর্বোচ্চ ১ ÷ RTT-সংখ্যক commit পারে; group commit (পর্ব ৪৮) মোট throughput বাঁচায়, কিন্তু প্রতিটির latency কমায় না।
দ্বিতীয় বাধা availability-র উল্টো টান। একটিমাত্র sync replica থাকলে সেটি বা তার নেটওয়ার্ক বন্ধ হলেই primary-র সব commit আটকে যায় — দুটি যন্ত্রের যেকোনো একটির ব্যর্থতায় লেখা বন্ধ, অর্থাৎ availability কমল। সমাধান হিসেবে quorum: তিনটি কপির যেকোনো দুটি (PostgreSQL-এ ANY ধাঁচ), যাতে একটি ধীর বা মৃত replica সবাইকে আটকাতে না পারে। MySQL-এর semi-sync উল্টো আপস করে: নির্দিষ্ট সময়ে ack না এলে async-এ নেমে যায় — availability বাঁচে, কিন্তু সেই মুহূর্তে RPO শূন্যের প্রতিশ্রুতি চুপচাপ ভাঙে।
তাই প্রচলিত নকশা দুই স্তরের: একই অঞ্চলে (কাছের দুটি ভবন বা ক্লাউডের দুটি zone) sync বা quorum replica — কম latency, প্রায় শূন্য RPO, দ্রুত failover — আর দূরের শহরে async replica আর backup, শহরজুড়ে বিপর্যয়ের জন্য। কোন লেনদেন কতটা নিশ্চয়তা চায়, সেটিও আলাদা করা যায়: PostgreSQL-এ synchronous_commit প্রতিটি transaction-এ বদলানো যায়।
মূল উপলব্ধি: আলোর গতি কোনো কনফিগ দিয়ে বাড়ানো যায় না। দূরত্ব যত, sync-এর দাম তত — তাই সব লেখা নয়, শুধু যেগুলোর হারানো অসহনীয়, সেগুলোর জন্য কাছের sync কপি, আর বাকি নিরাপত্তা আসে দূরের async কপি আর backup থেকে।
প্র ০৫একটি অনলাইন দোকানের ডেটাবেস ৫ GB থেকে বেড়ে ২ TB হয়েছে। শুরুর রাতের pg_dump এখন কেন যথেষ্ট নয়? তবু logical dump কোন কাজে অপরিহার্য থেকে যায়?
৫ GB-তে pg_dump কয়েক মিনিটে শেষ হয়, আর restore-ও প্রায় তেমনই। ২ TB-তে ছবিটা বদলে যায়, আর বদলায় মূলত restore-এর দিকে। dump ফেরানো মানে প্রতিটি সারি আবার COPY করা, তারপর প্রতিটি index শূন্য থেকে গড়া — পুরো টেবিল sort করে (পর্ব ৪২-এর external sort) — আর প্রতিটি foreign key আবার যাচাই। বড় টেবিলে শুধু index গড়াতেই ঘণ্টা যায়; pg_restore -j সাহায্য করে, কিন্তু সবচেয়ে বড় টেবিলটি একাই সীমা ঠিক করে। RTO এক ঘণ্টা হলে dump থেকে তা পূরণ অসম্ভব হতে পারে। তার ওপর রাতের dump-এর RPO ২৪ ঘণ্টা, আর লম্বা dump দীর্ঘ snapshot ধরে রাখে বলে পুরোনো সংস্করণ জমে (পর্ব ৪৭-এর vacuum বাধা পায়)।
physical backup এই সমস্যাগুলো এড়ায়: data ফাইল হুবহু কপি, index-সহ — restore মানে ফাইল কপি আর WAL replay, আর WAL archive থাকলে RPO মিনিটে। incremental backup প্রতিদিনের কপির আকার কমায়। তাই বড় ডেটাবেসে প্রধান ভরসা base backup + WAL archive.
তবু logical dump মরে না, কারণ তার কিছু কাজ physical পারে না। এক, major সংস্করণ বদল বা অন্য প্ল্যাটফর্মে যাওয়া: physical কপি একই major সংস্করণেই চলে (PostgreSQL-এ pg_upgrade বা logical replication বিকল্প পথ)। দুই, একটি টেবিল বা schema বাছাই করে ফেরানো — ভুল করে মোছা একটি ছোট টেবিল পুরো cluster PITR না করে ফেরানো। তিন, ডেভেলপার বা পরীক্ষার পরিবেশে ডেটার কপি দেওয়া (দরকারে ব্যক্তিগত তথ্য লুকিয়ে)। চার, physical কপিতে নীরবে লুকিয়ে থাকা data-page-এর ক্ষতি ধরা: dump প্রতিটি সারি পড়ে, তাই নষ্ট page-এ সাধারণত ত্রুটি দেয়।
মূল উপলব্ধি: backup-এর ধরন বাছাই হয় RPO, RTO আর ডেটার আকার দেখে, অভ্যাস দেখে নয়। বড় ডেটাবেসে physical + WAL প্রধান ভিত্তি, আর logical dump একটি বাড়তি, বহনযোগ্য, বাছাই-যোগ্য স্তর — দুটি একসাথে।
অনুশীলন
একটি কোয়েরি লেখা, একটি হাতে trace, একটি হিসাব, একটি ফল অনুমান।
-
কোয়েরি লিখুন: অংশ ৬-এর
replicasআরreplication_logটেবিল থেকে একটি সতর্কবার্তার তালিকা বানান: যেসব replica-র সময়ের lag ১০ সেকেন্ডের বেশি, তাদের নাম, city আর lag (সেকেন্ডে)। (অংশ ৬-এর যেকোনো এডিটরে লিখে চালাতে পারেন।)SELECT r.name, r.city, CAST(ROUND((julianday((SELECT MAX(committed_at) FROM replication_log)) - julianday(l.committed_at)) * 86400) AS INTEGER) AS lag_seconds FROM replicas r JOIN replication_log l ON l.lsn = r.replayed_lsn WHERE (julianday((SELECT MAX(committed_at) FROM replication_log)) - julianday(l.committed_at)) * 86400 > 10;ফল এক সারি:
ctg-dr | Chattogram | 16. WHERE-এ aliaslag_secondsলেখা যায় না, কারণ WHERE চলে SELECT-এর আগে; তাই হিসাবটি আবার লেখা (বা CTE-তে রেখে বাইরে ফিল্টার)। replica এমন lsn-এ থাকলে যা log-এ নেই, JOIN সারিটি ফেলে দেবে — অথচ সেটিই সবচেয়ে বিপজ্জনক; বাস্তবে LEFT JOIN দিয়ে NULL-কেও সতর্কবার্তা ধরুন। -
হাতে trace করুন: রবিবার রাত ২টায় full backup, সোম থেকে বুধ প্রতি রাত ২টায় incremental (“বুধবার রাত ২টা” মানে মাঝরাতের পরে, বৃহস্পতিবার ভোর ২:০০); সাথে WAL archive, যা প্রতি ৫ মিনিটে একটি segment পাঠায়। বুধবারের incremental ব্যর্থ হয়েছে, কিন্তু কেউ খেয়াল করেনি। বৃহস্পতিবার সকাল ১০:০০-এ ডিস্ক নষ্ট। (ক) WAL archive না থাকলে কোন backup-গুলো লাগবে আর কতটা হারাবে? (খ) WAL archive থাকলে?
(ক) শিকল: রবি full + সোম + মঙ্গল incremental; বুধবারেরটি নেই। ফেরা যাবে মঙ্গলবার রাত ২টার (বুধবার ভোর ২:০০-এর) অবস্থায় — হারাবে প্রায় ৩২ ঘণ্টা (বুধবার ভোর ২:০০ থেকে বৃহস্পতিবার সকাল ১০:০০)। ব্যর্থ backup নীরবে RPO বাড়িয়েছে। (খ) একই base (রবি + সোম + মঙ্গল), তারপর archived WAL replay করে বৃহস্পতিবার ৯:৫৫-এর কাছাকাছি পর্যন্ত। হারাবে শেষ archive না হওয়া segment — সর্বোচ্চ প্রায় ৫ মিনিট. তবে replay লম্বা (মঙ্গলবার রাতের backup থেকে), তাই RTO বাড়ে; আর একটি WAL segment হারালে তার পরে এগোনো যায় না — WAL archive-ও monitoring চায়।
-
RTO-র হিসাব: ৮০০ GB-র physical backup অন্য জায়গা থেকে ২০০ MB/s হারে নামানো আর বসানো যায়। তারপর ৩০ GB WAL replay হয় ৫০ MB/s হারে। যাচাই আর অ্যাপ ঘোরাতে আরও ১৫ মিনিট। (১ GB = ১০০০ MB ধরুন।) RTO ১ ঘণ্টা হলে লক্ষ্য পূরণ হয় কি? না হলে কী বদলাবেন?
কপি: ৮,০০,০০০ ÷ ২০০ = ৪,০০০ সেকেন্ড ≈ ৬৭ মিনিট. replay: ৩০,০০০ ÷ ৫০ = ৬০০ সেকেন্ড = ১০ মিনিট. মোট ৬৭ + ১০ + ১৫ ≈ ৯২ মিনিট — লক্ষ্য পূরণ হয় না, আর ধরা পড়ার সময় ধরাই হয়নি। উপায়: কাছের দ্রুত স্টোরেজে একটি কপি (নামানো কমে); আরও ঘন base backup (replay কমে); বা সারাক্ষণ চালু একটি replica — তখন হার্ডওয়্যার ব্যর্থতায় restore-ই লাগে না, failover সেকেন্ডে, আর restore থাকে ভুল কমান্ড ও দুর্যোগের জন্য।
-
ফল অনুমান করুন: ৫ সদস্যের একটি etcd দল, দুটি ডেটা সেন্টারে: ঢাকায় ৩, চট্টগ্রামে ২। primary ডেটাবেস চট্টগ্রামে। দুই শহরের নেটওয়ার্ক কেটে গেল। (ক) কোন দিক নতুন নেতা বাছতে পারবে, আর চট্টগ্রামের primary-র কী হওয়া উচিত? (খ) যদি ঢাকায় ২, চট্টগ্রামে ২ আর তৃতীয় একটি শহরে ১ সদস্য থাকে, আর শুধু ঢাকা বিচ্ছিন্ন হয়?
(ক) majority = ⌊৫/২⌋ + ১ = ৩. ঢাকার দিকে ৩ জন, তাই শুধু ঢাকা নেতা বাছতে পারবে, আর একটি ঢাকার replica primary হবে। চট্টগ্রামের ২ জন majority পায় না, তাই সেখানকার primary key নবায়ন করতে না পেরে মেয়াদ শেষে নিজেই লেখা বন্ধ করবে (fencing) — consistency-র জন্য availability ছাড়া হলো। (খ) চট্টগ্রাম ২ + তৃতীয় শহর ১ = ৩, majority — primary চট্টগ্রামেই থাকে, failover-ই লাগে না। শুধু দুই ডেটা সেন্টারে সদস্য রাখলে বড় দিকটি হারালে majority থাকে না; তৃতীয় জায়গার একটি ছোট “সাক্ষী” সদস্য এই সমস্যা মেটায়।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমভুল DELETE-এর পর PostgreSQL-এ point-in-time recovery-র কাজগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- log দেখে ভুল কমান্ডটির সঠিক সময় (বা transaction) বের করা
- আলাদা একটি সার্ভারে base backup-এর ফাইল বসানো
- restore_command, recovery_target_time আর recovery.signal ঠিক করে সার্ভার চালু করা
- archived WAL replay হয়ে লক্ষ্যের ঠিক আগে থামা
- ফল যাচাই করে হারানো সারি লাইভ সার্ভারে ফেরানো
একটি অনলাইন দোকান বলছে: “বিপর্যয়ে সর্বোচ্চ ১৫ মিনিটের অর্ডার হারানো মেনে নিতে পারি, আর সাইট ২ ঘণ্টার মধ্যে আবার চালু হতে হবে।” এখানে RPO আর RTO কত?
- RPO ১৫ মিনিট, RTO ২ ঘণ্টা
- RPO ২ ঘণ্টা, RTO ১৫ মিনিট
- দুটিই ১৫ মিনিট
- backup-এর ধরন না জেনে বলা যায় না
রবিবার full backup, তারপর সোম, মঙ্গল আর বুধবার রাতে incremental backup নেওয়া হয়েছে। বৃহস্পতিবার সকালে বুধবার রাতের অবস্থায় restore করতে কোনগুলো লাগবে?
- শুধু বুধবারের incremental
- রবিবারের full আর বুধবারের incremental
- রবিবারের full, তারপর সোম, মঙ্গল আর বুধবারের incremental — ক্রমানুসারে
- যেকোনো ক্রমে চারটি ফাইল
একজন ভুল করে primary সার্ভারে DROP TABLE orders চালালেন। ডেটাবেসের দুটি streaming replica আছে, lag কয়েক সেকেন্ড। সবচেয়ে সঠিক কথা কোনটি?
- replica-তে টেবিলটি নিরাপদে থেকে যাবে, সেখান থেকে ফেরানো যাবে
- কয়েক সেকেন্ডে replica-তেও টেবিল মুছে যাবে; ফেরাতে backup আর WAL দিয়ে PITR লাগবে
- primary রিস্টার্ট করলে crash recovery টেবিল ফিরিয়ে আনবে
- যেকোনো সময় ROLLBACK দিলেই টেবিল ফিরে আসবে
asynchronous replication-এ primary হঠাৎ নষ্ট হলো, আর replica-কে নতুন primary বানানো হলো। কী ঘটতে পারে?
- কিছুই হারাবে না, কারণ commit সবসময় replica-তে আগে যায়
- replica-র ডেটাবেস অসংগত হয়ে পড়বে আর চালু হবে না
- শুধু অসমাপ্ত transaction হারাবে, সফল বলে জানানো সব commit থাকবে
- অ্যাপকে “সফল” জানানো শেষ কয়েকটি commit হারাতে পারে
তিন সদস্যের একটি consensus দল (যেমন etcd) কে primary তা ঠিক করে। নেটওয়ার্ক ভাগ হয়ে primary A একা পড়ল, বাকি দুই সদস্য আর replica B অন্য দিকে। split-brain ঠেকাতে কোনটি সঠিক আচরণ?
- A লেখা নিতে থাকবে, কারণ সে তো চালু আছে
- B-কে promote করতে ping না পাওয়াই যথেষ্ট, A-র কী হলো দেখার দরকার নেই
- A leader key নবায়ন করতে না পেরে লেখা বন্ধ করবে, আর majority-র দিক B-কে primary বানাবে
- A আর B দুজনেই লেখা নেবে, পরে স্বয়ংক্রিয়ভাবে মিলে যাবে
একটি সেবার availability লক্ষ্য বছরে ৯৯.৯%. বছরে (৩৬৫ দিন) সর্বোচ্চ কতটা সময় বন্ধ থাকা চলে?
- প্রায় ৫২.৬ মিনিট
- প্রায় ৮.৭৬ ঘণ্টা
- প্রায় ৮৭.৬ ঘণ্টা
- প্রায় ৫.৩ মিনিট
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Replication ≠ backup: replication যন্ত্রের মৃত্যু থেকে বাঁচায়, ভুল কমান্ড আর ransomware কপি করে। অতীতে ফেরা যায় শুধু অন্য জায়গার backup আর archived log দিয়ে।
- RPO আর RTO আগে: হারানো ডেটার সীমা আর বন্ধ থাকার সীমা। সফল backup-এর মাঝের ফাঁকই আসল RPO, আর মহড়ায় মাপা restore-এর সময়ই আসল RTO.
- ধরন: logical (বহনযোগ্য, বাছাই-যোগ্য, restore ধীর) বনাম physical (দ্রুত, PITR-যোগ্য); incremental-এ পুরো শিকল, differential-এ full + শেষটি; base backup + WAL archive = point-in-time recovery.
- 3-2-1, encryption, retention, restore test: একটি কপি অন্য জায়গায় আর মুছতে-না-পারা; চাবি আলাদা; নিয়মিত restore করে hash আর সারি মেলানো।
- Replication-এর দাম: sync = RPO শূন্য কিন্তু round trip আর replica-নির্ভরতা; async = দ্রুত কিন্তু lag, read-your-own-writes সমস্যা আর failover-এ হারানো। split-brain ঠেকায় quorum + fencing; ৯৯.৯% = বছরে ৮.৭৬ ঘণ্টা।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৩ · পারফরম্যান্স টিউনিং আর DBA-র প্রতিদিনের কাজপরবর্তী পর্বmonitoring, ধীর কোয়েরি আর রক্ষণাবেক্ষণ।
- পর্ব ৪৮ · Recovery — log, WAL, checkpoint আর ARIESফিরে দেখুনWAL, redo আর media recovery-র ভিত্তি।
- পর্ব ৫৪ · Distributed database — partitioning, sharding আর CAPসামনেনেটওয়ার্ক ভাগ, consistency আর availability-র তত্ত্ব।
- পর্ব ৬১ · Cloud database আর DBaaSসামনেmanaged backup, replica আর failover.
- ডেটাবেস (DBMS) ও SQL · কোর্স হোম৬৭টি পর্বপুরো কোর্সের রোডম্যাপ — ১০টি মডিউল।
- Python · SQLite ডেটাবেসসম্পর্কিত কোর্সPython থেকে sqlite3 ব্যবহার।
- কম্পিউটার ফান্ডামেন্টালস · সাইবার নিরাপত্তাসম্পর্কিত কোর্সransomware আর নিরাপদ অভ্যাস।