বিষয়বস্তুতে যান

Backup, নির্দিষ্ট সময়ে পুনরুদ্ধার ও restore drill

Quire backup করুন, নির্দিষ্ট সময়ে ফিরিয়ে আনুন এবং restore drill দিয়ে প্রমাণ করুন যে প্রক্রিয়া কাজ করে।

Markdown হিসেবে দেখুন

স্থাপত্যের বিবরণ docs/architecture/23-ops.md-এর section 8-এ। এটি Docker Compose product-এর runbook। যিনি এটি লেখেননি, তিনিও যেন অনুসরণ করতে পারেন এমনভাবে লেখা; কোনো ধাপ অস্পষ্ট হলে সেটি এই নথির ত্রুটি।

কী সুরক্ষিত এবং কীভাবে

বিষয় পদ্ধতি কোথায়
Database প্রথম boot থেকেই একটানা WAL archive হয়, সর্বোচ্চ প্রতি 60 সেকেন্ডে pgwal volume
Database pg_basebackup দিয়ে base backup, default-এ প্রতিদিন (backup-scheduler) pgbackup volume
Database প্রতি পাঁচ মিনিটে base backup ও WAL-এর encrypted copy (backup-offsite) আপনার নির্ধারিত আলাদা store
File files volume। Host-এর backup tool দিয়ে copy করুন অথবা version-সহ object storage ব্যবহার করুন files volume
Secret docker/.env, বিশেষত QUIRE_MASTER_KEY (এবং এখনও ব্যবহৃত QUIRE_MASTER_KEY_RETIRED), QUIRE_BACKUP_ENCRYPTION_KEY ও docker/secrets/audit-signing-key.pem এই host-এর বাইরে copy রাখুন
Search index, cache, rendition Backup হয় না; আবার তৈরি করা হয়

লক্ষ্য: failure-এর 60 সেকেন্ডের মধ্যে recovery point এবং 500 GB database 60 মিনিটের মধ্যে restore।

দুটি ভুল প্রায়ই হয়। File ছাড়া restore করা database-এ page ভাঙা দেখায়। QUIRE_MASTER_KEY ছাড়া restore করা database তার SSO, webhook ও integration credential decrypt করতে পারে না; কোনো unresolved সমস্যা ছাড়া master key rotation সম্পন্ন না হওয়া পর্যন্ত retired key-ও এর অন্তর্ভুক্ত (key-rotation.md)। দুটিই backup-এ থাকতে হবে।

Backup নেওয়া

সম্পূর্ণ cluster-এর base backup:

docker compose -f docker/compose.yaml --profile backup run --rm backup

এটি সর্বশেষ QUIRE_BACKUP_KEEPটি base backup রাখে (default 5) এবং সবচেয়ে পুরোনোটি আর যে WAL ব্যবহার করে না তা পরিষ্কার করে, যাতে archive সীমাহীনভাবে বড় না হয়। Host-এ cron অথবা systemd timer দিয়ে প্রতিদিনের schedule করুন:

15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1

অথবা stack-কে schedule করতে দিন: backup profile backup-scheduler চালায়, যা প্রতি QUIRE_BACKUP_INTERVAL_HOURS-এ (default 24) একটি base backup নেয়, এবং পরের অংশে বর্ণিত backup-offsite চালায়।

docker compose -f docker/compose.yaml --profile backup up -d

Host-এর বাইরে encrypted copy

দুটি volume-ই database-এর একই host-এ থাকে; যে machine নষ্ট হয়েছে, সেখানে থাকা backup backup নয়। backup-offsite storage port দিয়ে সব base backup ও archive করা প্রতিটি WAL segment আলাদা store-এ encrypted অবস্থায় copy করে এবং retention নিয়মে সেখানে রাখে:

  • Encryption। QUIRE_BACKUP_ENCRYPTION_KEY (অথবা QUIRE_BACKUP_ENCRYPTION_KEY_FILE দিয়ে নাম দেওয়া file) দিয়ে AES-256-GCM: openssl rand -hex 32 থেকে 32 byte। প্রতিটি file-এর নিজস্ব nonce ও authentication tag আছে; তাই key ছাড়া copy পড়া যায় না এবং কোনো পরিবর্তন হলে তা শনাক্ত হয়। QUIRE_MASTER_KEY-এর সঙ্গে key রাখুন, তবে এই host ও backup store—দুটির বাইরের জায়গায়। Key ছাড়া restore করা যায় না।
  • স্থান। QUIRE_BACKUP_STORAGE_DRIVER হলো s3, azure অথবা local (QUIRE_BACKUP_STORAGE_ROOT-এ mount করা remote disk)। File storage-এর setting-গুলোতেই QUIRE_BACKUP_ prefix যোগ হয়: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID ইত্যাদি। File-এর জন্য ব্যবহৃত bucket থেকে আলাদা bucket এবং সম্ভব হলে আলাদা account ব্যবহার করুন; provider অনুমতি দিলে credential-এ write থাকবে, delete থাকবে না।
  • Retention। সর্বশেষ QUIRE_BACKUP_OFFSITE_KEEPটি base backup (default QUIRE_BACKUP_KEEP, সেটি না থাকলে 7) এবং সবচেয়ে পুরোনোটির জন্য যত WAL দরকার; পুরোনো set ও segment store থেকে delete হয়।
  • সময়। প্রতি QUIRE_BACKUP_SHIP_INTERVAL_SECONDS-এ (default 300)। Shipping idempotent: store-এ যা আছে তা বাদ যায় এবং manifest লেখা হলে—সবশেষে—তবেই base backup stored বলে গণ্য হয়।

একই command হাতে চালানো যায়:

docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts ship
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify

নতুন host-এ restore করতে আগে একটি set ফিরিয়ে আনুন, তারপর pgbackup volume-এর বদলে আনা directory এবং wal-archive directory ব্যবহার করুন, যা pgwal-এর স্থলাভিষিক্ত হবে; এরপর নিচের ধাপগুলো অনুসরণ করুন:

bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore

নির্দিষ্ট সময়ে restore

Data loss-এর পরে এটি ব্যবহার করুন: ভুল import, মুছে ফেলা course অথবা ফিরিয়ে নিতে হবে এমন contract migration। এতে live database বদলে যায়, তাই আগে নিচের drill দিয়ে অনুশীলন করুন।

  1. লক্ষ্য সময় বেছে নিন, UTC-তে, ক্ষতির ঠিক আগের মুহূর্ত: 2026-09-24 09:30:00+00। Audit log (/admin/audit)-এ সাধারণত সেই সময় দেখা যায়।
  2. যা write করে সব বন্ধ করুন: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Restore যাচাই না হওয়া পর্যন্ত ক্ষতিগ্রস্ত cluster রাখুন:
    docker compose -f docker/compose.yaml stop postgres
    docker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
  4. লক্ষ্য সময়ের আগের সর্বশেষ base backup data volume-এ unpack করে নির্দিষ্ট সময়ে recovery চালু করুন:
    docker run --rm -v quire_pgbackup:/backups:ro -v quire_postgres18-data:/var/lib/postgresql postgres:18-alpine sh -euc '
      base="$(ls -1d /backups/base-* | sort | tail -n 1)"   # or the one before the target
      rm -rf /var/lib/postgresql/18/docker && mkdir -p /var/lib/postgresql/18/docker
      tar -xzf "$base/base.tar.gz" -C /var/lib/postgresql/18/docker
      touch /var/lib/postgresql/18/docker/recovery.signal
      chown -R postgres:postgres /var/lib/postgresql/18/docker && chmod 700 /var/lib/postgresql/18/docker'
  5. Recovery করুন: স্বাভাবিক file না বদলে Compose override থেকে recovery setting-সহ Postgres একবার চালু করুন:
    # docker/compose.recover.yaml
    services:
      postgres:
        command: [postgres, -c, "restore_command=cp /var/lib/postgresql/wal-archive/%f %p",
                  -c, "recovery_target_time=2026-09-24 09:30:00+00",
                  -c, recovery_target_action=promote, -c, archive_mode=off,
                  -c, max_connections=200, -c, hba_file=/etc/postgresql/pg_hba.conf]
    Override সম্পূর্ণ command-টি বদলে দেয়, তাই recovery-র জন্য দরকারি দুটি setting-ও এতে দিতে হয়: max_connections primary-র চেয়ে কম নয় (নইলে “insufficient parameter settings” error-এ recovery বন্ধ হবে) এবং mount করা pg_hba.conf।
    docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgres
    docker compose -f docker/compose.yaml logs -f postgres   # wait for "database system is ready"
  6. প্রবেশাধিকার দেওয়ার আগে পরীক্ষা করুন: audit chain (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) এবং হারানো data ফিরে এসেছে কি না।
  7. স্বাভাবিক অবস্থায় ফিরুন: docker compose -f docker/compose.yaml up -d। এতে archiving চালু করে Postgres restart হয় এবং নতুন WAL timeline শুরু হয়। সঙ্গে সঙ্গেই নতুন base backup নিন।

Project name quire প্রতিটি volume-এর নামের আগে যুক্ত হয়; সঠিক নাম জানতে docker volume ls দেখুন।

নির্ধারিত verification drill

backup-offsite প্রতি QUIRE_BACKUP_DRILL_INTERVAL_HOURS (default 168, সাপ্তাহিক) পর drill চালায় এবং ব্যর্থ হলে পরের pass-এ আবার চালায়। এটি host-এর বাইরের সর্বশেষ base backup ও তার পরের প্রতিটি WAL segment fetch করে, প্রত্যেকটি decrypt করে (এতে প্রমাণ হয় key এখনও সেগুলো খুলতে পারে এবং কিছু বদলানো হয়নি), প্রতিটি file তার manifest-এর সঙ্গে মেলায়, archive একটি Postgres data directory কি না পরীক্ষা করে এবং backup-এর পর থেকে WAL-এ কোনো ফাঁক নেই কি না যাচাই করে। Report store-এ reports/drill-<time>.json নামে এবং service log-এ লেখা হয়; drill ব্যর্থ হলে file অথবা প্রথম অনুপস্থিত segment-এর নাম জানায়।

Restore drill

কখনো restore করা হয়নি এমন backup আসলে backup নয়। Drill লক্ষ্য সময়ের আগের সবচেয়ে নতুন সম্পূর্ণ base backup এবং WAL archive-কে live database থেকে সম্পূর্ণ আলাদা scratch Postgres-এ restore করে এবং ফল যাচাই করে:

docker/scripts/restore-drill.sh                                  # to ninety minutes ago
docker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"

লক্ষ্য UTC-তে ঠিক এই format-এ দিতে হয়। এর আগের একটি base backup ও পরের archived WAL দরকার: নতুন install-এ একটি base backup নিন এবং backup-এর পরের সময় বেছে নেওয়ার আগে পরবর্তী archived segment আসা পর্যন্ত অপেক্ষা করুন (write চললে সর্বোচ্চ এক মিনিট)। Host-এ drill চালাতে Docker ও bash ছাড়া আর কিছু লাগে না।

নিচের প্রতিটি ধাপে drill ব্যর্থ হতে পারে:

  1. Recovery সম্ভব কি না: scratch cluster লক্ষ্য সময় পর্যন্ত replay করে এবং চালু হয়।
  2. সম্পূর্ণতা: live database-এর তুলনায় প্রতিটি table-এর row count (tooling/restore-drill)। লক্ষ্য সময়ের পর live database-এ পরিবর্তন হয়েছে; তাই row-এর সংখ্যা কম বা বেশি হতে পারে—দুটির যেটি বড়, 500 row অথবা table-এর দশ ভাগের এক ভাগ। Write হলে restore-এ row কম থাকে, delete হলে বেশি থাকে; লক্ষ্য সময়ের পরে তৈরি partition হারানো table নয়। বেশি ব্যস্ত install-এ QUIRE_DRILL_MAX_BEHIND ও QUIRE_DRILL_MAX_DRIFT_RATIO দিয়ে অনুমোদিত পার্থক্য বাড়ান। কোনো table অনুপস্থিত বা খালি হলে drill ব্যর্থ হয়।
  3. অখণ্ডতা: restore করা copy-তে audit hash chain যাচাই হয়।
  4. ব্যবহারযোগ্যতা: application role row-level security মেনে পড়তে পারে।
  5. সময়: শুরু থেকে সফল অবস্থা পর্যন্ত সময় QUIRE_DRILL_RTO_SECONDS-এর (default 3600) মধ্যে কি না।

এটি live database বা তার volume-এ কখনো write করে না: backup ও WAL volume read-only mount হয়; ফল সফল হোক বা ব্যর্থ, শেষে scratch cluster সরিয়ে ফেলা হয়।

QUIRE_DRILL_REPORT-এ path দিলে ফল যাই হোক JSON report লেখা হয়। Docker host-এ schedule করে চালান:

30 3 1 * * cd /srv/quire && QUIRE_DRILL_REPORT=/var/log/quire-drill.json docker/scripts/restore-drill.sh >> /var/log/quire-drill.log 2>&1

প্রতি মাসে এবং প্রতিটি upgrade-এর আগে এটি চালান। Drill ব্যর্থ হলে upgrade আটকে যায়। প্রতি তিন মাসে runbook না-লেখা কাউকে spare host-এ শুধু এই নথি ব্যবহার করে বাস্তব point-in-time restore করতে দিন।

File

Local file files volume-এ থাকে। Database-এর সঙ্গে একই সময়ে backup করে দুটিই একসঙ্গে restore করুন:

docker run --rm -v quire_files:/files:ro -v "$PWD":/out alpine tar -czf /out/files-$(date -u +%Y%m%d).tar.gz -C /files .

Object storage হলে bucket versioning চালু করে noncurrent version 35 দিন রাখুন; তখন file-এর point-in-time recovery bucket-ই করে।

নেভিগেশন

খুঁজতে লিখুন…

↑↓ নেভিগেট করুন↵ নির্বাচন করুনEsc বন্ধ করুন