---
title: "Backup, নির্দিষ্ট সময়ে পুনরুদ্ধার ও restore drill"
description: "Quire backup করুন, নির্দিষ্ট সময়ে ফিরিয়ে আনুন এবং restore drill দিয়ে প্রমাণ করুন যে প্রক্রিয়া কাজ করে।"
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/bn/llms.txt
> Use this file to discover all available pages before exploring further.

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

<span id="backup-point-in-time-recovery-and-the-restore-drill"></span>

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

## কী সুরক্ষিত এবং কীভাবে <!--quire:what-is-protected-and-how-->

| বিষয় | পদ্ধতি | কোথায় |
| --- | --- | --- |
| 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](/bn/ops/key-rotation/))। দুটিই backup-এ থাকতে হবে।

## Backup নেওয়া <!--quire:taking-backups-->

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

```sh
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 করুন:

```cron
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` চালায়।

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

## Host-এর বাইরে encrypted copy <!--quire:encrypted-off-host-copies-->

দুটি 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 হাতে চালানো যায়:

```sh
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`-এর স্থলাভিষিক্ত হবে; এরপর নিচের ধাপগুলো অনুসরণ করুন:

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

## নির্দিষ্ট সময়ে restore <!--quire:restoring-to-a-point-in-time-->

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 রাখুন**:
   ```sh
   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 চালু করুন:
   ```sh
   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 একবার চালু করুন:
   ```yaml
   # 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`।
   ```sh
   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 <!--quire:the-scheduled-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 <!--quire:the-restore-drill-->

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

```sh
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 করে চালান:

```cron
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 <!--quire:files-->

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

```sh
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-ই করে।

Source: https://docs.quirelms.com/bn/ops/backup-restore/index.mdx
