رفتن به محتوا

پشتیبان‌گیری، بازیابی نقطه‌ای در زمان و تمرین بازیابی

از Quire پشتیبان بگیرید، آن را به نقطه‌ای در زمان بازگردانید و با تمرین بازیابی صحت کار را ثابت کنید.

طرح در بخش ۸ از docs/architecture/23-ops.md آمده است. این راهنمای محصول Docker Compose است و برای پیروی کسی نوشته شده که آن را ننوشته است؛ اگر گامی روشن نیست، این نقص مستندات است.

چه چیزی محافظت می‌شود و چگونه

دارایی روش محل
پایگاه داده بایگانی پیوستهٔ WAL، حداکثر هر ۶۰ ثانیه و از نخستین راه‌اندازی pgwal volume
پایگاه داده پشتیبان پایه با pg_basebackup، به‌طور پیش‌فرض هر روز (backup-scheduler) pgbackup volume
پایگاه داده نسخه‌های رمزگذاری‌شدهٔ پشتیبان پایه و WAL، هر پنج دقیقه (backup-offsite) مخزن جداگانه‌ای که نام می‌برید
فایل‌ها files volume؛ با ابزار پشتیبان میزبان کپی کنید یا ذخیره‌سازی شیء نسخه‌دار به کار ببرید files volume
رازها docker/.env، به‌ویژه QUIRE_MASTER_KEY (و هر QUIRE_MASTER_KEY_RETIRED که هنوز به کار می‌رود)، QUIRE_BACKUP_ENCRYPTION_KEY و docker/secrets/audit-signing-key.pem نسخه‌ای بیرون از این میزبان نگه دارید
نمایه‌های جست‌وجو، cacheها و renditionها پشتیبان‌گیری نمی‌شوند؛ دوباره ساخته می‌شوند

هدف‌ها: نقطهٔ بازیابی حداکثر ۶۰ ثانیه پیش از شکست و بازیابی پایگاه ۵۰۰ GBای در ۶۰ دقیقه.

دو اشتباه رایج‌اند. پایگاهی که بدون فایل‌هایش بازیابی شود صفحه‌های خراب نشان می‌دهد. پایگاهی که بدون QUIRE_MASTER_KEY بازیابی شود نمی‌تواند اعتبارنامه‌های SSO، وب‌هوک و یکپارچه‌سازی را رمزگشایی کند؛ تا وقتی چرخش کلید اصلی بدون مقدار حل‌نشده تمام نشده (key-rotation.md)، کلیدهای بازنشسته هم شامل‌اند. هر دو بخش پشتیبان‌اند.

گرفتن پشتیبان

پشتیبان پایه از سراسر cluster:

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

تازه‌ترین QUIRE_BACKUP_KEEP پشتیبان پایه را نگه می‌دارد (پیش‌فرض ۵) و WALای را که قدیمی‌ترین نسخه دیگر نیاز ندارد پاک می‌کند تا بایگانی بی‌حد رشد نکند. روی میزبان با cron یا زمان‌سنج systemd روزانه زمان‌بندی‌اش کنید:

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

یا بگذارید stack زمان‌بندی‌اش کند: profile backup، backup-scheduler را اجرا می‌کند که هر QUIRE_BACKUP_INTERVAL_HOURS (پیش‌فرض ۲۴) ساعت پشتیبان پایه می‌گیرد و backup-offsite را نیز اجرا می‌کند که بعد می‌آید.

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

نسخه‌های رمزگذاری‌شده بیرون از میزبان

هر دو volume در همان میزبانی‌اند که پایگاه داده؛ پشتیبانی که روی ماشین ازکارافتاده باشد پشتیبان نیست. backup-offsite از راه درگاه ذخیره‌سازی هر پشتیبان پایه و هر بخش بایگانی‌شدهٔ WAL را رمزگذاری می‌کند، به مخزنی جدا می‌فرستد و طبق سیاست نگهداری همان‌جا حفظ می‌کند:

  • رمزگذاری: AES-256-GCM با QUIRE_BACKUP_ENCRYPTION_KEY یا فایلی که QUIRE_BACKUP_ENCRYPTION_KEY_FILE نام می‌برد؛ ۳۲ بایت، ساخته‌شده با openssl rand -hex 32. هر فایل nonce و برچسب اصالت‌سنجی خودش را دارد، پس بدون کلید خوانده نمی‌شود و هر تغییرش آشکار می‌شود. کلید را همراه QUIRE_MASTER_KEY، دور از این میزبان و مخزن پشتیبان نگه دارید. بدون کلید بازیابی ممکن نیست.
  • محل: QUIRE_BACKUP_STORAGE_DRIVER یکی از s3، azure یا local است (دیسک دوردست mountشده در QUIRE_BACKUP_STORAGE_ROOT). تنظیم‌ها همان تنظیم‌های ذخیره‌سازی فایل‌اند با پیشوند QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT، QUIRE_BACKUP_S3_BUCKET، QUIRE_BACKUP_S3_ACCESS_KEY_ID و بقیه. bucketی متفاوت و بهتر آنکه حسابی جدا از فایل‌ها به کار ببرید؛ اگر ارائه‌دهنده اجازه می‌دهد اعتبارنامه فقط نوشتن داشته باشد، نه حذف.
  • مدت نگهداری: تازه‌ترین QUIRE_BACKUP_OFFSITE_KEEP پشتیبان پایه (پیش‌فرض QUIRE_BACKUP_KEEP و اگر آن هم تنظیم نشده باشد ۷) و WAL موردنیاز قدیمی‌ترین آن‌ها نگه داشته می‌شوند؛ مجموعه‌ها و بخش‌های قدیمی‌تر از مخزن حذف می‌شوند.
  • زمان: هر QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (پیش‌فرض ۳۰۰). فرستادن تکرارپذیر است: آنچه از پیش ذخیره شده کنار گذاشته می‌شود و پشتیبان پایه فقط پس از نوشتن manifest، آن هم در پایان، ذخیره‌شده به‌شمار می‌آید.

همین فرمان را می‌توان دستی اجرا کرد:

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

برای بازیابی روی میزبان تازه، ابتدا یک مجموعه را برگردانید؛ سپس گام‌های پایین را با پوشهٔ دریافت‌شده به‌جای volume pgbackup و wal-archive دریافت‌شده به‌جای pgwal دنبال کنید:

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

بازیابی تا نقطه‌ای در زمان

پس از از دست رفتن داده از این روش استفاده کنید: ورود بد، حذف دوره یا migration قراردادی‌ای که باید واگرد شود. این کار پایگاه زنده را جایگزین می‌کند، پس ابتدا با تمرین پایین آن را بیازمایید.

  1. زمان مقصد را انتخاب کنید، به UTC و درست پیش از آسیب: 2026-09-24 09:30:00+00. گزارش حسابرسی (/admin/audit) معمولاً زمان را نشان می‌دهد.
  2. هر چیزی را که می‌نویسد متوقف کنید: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. 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. پشتیبان پایهٔ تازه‌تری را که از زمان مقصد قدیمی‌تر است از حالت بسته خارج کنید و آن را در volume داده بگذارید تا بازیابی هدفمند شود:
    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. بازیابی کنید: Postgres را یک‌بار با تنظیمات بازیابی، از راه override مربوط به Compose آغاز کنید تا فایل عادی دست‌نخورده بماند:
    # 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 کل فرمان را جایگزین می‌کند، پس دو تنظیمی را که بازیابی به آن‌ها وابسته است دوباره می‌آورد: max_connections باید کمتر از مقدار اصلی نباشد (وگرنه بازیابی با خطای «insufficient parameter settings» متوقف می‌شود) و فایل 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. پیش از راه دادن افراد بررسی کنید: زنجیرهٔ حسابرسی را با (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) بسنجید و مطمئن شوید دادهٔ ازدست‌رفته برگشته است.
  7. به حالت عادی برگردید: docker compose -f docker/compose.yaml up -d. این کار Postgres را با بایگانی روشن دوباره آغاز می‌کند و خط زمانی WAL تازه‌ای شروع می‌شود. بلافاصله پشتیبان پایهٔ تازه بگیرید.

نام پروژهٔ quire به آغاز نام هر volume افزوده می‌شود؛ docker volume ls نام‌های دقیق را نشان می‌دهد.

تمرین راستی‌آزمایی زمان‌بندی‌شده

backup-offsite همچنین هر QUIRE_BACKUP_DRILL_INTERVAL_HOURS ساعت (پیش‌فرض ۱۶۸، هفتگی) تمرین می‌کند و اگر تمرینی شکست بخورد در نوبت بعدی دوباره انجامش می‌دهد. تازه‌ترین پشتیبان پایهٔ بیرون از میزبان و همهٔ بخش‌های WAL پس از آن را می‌گیرد، هرکدام را رمزگشایی می‌کند (ثابت می‌کند کلید هنوز آن‌ها را باز می‌کند و چیزی عوض نشده)، هر فایل را با manifestش می‌سنجد، بررسی می‌کند آرشیو پوشهٔ دادهٔ Postgres است و مطمئن می‌شود WAL پس از پشتیبان پایه بی‌وقفه است. گزارش در مخزن با نام reports/drill-<time>.json و در گزارش سرویس نوشته می‌شود؛ تمرین نام فایل یا نخستین بخش گمشده را می‌گوید.

تمرین بازیابی

پشتیبانی که هرگز بازیابی نشده باشد پشتیبان نیست. تمرین، تازه‌ترین پشتیبان پایهٔ کاملِ گرفته‌شده پیش از زمان مقصد و بایگانی WAL را به Postgres آزمایشی‌ای می‌برد که هیچ‌چیزی با نمونهٔ زنده شریک نیست و نتیجه را اثبات می‌کند:

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

مقصد باید UTC و دقیقاً به همین شکل باشد. پشتیبان پایه‌ای قدیمی‌تر از مقصد و WAL بایگانی‌شده پس از آن لازم است: در نصب تازه پشتیبان پایه بگیرید و پیش از انتخاب مقصدی پس از آن، تا رسیدن بخش بایگانی‌شدهٔ بعدی صبر کنید (با نوشتن حداکثر یک دقیقه). تمرین روی میزبان فقط Docker و bash می‌خواهد.

هرکدام از این گام‌ها تمرین را شکست می‌دهد:

  1. قابلیت بازیابی: cluster آزمایشی تا مقصد بازپخش و باز می‌شود.
  2. کامل بودن: شمار ردیف‌های هر جدول با پایگاه زنده سنجیده می‌شود (tooling/restore-drill). پایگاه زنده از زمان مقصد جلو رفته است، پس اختلاف هر جدول می‌تواند در هر جهت بیشینهٔ ۵۰۰ ردیف یا یک‌دهم اندازه‌اش باشد (نوشتن باعث عقب‌ماندنش می‌شود و حذف باعث می‌شود نسخهٔ بازیابی‌شده بیشتر داشته باشد)؛ partitionای که پس از مقصد ساخته شده جدول ازدست‌رفته نیست. در نصب پرکار با QUIRE_DRILL_MAX_BEHIND و QUIRE_DRILL_MAX_DRIFT_RATIO دامنهٔ مجاز را بیشتر کنید. جدول گمشده یا خالی‌شده شکست است.
  3. درستی: زنجیرهٔ hash حسابرسی در نسخهٔ بازیابی‌شده تأیید می‌شود.
  4. کاربردپذیری: نقش برنامه زیر امنیت سطح سطر داده را می‌خواند.
  5. زمان: از آغاز تا سبز شدن، در برابر QUIRE_DRILL_RTO_SECONDS (پیش‌فرض ۳۶۰۰) سنجیده می‌شود.

این کار هرگز به پایگاه زنده یا volumeهایش چیزی نمی‌نویسد: volumeهای پشتیبان و WAL فقط‌خواندنی mount می‌شوند و cluster آزمایشی در پایان، چه موفق چه ناموفق، پاک می‌شود.

QUIRE_DRILL_REPORT را روی مسیری بگذارید تا گزارش JSON چه موفق شود چه نه نوشته شود و آن را از میزبان Docker زمان‌بندی کنید:

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

ماهانه و پیش از هر ارتقا اجرا کنید. تمرین ناموفق ارتقا را متوقف می‌کند. هر سه ماه یک‌بار از کسی که این راهنما را ننوشته بخواهید با تکیه بر همین سند، بازیابی واقعی نقطه‌ای در زمان را روی میزبان یدکی انجام دهد.

فایل‌ها

فایل‌های محلی در volume files هستند. هم‌زمان با پایگاه داده از آن پشتیبان بگیرید و هر دو را با هم بازیابی کنید:

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 .

در ذخیره‌سازی شیء، نسخه‌بندی bucket را روشن کنید و ۳۵ روز نسخه‌های غیربه‌روز را نگه دارید؛ بازیابی نقطه‌ای فایل‌ها آن‌گاه با سازوکار خود bucket انجام می‌شود.

پیمایش

برای جست‌وجو بنویسید…

↑↓ پیمایش↵ انتخابEsc بستن