---
title: "Sandaran, pemulihan pada titik masa dan latihan pemulihan"
description: "Sandarkan Quire, pulihkannya ke satu titik masa, dan buktikannya dengan latihan pemulihan."
image: "https://docs.quirelms.com/og.png"
---

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

# Sandaran, pemulihan pada titik masa dan latihan pemulihan

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

Reka bentuknya ialah `docs/architecture/23-ops.md` seksyen 8. Ini ialah buku
panduan untuk produk Docker Compose. Ia ditulis untuk diikuti oleh seseorang yang
tidak menulisnya; jika sesuatu langkah tidak jelas, itu ialah satu kecacatan
dalam dokumen ini.

## Apa yang dilindungi, dan bagaimana <!--quire:what-is-protected-and-how-->

| Aset | Cara | Di mana |
| --- | --- | --- |
| Pangkalan data | WAL diarkibkan berterusan, paling banyak setiap 60 saat, daripada but pertama | Volum `pgwal` |
| Pangkalan data | Sandaran asas dengan `pg_basebackup`, harian secara lalai (`backup-scheduler`) | Volum `pgbackup` |
| Pangkalan data | Salinan bersulit daripada sandaran asas dan WAL, setiap lima minit (`backup-offsite`) | Sebuah stor berasingan yang anda namakan |
| Fail | Volum `files`. Salin dengan alat sandaran hos anda, atau gunakan storan objek berversi | Volum `files` |
| Rahsia | `docker/.env`, di atas segalanya `QUIRE_MASTER_KEY` (dan mana-mana `QUIRE_MASTER_KEY_RETIRED` yang masih digunakan), `QUIRE_BACKUP_ENCRYPTION_KEY`, dan `docker/secrets/audit-signing-key.pem` | Simpan satu salinan di luar hos ini |
| Indeks carian, cache, rendition | Tidak disandarkan; dibina semula | |

Sasaran: satu titik pemulihan dalam 60 saat selepas kegagalan, dan satu pemulihan
dalam 60 minit untuk sebuah pangkalan data 500 GB.

Dua kesilapan adalah biasa. Sebuah pangkalan data yang dipulihkan **tanpa failnya**
memaparkan halaman yang rosak. Sebuah pangkalan data yang dipulihkan **tanpa
`QUIRE_MASTER_KEY`** tidak dapat menyahsulit kelayakan SSO, webhook dan integrasi
yang disimpannya; sehinggalah satu pusingan kunci induk selesai tanpa apa-apa yang
tidak selesai ([key-rotation.md](/ms/ops/key-rotation/)), itu termasuk kunci yang bersara.
Kedua-duanya adalah sebahagian daripada sandaran itu.

## Mengambil sandaran <!--quire:taking-backups-->

Satu sandaran asas untuk seluruh kelompok:

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

Ia mengekalkan `QUIRE_BACKUP_KEEP` sandaran asas terbaharu (lalai 5) dan memangkas
WAL yang tidak lagi diperlukan oleh yang paling lama, supaya arkif tidak boleh
membesar tanpa batas. Jadualkannya harian dengan cron atau satu pemasa systemd pada
hos:

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

Atau biarkan tindanan itu menjadualkannya: profil `backup` menjalankan
`backup-scheduler`, yang mengambil satu sandaran asas setiap
`QUIRE_BACKUP_INTERVAL_HOURS` (lalai 24), dan `backup-offsite`, yang diterangkan
seterusnya.

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

## Salinan bersulit di luar hos <!--quire:encrypted-off-host-copies-->

Kedua-dua volum berada pada hos yang sama dengan pangkalan data, dan sebuah sandaran
pada mesin yang gagal bukanlah satu sandaran. `backup-offsite` menyalin setiap
sandaran asas dan setiap segmen WAL yang diarkibkan ke sebuah stor berasingan
melalui port storan, disulitkan, dan menyimpannya di sana di bawah pengekalan:

- **Penyulitan.** AES-256-GCM dengan `QUIRE_BACKUP_ENCRYPTION_KEY` (atau fail
  yang dinamakan oleh `QUIRE_BACKUP_ENCRYPTION_KEY_FILE`): 32 bait, daripada
  `openssl rand -hex 32`. Setiap fail mempunyai nonce sendiri dan satu tag
  pengesahan, jadi sebuah salinan tidak boleh dibaca tanpa kunci dan sebarang
  perubatan kepadanya dikesan. Simpan kunci itu bersama `QUIRE_MASTER_KEY`, jauh
  daripada hos ini dan jauh daripada stor sandaran. Tanpa kunci tiada pemulihan.
- **Di mana.** `QUIRE_BACKUP_STORAGE_DRIVER` ialah `s3`, `azure` atau `local`
  (sebuah cakera jauh yang dilekatkan pada `QUIRE_BACKUP_STORAGE_ROOT`). Tetapannya
  ialah tetapan storan fail dengan awalan `QUIRE_BACKUP_`:
  `QUIRE_BACKUP_S3_ENDPOINT`, `QUIRE_BACKUP_S3_BUCKET`,
  `QUIRE_BACKUP_S3_ACCESS_KEY_ID`, dan sebagainya. Guna baldi yang berbeza dan,
  sebaiknya, akaun yang berbeza daripada fail, dengan kelayakan yang boleh menulis
  tetapi tidak memadam jika pembekal membenarkannya.
- **Pengekalan.** `QUIRE_BACKUP_OFFSITE_KEEP` sandaran asas terbaharu (lalai
  `QUIRE_BACKUP_KEEP`, selain itu 7) dan WAL yang diperlukan oleh yang paling lama
  antaranya; set dan segmen yang lebih lama dipadam daripada stor.
- **Bila.** Setiap `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (lalai 300). Penghantaran
  adalah idempoten: apa yang sudah disimpan dilangkau, dan sebuah sandaran asas
  dikira sebagai disimpan hanya apabila manifesnya ditulis, terakhir.

Arahan yang sama dijalankan secara manual:

```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
```

Untuk memulihkan pada sebuah hos baharu, bawa satu set kembali terlebih dahulu,
kemudian ikuti langkah-langkah di bawah dengan direktori yang dibawa sebagai ganti
volum `pgbackup` dan `wal-archive` yang dibawa sebagai ganti `pgwal`:

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

## Memulihkan ke satu titik masa <!--quire:restoring-to-a-point-in-time-->

Guna ini selepas kehilangan data: satu import yang buruk, sebuah kursus yang
dipadam, satu migrasi kontrak yang perlu anda batalkan. Ia menggantikan pangkalan
data langsung, jadi latihkannya dengan latihan di bawah terlebih dahulu.

1. **Pilih masa sasaran**, dalam UTC, tepat sebelum kerosakan:
   `2026-09-24 09:30:00+00`. Log audit (`/admin/audit`) biasanya menunjukkan
   saat itu.
2. **Hentikan segala-galanya yang menulis**:
   `docker compose -f docker/compose.yaml stop web content worker scheduler collab`
3. **Kekalkan kelompok yang rosak** sehingga pemulihan disahkan:
   ```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. **Buka paket sandaran asas terbaharu yang lebih lama daripada masa sasaran**
   ke dalam volum data, dan minta pemulihan bertarget:
   ```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. **Pulihkan**: mulakan Postgres sekali dengan tetapan pemulihan, daripada satu
   override Compose supaya fail biasa tidak disentuh:
   ```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 itu menggantikan seluruh perintah, jadi ia mengulang dua tetapan yang
   bergantung kepada pemulihan: `max_connections` tidak lebih rendah daripada
   yang utama (selain itu pemulihan dibatalkan dengan "insufficient parameter
   settings") dan `pg_hba.conf` yang dilekatkan.
   ```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. **Semaknya** sebelum membenarkan sesiapa masuk: rantai audit
   (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`),
   dan bahawa data yang hilang telah kembali.
7. **Kembali kepada keadaan biasa**: `docker compose -f docker/compose.yaml up -d`.
   Ini memulakan semula Postgres dengan pengarkiban hidup, dan satu garis masa WAL
   baharu bermula. Ambil satu sandaran asas baharu serta-merta.

Nama projek `quire` menjadi awalan setiap volum; `docker volume ls` menunjukkan
nama yang tepat.

## Latihan pengesahan berjadual <!--quire:the-scheduled-verification-drill-->

`backup-offsite` juga menjalankan satu latihan setiap
`QUIRE_BACKUP_DRILL_INTERVAL_HOURS` (lalai 168, mingguan), dan sekali lagi pada
laluan seterusnya selepas satu gagal. Ia membawa masuk sandaran asas di luar hos
terbaharu dan setiap segmen WAL selepasnya, menyahsulit setiap satu (yang
membuktikan kunci itu masih membukanya dan tiada apa-apa yang diubah), membandingkan
setiap fail dengan manifesnya, menyemak bahawa arkif itu ialah satu direktori data
Postgres, dan menyemak bahawa WAL daripada sandaran dan seterusnya tiada jurang.
Laporan itu ditulis ke stor sebagai `reports/drill-<time>.json` dan ke log
perkhidmatan; satu latihan yang gagal menamakan fail itu atau segmen hilang yang
pertama.

## Latihan pemulihan <!--quire:the-restore-drill-->

Sebuah sandaran yang tidak pernah dipulihkan bukanlah satu sandaran. Latihan itu
memulihkan sandaran asas lengkap terbaharu yang diambil sebelum masa sasaran,
tambah arkif WAL, ke dalam sebuah Postgres sementara yang tidak berkongsi apa-apa
dengan yang langsung, dan membuktikan hasilnya:

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

Masa sasaran ialah UTC dalam bentuk tepat itu. Ia memerlukan satu sandaran asas
yang lebih lama daripadanya dan WAL yang diarkibkan melampaunya: pada sebuah
pemasangan baharu, ambil satu sandaran asas dan tunggu segmen diarkibkan seterusnya
(paling banyak seminit dengan tulisan) sebelum memilih satu masa sasaran selepas
sandaran itu. Latihan memerlukan Docker dan bash pada hos, tiada apa-apa lagi.

Setiap langkah menggagalkan latihan itu:

1. **Boleh dipulihkan**: kelompok sementara itu dimainkan semula ke masa sasaran
   dan dibuka.
2. **Kelengkapan**: kiraan baris setiap jadual terhadap pangkalan data langsung
   (`tooling/restore-drill`). Pangkalan data langsung telah bergerak sejak masa
   sasaran, jadi sebuah jadual mungkin berbeza dengan yang lebih besar antara 500
   baris dan sepuluh peratus saiznya, dalam mana-mana arah (tulisan menjadikannya
   ketinggalan, pemadaman menjadikan pemulihan memegang lebih); satu sekatan yang
   dicipta selepas masa sasaran bukanlah sebuah jadual yang hilang. Luaskan
   kelonggaran pada pemasangan yang lebih sibuk dengan `QUIRE_DRILL_MAX_BEHIND`
   dan `QUIRE_DRILL_MAX_DRIFT_RATIO`. Sebuah jadual yang hilang atau dikosongkan
   menggagalkan.
3. **Integriti**: rantai hash audit disahkan pada salinan yang dipulihkan.
4. **Kebolehgunaan**: peranan aplikasi membaca melalui keselamatan tahap baris.
5. **Masa**: daripada mula hingga hijau, berbanding `QUIRE_DRILL_RTO_SECONDS`
   (lalai 3600).

Ia tidak pernah menulis ke pangkalan data langsung atau volumnya: volum sandaran
dan WAL dilekatkan hanya-baca dan kelompok sementara itu dibuang di penghujungnya,
sama ada lulus atau gagal.

Tetapkan `QUIRE_DRILL_REPORT` kepada satu laluan untuk memiliki satu laporan JSON
ditulis, sama ada lulus atau gagal, dan jalankannya secara berjadual daripada hos
Docker:

```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
```

Jalankannya setiap bulan dan sebelum setiap naik taraf. Sebuah latihan yang gagal
menyekat naik taraf itu. Sekali setiap suku tahun, minta seseorang yang tidak
menulis buku panduan ini melakukan satu pemulihan titik masa sebenar pada sebuah
hos terbiang, menggunakan dokumen ini sahaja.

## Fail <!--quire:files-->

Fail setempat berada dalam volum `files`. Sandarkannya bersama pangkalan data,
pada masa yang sama, dan pulihkan kedua-duanya bersama:

```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 .
```

Dengan storan objek, hidupkan penghantaran versi baldi dan kekalkan 35 hari versi
bukan semasa; pemulihan pada titik masa untuk fail kemudian ialah milik baldi itu
sendiri.

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