Nei de ynhâld gean

Reservekopy, herstel op in tiidstip en de restore drill

Meitsje reservekopyen fan Quire, set it werom nei in tiidstip en bewize dat mei de restore drill.

As Markdown besjen

It ûntwerp stiet yn docs/architecture/23-ops.md seksje 8. Dit is de runbook foar it Docker Compose-produkt. De hantlieding is skreaun om folge te wurden troch ien dy’t him net skreaun hat; as in stap ûndúdlik is, is dat in mankemint yn dit dokumint.

Wat beskerme wurdt en hoe

Asset Hoe Wêr
De database WAL wurdt hieltyd argivearre, maksimaal elke 60 sekonden, fan de earste boot ôf pgwal-volume
De database Base backups mei pg_basebackup, standert deistich (backup-scheduler) pgbackup-volume
De database Fersifere kopyen fan base backups en WAL, elke fiif minuten (backup-offsite) In aparte store dy’tst sels neamst
Bestannen It files-volume. Kopiearje mei it reservekopy-ark fan dyn host of brûk ferzjonearre objektopslach files-volume
Geheimen docker/.env, benammen QUIRE_MASTER_KEY (en elke QUIRE_MASTER_KEY_RETIRED dy’t noch brûkt wurdt), QUIRE_BACKUP_ENCRYPTION_KEY en docker/secrets/audit-signing-key.pem Bewarje in kopy bûten dizze host
Sykyndeksen, caches en renditions Gjin reservekopy; wurde opnij makke

Doelen: in herstelpunt binnen 60 sekonden fan de fal ôf en in restore binnen 60 minuten foar in database fan 500 GB.

Twa flaters komme faak foar. In database dy’t weromset is sûnder de bestannen, jout brutsen siden. In database dy’t weromset is sûnder QUIRE_MASTER_KEY, kin de SSO-, webhook- en yntegraasjebewizen yn de database net ûntsiferje; oant in master-keyrotaasje klear is sûnder net-oploste wearden (key-rotation.md), jildt dit ek foar eardere kaaien. Beide hearre by de reservekopy.

Reservekopyen meitsje

In base backup fan it hiele cluster:

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

It behâldt de nijste QUIRE_BACKUP_KEEP base backups (standert 5) en snoeit WAL dy’t de âldste net mear nedich hat, sadat it argyf net ûnbeheind groeie kin. Plan it deistich mei cron of in systemd-timer op de host:

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

Of lit de stack it planne: it backup-profile draait backup-scheduler, dat elke QUIRE_BACKUP_INTERVAL_HOURS oeren in base backup makket (standert 24), en ek backup-offsite, hjirnei beskreaun.

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

Fersifere kopyen bûten de host

Beide volumes steane op deselde host as de database; in reservekopy op in mislearre masine is gjin reservekopy. backup-offsite kopiearret elke base backup en elk argivearre WAL-segmint fia de storage-port fersifere nei in aparte store en hâldt se dêr neffens de bewaartermyn:

  • Fersifering. AES-256-GCM mei QUIRE_BACKUP_ENCRYPTION_KEY (of it bestân neamd troch QUIRE_BACKUP_ENCRYPTION_KEY_FILE): 32 byte fan openssl rand -hex 32. Elk bestân hat in eigen nonce en autentikaasjetag; sûnder kaai is de kopy net te lêzen en elke feroaring wurdt ûntdutsen. Bewarje de kaai tegearre mei QUIRE_MASTER_KEY, fier fan dizze host en de reservekopy-store. Sûnder de kaai is der gjin restore.
  • Wêr. QUIRE_BACKUP_STORAGE_DRIVER is s3, azure of local (in mounted remote disk op QUIRE_BACKUP_STORAGE_ROOT). Dit binne ynstellingen fan bestânsopslach mei prefix QUIRE_BACKUP_: QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET, QUIRE_BACKUP_S3_ACCESS_KEY_ID ensafuorthinne. Brûk in oare bucket en leafst in oar akkount as foar de bestannen, mei bewiis dat skriuwe mar net wiskje kin as de provider dat tastiet.
  • Bewartermyn. De nijste QUIRE_BACKUP_OFFSITE_KEEP base backups (standert QUIRE_BACKUP_KEEP, oars 7) en de WAL dy’t de âldste dêrfan nedich hat; âldere sets en segminten wurde út de store wiske.
  • Wannear. Elke QUIRE_BACKUP_SHIP_INTERVAL_SECONDS (standert 300). It ferstjoeren is idempotint: al opsleine gegevens wurde oerslein, en in base backup telt pas as opslein neidat syn manifest as lêste skreaun is.

Deselde kommando’s kinne mei de hân rinne:

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

Om op in nije host te herstellen, helje earst in set werom; folgje dan de stappen hjirûnder mei de ophelle map ynstee fan it pgbackup-volume en it ophelle wal-archive ynstee fan pgwal:

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

Herstellen nei in tiidstip

Brûk dit nei gegevensferlies: in ferkearde ymport, in wiske kursus of in contractmigration dy’t weromdraaid wurde moat. It ferfangt de live database; oefenje dêrom earst mei de drill hjirûnder.

  1. Kies it doelsstip, yn UTC, krekt foar de skea: 2026-09-24 09:30:00+00. It auditlogboek (/admin/audit) lit dat momint meastal sjen.
  2. Stopje alles dat skriuwt: docker compose -f docker/compose.yaml stop web content worker scheduler collab
  3. Hâld it skansearre cluster oant de restore kontrolearre is:
    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. Pak de nijste base backup foar it doelsstip út yn it datavolume en freegje herstel nei it opjûne doel oan:
    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. Herstelle: start Postgres ien kear mei de herstelynstellingen út in Compose-override, sadat it gewoane bestân net feroaret:
    # 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]
    De override ferfangt it hiele kommando, dus werhellet de twa ynstellingen dêr’t herstel fan ôfhinget: max_connections mei net leger wêze as op de primêre database (oars brekt herstel ôf mei “insufficient parameter settings”) en it mountte 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. Kontrolearje it foardatst immen talittest: de auditketen (docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts) en oft de ferlerne gegevens werom binne.
  7. Gean werom nei normaal: docker compose -f docker/compose.yaml up -d. Dit start Postgres opnij mei archiving oan; in nije WAL-tiidline begjint. Nim fuort in nije base backup.

De projektnamme quire stiet foar elke volumenaam; docker volume ls lit de eksakte nammen sjen.

Plande ferifikaasjedrill

backup-offsite docht ek elke QUIRE_BACKUP_DRILL_INTERVAL_HOURS in drill (standert 168, wykliks), en op de folgjende trochrin nei in mislearre drill nochris. It hellet de nijste base backup bûten de host op mei elk WAL-segmint dêrnei; ûntsiferet elk (wat bewiist dat de kaai it noch iepenet en neat feroare is), ferliket elk bestân mei it manifest, kontrolearret dat it argyf in Postgres-datamap is en dat der gjin gat sit yn WAL fanôf de backup. It rapport wurdt as reports/drill-<time>.json yn de store en yn it logboek fan de tsjinst skreaun; by mislearring neamt de drill it bestân of it earste ûntbrekkende segmint.

De restore drill

In reservekopy dy’t nea weromset is, is gjin reservekopy. De drill set de nijste folsleine base backup fan foar it doelsstip plus it WAL-argyf werom yn in tydlike Postgres dy’t neat mei de live database dielt, en bewiist it resultaat:

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

It doelsstip is UTC yn krekt dy foarm. Der moat in âldere base backup wêze en argivearre WAL dêrnei: nim by in nije ynstallaasje in base backup en wachtsje op it folgjende argivearre segmint (maksimaal in minút by writes) foardatst in doelsstip nei de backup kiest. De drill hat allinnich Docker en bash op de host nedich.

Elke stap lit de drill mislearje:

  1. Werom te heljen: it tydlike cluster spilet op nei it doelsstip en iepenet.
  2. Folslein: tal rigen fan elke tabel tsjin de live database (tooling/restore-drill). De live database is sûnt it doelsstip fierder gien, dus kin it tal rigen ferskille mei de grutste fan 500 rigen of in tsiende fan de tabelgrutte, yn beide rjochtingen (writes meitsje it leger; by wiskjen hat de restore mear). In partition oanmakke nei it doelsstip is gjin ûntbrekkende tabel. Ferbreedzje de romte op in drokkere ynstallaasje mei QUIRE_DRILL_MAX_BEHIND en QUIRE_DRILL_MAX_DRIFT_RATIO. In ûntbrekkende of lege tabel lit de test mislearje.
  3. Yntakt: de audit-hashketen wurdt kontrolearre op de weromsette kopy.
  4. Brûkber: de applikaasjerol kin lêze mei row-level security.
  5. Tiid: fan start oant grien, neffens QUIRE_DRILL_RTO_SECONDS (standert 3600).

It skriuwt nea nei de live database of syn volumes: de reservekopy- en WAL-volumes wurde allinnich-lêzen mount en it tydlike cluster wurdt oan de ein fuorthelle, oft de drill slagget of net.

Set QUIRE_DRILL_REPORT op in paad om in JSON-rapport (sukses of mislearring) te skriuwen en plan it út de Docker-host wei:

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

Doch dit alle moannen en foar elke upgrade. In mislearre drill hâldt in upgrade tsjin. Ien kear it fearnsjier litst ien dy’t dizze runbook net skreaun hat in echte point-in-time- restore dwaan op in ekstra host, mei allinnich dit dokumint.

Bestannen

Lokale bestannen steane yn it files-volume. Meitsje der tagelyk mei de database in reservekopy fan en set beide tegearre werom:

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 .

Skeakelje by objektopslach bucketferzjes yn en bewarje net-aktuele ferzjes 35 dagen; point-in-time-restore fan bestannen wurdt dan troch de bucket sels fersoarge.

Navigaasje

Typ om te sykjen…

↑↓ navigearje↵ selektearjeEsc slute