Lumaktaw sa nilalaman

Backup, point-in-time recovery and the restore drill

I-backup ang Quire, ibalik ito sa isang tiyak na oras, at patunayan ito sa pamamagitan ng restore drill.

Tingnan bilang Markdown

Ang disenyo ay nasa docs/architecture/23-ops.md seksiyon 8. Ito ang runbook para sa Docker Compose na produkto. Isinulat ito upang masundan ng taong hindi sumulat nito; kung malabo ang isang hakbang, depekto iyon sa dokumentong ito.

Ano ang pinoprotektahan, at paano

Asset Paano Saan
Database Tuloy-tuloy na ina-archive ang WAL, hindi hihigit sa bawat 60 segundo, mula sa unang boot pgwal volume
Database Mga base backup gamit ang pg_basebackup, araw-araw bilang default (backup-scheduler) pgbackup volume
Database Mga naka-encrypt na kopya ng mga base backup at WAL, bawat limang minuto (backup-offsite) Hiwalay na store na ikaw ang magtatalaga
Mga file Ang files volume. Kopyahin ito gamit ang backup tool ng host mo, o gumamit ng versioned object storage files volume
Mga lihim docker/.env, higit sa lahat ang QUIRE_MASTER_KEY (at anumang QUIRE_MASTER_KEY_RETIRED na ginagamit pa), QUIRE_BACKUP_ENCRYPTION_KEY, at docker/secrets/audit-signing-key.pem Magtabi ng kopya sa labas ng host na ito
Mga search index, cache, rendition Hindi bina-backup; muling ginagawa

Mga target: recovery point na hindi lalampas sa 60 segundo bago ang pagkabigo, at restore sa loob ng 60 minuto para sa 500 GB na database.

Dalawang pagkakamali ang karaniwan. Ang database na na-restore nang wala ang mga file ay nagdudulot ng sirang mga pahina. Ang database na na-restore nang wala ang QUIRE_MASTER_KEY ay hindi makakapag-decrypt ng mga kredensiyal sa SSO, webhook at integration na laman nito; hanggang sa matapos ang master key rotation nang walang natitirang hindi nalutas (key-rotation.md), kasama rito ang mga retiradong key. Bahagi ng backup ang dalawa.

Pagkuha ng mga backup

Base backup ng buong cluster:

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

Pinananatili nito ang pinakabagong QUIRE_BACKUP_KEEP na mga base backup (default 5) at pinuputol ang WAL na hindi na kailangan ng pinakaluma, kaya hindi lalaki nang walang hanggan ang archive. I-iskedyul ito araw-araw gamit ang cron o systemd timer sa host:

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

O hayaang ang stack ang mag-iskedyul nito: pinapatakbo ng backup profile ang backup-scheduler, na kumukuha ng base backup bawat QUIRE_BACKUP_INTERVAL_HOURS (default 24), at ang backup-offsite na inilalarawan sa susunod.

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

Mga naka-encrypt na kopyang nasa labas ng host

Nasa parehong host ng database ang dalawang volume, at hindi maituturing na backup ang backup na nasa makinang pumalya. Kinokopya ng backup-offsite ang bawat base backup at bawat naka-archive na WAL segment papunta sa hiwalay na store sa pamamagitan ng storage port, ini-encrypt ito, at pinananatili roon ayon sa retention:

  • Encryption. AES-256-GCM gamit ang QUIRE_BACKUP_ENCRYPTION_KEY (o ang file na pinangalanan ng QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 byte mula sa openssl rand -hex 32. May sariling nonce at authentication tag ang bawat file, kaya hindi mababasa ang kopya kung wala ang key at natutukoy ang anumang pagbabago rito. Itabi ang key kasama ng QUIRE_MASTER_KEY, malayo sa host na ito at sa backup store. Kung wala ang key, walang maibabalik.
  • Saan. QUIRE_BACKUP_STORAGE_DRIVER ay s3, azure o local (isang naka-mount na remote disk sa QUIRE_BACKUP_STORAGE_ROOT). Mga setting ito ng file storage na may QUIRE_BACKUP_ na prefix: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID, at iba pa. Gumamit ng ibang bucket at, kung maaari, ibang account kaysa sa mga file, na may mga kredensiyal na puwedeng magsulat pero hindi magtanggal kung pinapayagan ito ng provider.
  • Retention. Pinakabagong QUIRE_BACKUP_OFFSITE_KEEP na mga base backup (default QUIRE_BACKUP_KEEP, o 7 kung wala ito) at ang WAL na kailangan ng pinakaluma sa mga ito; tatanggalin sa store ang mas lumang mga set at segment.
  • Kailan. Bawat QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (default 300). Idempotent ang pagpapadala: nilalaktawan ang naka-store na, at maituturing lang na naka-store ang base backup kapag naisulat na ang manifest nito, bilang huling hakbang.

Manu-mano ring pinapatakbo ang parehong 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

Para mag-restore sa bagong host, kunin muna roon ang isang set, saka sundin ang mga hakbang sa ibaba gamit ang nakuha mong directory kapalit ng pgbackup volume at ang nakuha mong wal-archive kapalit ng pgwal:

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

Pag-restore sa isang tiyak na oras

Gamitin ito pagkatapos mawalan ng data: maling import, naburang kurso, o paglipat ng kontrata na kailangan mong bawiin. Pinapalitan nito ang live na database, kaya magsanay muna rito gamit ang drill sa ibaba.

  1. Piliin ang target na oras, sa UTC, bago mismo ang pinsala: 2026-09-24 09:30:00+00. Karaniwang ipinapakita ng audit log (/admin/audit) ang sandaling iyon.
  2. Ihinto ang lahat ng sumusulat: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Panatilihin ang napinsalang cluster hanggang ma-verify ang restore:
    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. I-unpack sa data volume ang pinakabagong base backup na mas luma sa target, at humiling ng recovery sa tinukoy na oras:
    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. Mag-recover: minsanang simulan ang Postgres gamit ang mga setting ng recovery mula sa Compose override para manatiling hindi nagbabago ang karaniwang file:
    # 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]
    Pinapalitan ng override ang buong command, kaya inuulit nito ang dalawang setting na kailangan ng recovery: max_connections na hindi mas mababa sa primary (kung hindi, hihinto ang recovery dahil sa “insufficient parameter settings”) at ang naka-mount na 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. Suriin ito bago papasukin ang sinuman: ang audit chain (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts), at tiyaking naibalik ang nawalang data.
  7. Bumalik sa normal: docker compose -f docker/compose.yaml up -d. Magsisimulang muli ang Postgres na naka-on ang archiving, at magsisimula ang bagong WAL timeline. Kumuha agad ng bagong base backup.

Nilalagyan ng pangalan ng proyekto na quire ang bawat volume; ipinapakita ng docker volume ls ang eksaktong mga pangalan.

Naka-iskedyul na verification drill

Nagpapatakbo rin ng drill ang backup-offsite bawat QUIRE_BACKUP_DRILL_INTERVAL_HOURS (default 168, lingguhan), at muli sa susunod na pagtakbo kapag pumalya ang isa. Kinukuha nito ang pinakabagong off-host base backup at bawat WAL segment pagkatapos nito, dine-decrypt ang bawat isa (na nagpapatunay na nabubuksan pa rin ng key ang mga ito at walang binago), inihahambing ang bawat file sa manifest nito, sinusuri kung Postgres data directory ang archive, at tinitiyak na walang puwang sa WAL mula sa backup pataas. Isinusulat ang ulat sa store bilang reports/drill-<time>.json at sa log ng serbisyo; tinutukoy ng pumalyang drill ang file o ang unang nawawalang segment.

Ang restore drill

Hindi maituturing na backup ang backup na hindi pa na-restore. Ibinabalik ng drill ang pinakabagong kumpletong base backup na kinuha bago ang target, kasama ang WAL archive, sa isang pansamantalang Postgres na walang anumang ibinabahagi sa live na database, at pinatutunayan ang resulta:

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

UTC ang target at dapat eksaktong ganito ang format. Kailangan nito ng base backup na mas luma rito at naka-archive na WAL na lampas dito: sa bagong install, kumuha ng base backup at hintayin ang susunod na naka-archive na segment (hindi hihigit sa isang minuto kapag may mga write) bago pumili ng target na pagkatapos ng backup. Docker at bash lang sa host ang kailangan ng drill.

Ang bawat hakbang na ito ay nagpapabagsak sa drill:

  1. Kakayahang mag-recover: nare-replay ng pansamantalang cluster ang mga log hanggang sa target at nagbubukas ito.
  2. Pagiging kumpleto: ihambing ang bilang ng row ng bawat talahanayan sa live na database (tooling/restore-drill). Nagpatuloy na ang pagbabago sa live na database mula sa target, kaya maaaring magkaiba ang isang talahanayan nang hanggang sa mas malaki sa 500 row o ikasampu ng laki nito, sa alinmang direksiyon (napag-iiwanan ito ng mga write, samantalang mas marami ang nananatili sa restore kapag may mga deletion); hindi nawawalang talahanayan ang partition na ginawa pagkatapos ng target. Palawakin ang allowance sa mas abalang install gamit ang QUIRE_DRILL_MAX_BEHIND at QUIRE_DRILL_MAX_DRIFT_RATIO. Nabibigo ito kapag may nawawala o naubos na talahanayan.
  3. Integridad: nabe-verify ang audit hash chain sa na-restore na kopya.
  4. Pagiging magagamit: nakakabasa ang application role gamit ang row-level security.
  5. Oras: mula simula hanggang maging berde, ayon sa QUIRE_DRILL_RTO_SECONDS (default 3600).

Hindi ito kailanman sumusulat sa live na database o mga volume nito: read-only ang pagkaka-mount ng mga backup at WAL volume at inaalis ang pansamantalang cluster sa katapusan, pumasa man o bumagsak.

Itakda ang QUIRE_DRILL_REPORT sa isang path upang maisulat doon ang JSON report, pumasa man o bumagsak, at patakbuhin ito ayon sa iskedyul mula sa Docker host:

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

Patakbuhin ito buwan-buwan at bago ang bawat upgrade. Pinipigilan ng pumalyang drill ang upgrade. Kada quarter, magpatest ng tunay na point-in-time restore sa ekstrang host sa taong hindi sumulat ng runbook na ito, gamit lamang ang dokumentong ito.

Mga file

Nasa files volume ang mga lokal na file. I-backup ito kasabay ng database, sa parehong oras, at i-restore ang dalawa nang magkasama:

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 .

Sa object storage, i-on ang bucket versioning at panatilihin nang 35 araw ang mga hindi na pinakabagong bersiyon; ang bucket mismo ang bahala sa point-in-time recovery ng mga file.

Nabigasyon

Mag-type para maghanap…

↑↓ mag-navigate↵ pumiliEsc isara