---
title: "Dublēšana, atjaunošana punkta laikā un atjaunošanas mācība"
description: "Dublējiet Quire, atjaunojiet to līdz noteiktam laika punktam un pierādiet to ar atjaunošanas mācību."
image: "https://docs.quirelms.com/og.png"
---

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

# Dublēšana, atjaunošana punkta laikā un atjaunošanas mācība

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

Konstrukcija ir `docs/architecture/23-ops.md` 8. sadaļa. Šī ir darbību lapa
Docker Compose produktam. Tā ir uzrakstīta tā, lai tai sekotu cilvēks, kas to
neuzrakstīja; ja solis ir neskaidrs, tas ir šī dokumenta defekts.

## Kas tiek aizsargāts un kā <!--quire:what-is-protected-and-how-->

| Resurss | Kā | Kur |
| --- | --- | --- |
| Datubāze | WAL arhivēts nepārtraukti, vismaz ik pēc 60 sekundēm, no pirmās ielādes | `pgwal` sējums |
| Datubāze | Bāzes dublējumi ar `pg_basebackup`, pēc noklusējuma katru dienu (`backup-scheduler`) | `pgbackup` sējums |
| Datubāze | Bāzes dublējumu un WAL šifrētas kopijas ik pēc piecām minūtēm (`backup-offsite`) | Atsevišķa glabātuve, kuru nosauci |
| Faili | `files` sējums. Kopē to ar sava servera dublēšanas rīku vai izmanto versijām objektu glabātuvi | `files` sējums |
| Noslēpumi | `docker/.env`, vispirms `QUIRE_MASTER_KEY` (un jebkurš joprojām lietojamais `QUIRE_MASTER_KEY_RETIRED`), `QUIRE_BACKUP_ENCRYPTION_KEY` un `docker/secrets/audit-signing-key.pem` | Glabā kopiju ārpus šī servera |
| Meklēšanas indeksi, keši, renderējumi | Netiek dublēti; tiek pārbūvēti | |

Mērķi: atgūšanas punkts 60 sekunžu robežās pēc kļūmes un atjaunošana 60 minūšu
laikā 500 GB datubāzei.

Divas kļūdas ir izplatītas. Datubāze, kas atjaunota **bez tās failiem**, rāda
bojātas lapas. Datubāze, kas atjaunota **bez `QUIRE_MASTER_KEY`**, nevar
atsifrēt tajā glabātos SSO, tīmekļa āķu un integrāciju akreditācijas datus; līdz
galvenās atslēgas maiņa nav pabeigta bez nekā neatrisināta
([key-rotation.md](/lv/ops/key-rotation/)), tas ietver arī atsauktās atslēgas. Abas
ir dublējuma sastāvdaļa.

## Dublējumu uzņemšana <!--quire:taking-backups-->

Bāzes dublējums visai kopklasei:

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

Tas patur jaunākos `QUIRE_BACKUP_KEEP` bāzes dublējumus (noklusējums 5) un
izgriež WAL, ko vecākajam vairs nevajag, tāpēc arhīvs neaug bez robežas.
Plānojiet to katru dienu ar cron vai systemd taimeri uz servera:

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

Vai ļaujiet kopklasei to plānot: profils `backup` palaiž `backup-scheduler`,
kas ik pēc `QUIRE_BACKUP_INTERVAL_HOURS` (noklusējums 24) ņem bāzes dublējumu,
un `backup-offsite`, kas aprakstīts tālāk.

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

## Šifrētas kopijas ārpus servera <!--quire:encrypted-off-host-copies-->

Abi sējumi atrodas uz tā paša servera kur datubāze, un dublējums uz mašīnas,
kas izgāzās, nav dublējums. `backup-offsite` nokopē katru bāzes dublējumu un
katru arhivēto WAL segmentu uz atsevišķu glabātuvi caur glabātuves portu,
šifrētus, un tur tos glabā paturēšanas noteikumu dēļ:

- **Šifrēšana.** AES-256-GCM ar `QUIRE_BACKUP_ENCRYPTION_KEY` (vai failu, ko
  nosaka `QUIRE_BACKUP_ENCRYPTION_KEY_FILE`): 32 baiti, no
  `openssl rand -hex 32`. Katram failam ir sava nonce un autentifikācijas
  žetons, tāpēc bez atslēgas kopija nav lasāma un jebkura tās maiņa tiek
  atklāta. Glabā atslēgu kopā ar `QUIRE_MASTER_KEY`, prom no šī servera un prom
  no dublējumu glabātuves. Bez atslēgas atjaunošanas nav.
- **Kur.** `QUIRE_BACKUP_STORAGE_DRIVER` ir `s3`, `azure` vai `local`
  (montēts attālināts disks vietnē `QUIRE_BACKUP_STORAGE_ROOT`). Iestatījumi
  ir tie paši, kas failu glabātuvei, ar `QUIRE_BACKUP_` prefiksu:
  `QUIRE_BACKUP_S3_ENDPOINT`, `QUIRE_BACKUP_S3_BUCKET`,
  `QUIRE_BACKUP_S3_ACCESS_KEY_ID` un tā tālāk. Izmantojiet citu spaini un,
  vēlams, citu kontu nekā failiem, ar akreditācijas datiem, kas drīkst rakstīt,
  bet ne dzēst, ja pakalpojums to ļauj.
- **Paturēšana.** Jaunākie `QUIRE_BACKUP_OFFSITE_KEEP` bāzes dublējumi
  (noklusējums `QUIRE_BACKUP_KEEP`, citādi 7) un WAL, ko vajag vecākajam no
  tiem; vecāki kopu un segmenti tiek no glabātuves izdzēsti.
- **Kad.** Katrs `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (noklusējums 300).
  Nosūtīšana ir idempota: kas jau ir saglabāts, tiek izlaists, un bāzes
  dublējums tiek uzskatīts par saglabātu tikai tad, kad tā manifests ir
  pierakstīts, pēdējais.

To pašu komandu var palaist ar roku:

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

Lai atjaunotu jaunā serverī, vispirms atvediet kopu atpakaļ, pēc tam sekojiet
tālāk esošajiem soļiem, vietā liekot `pgbackup` sējumu un vietā liekot
atvilktos `wal-archive` sējumam `pgwal`:

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

## Atjaunošana līdz noteiktam laika punktam <!--quire:restoring-to-a-point-in-time-->

To izmanto pēc datu zaudēšanas: sliktas importes, izdzēsta kursa,
sašaurinājuma migrācijas, kuru vajag atsaukt. Tā aizstāj dzīvo datubāzi, tāpēc
vispirms to izmēģiniet ar zemāk esošo mācību.

1. **Izvēlieties mērķa laiku**, UTC, tieši pirms bojājuma:
   `2026-09-24 09:30:00+00`. Audita žurnāls (`/admin/audit`) parasti rāda šo
   brīdi.
2. **Apturiet visu, kas raksta**:
   `docker compose -f docker/compose.yaml stop web content worker scheduler collab`
3. **Paturiet bojāto kopklasi**, līdz atjaunošana ir pārbaudīta:
   ```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. **Atspiediet jaunāko bāzes dublējumu, kas ir vecāks par mērķi**, datu
   sējumā un pieprasiet mērķētu atgūšanu:
   ```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. **Atgūstiet**: palaidiet Postgres vienreiz ar atgūšanas iestatījumiem, no
   Compose pārklājuma, lai parastais fails paliktu neskarts:
   ```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]
   ```
   Pārklājums aizstāj visu komandu, tāpēc tajā atkārtojas abi iestatījumi, no
   kuriem ir atkarīga atgūšana: `max_connections` ne zemāks par galvenā
   servera (citādi atgūšana pārtrūkst ar "insufficient parameter settings") un
   montētais `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. **Pārbaudiet to**, pirms ļaujat kādam ienākt: audita ķēdi
   (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`)
   un to, ka zaudētie dati ir atpakaļ.
7. **Atgriezieties normālā stāvoklī**:
   `docker compose -f docker/compose.yaml up -d`. Tas atkal startē Postgres ar
   ieslēgtu arhivēšanu, un sākas jauna WAL laika līnija. Uzreiz paņemiet svaigu
   bāzes dublējumu.

Projekta nosaukums `quire` ir prefikss katram sējumam; `docker volume ls` rāda
precīzos nosaukumus.

## Plānotā pārbaudes mācība <!--quire:the-scheduled-verification-drill-->

`backup-offsite` arī katru `QUIRE_BACKUP_DRILL_INTERVAL_HOURS` (noklusējums 168,
reizi nedēļā) palaiž mācību un atkal nākamajā reizē pēc neveiksmes. Tā atvelk
jaunāko bāzes dublējumu ārpus servera un katru pēc tam sekojošo WAL segmentu,
katru atšifrē (kas pierāda, ka atslēga joprojām tos atver un nekas nav
mainīts), salīdzina katru failu ar tā manifestu, pārbauda, ka arhīvs ir
Postgres datu direktorijs, un pārbauda, ka WAL no dublējuma uz priekšu nav ar
caurumiem. Atskaite tiek rakstīta glabātuvē kā
`reports/drill-<time>.json` un pakalpojuma žurnālā; neizdevusies mācība nosauc
failu vai pirmo trūkstošo segmentu.

## Atjaunošanas mācība <!--quire:the-restore-drill-->

Dublējums, kas nekad nav atjaunots, nav dublējums. Mācība atjauno jaunāko
pilno bāzes dublējumu, kas uzņemts pirms mērķa, plus WAL arhīvu, pagaidu
Postgres, kas ar dzīvo nedalās neko, un pierāda rezultātu:

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

Mērķis ir UTC tieši šajā formā. Tam vajag bāzes dublējumu, kas ir vecāks par to,
un arhivētu WAL aiz tā: jaunā uzstādīšanā paņemiet bāzes dublējumu un pagaidiet
nākamo arhivēto segmentu (ar ierakstījumiem ne vairāk par minūti), pirms
izvēlaties mērķi pēc dublējuma. Mācībai uz servera vajag Docker un bash, neko
citu.

Katrs solis izsauc mācības neveiksmi:

1. **Atgūstamība**: pagaidu kopklase atsāk līdz mērķim un atveras.
2. **Pilnība**: katra tabulu rindu skaits pret dzīvo datubāzi
   (`tooling/restore-drill`). Dzīvā datubāze kopš mērķa ir virzījusies
   uz priekšu, tāpēc tabula var atšķirties par lielāko no 500 rindām un
   desmitdaļas no tās izmēra, abos virzienos (ierakstījumi to padara
   atpaliekošu, dzēšanu — atjaunošanā var būt vairāk); pēc mērķa izveidota
   daļa nav zaudēta tabula. Platāku atļauju aizņemtākā uzstādīšanā iestatiet
   ar `QUIRE_DRILL_MAX_BEHIND` un `QUIRE_DRILL_MAX_DRIFT_RATIO`. Trūkstoša vai
   iztukšota tabula ir neveiksme.
3. **Integritāte**: audita maiņas ķēda pārbaudās atjaunotajā kopijā.
4. **Lietojamība**: lietojumprogrammas loma lasa caur rindu līmeņa drošību.
5. **Laiks**: no sākuma līdz zaļajam, pret `QUIRE_DRILL_RTO_SECONDS`
   (noklusējums 3600).

Tā nekad neraksta dzīvajā datubāzē vai tās sējumos: dublējumu un WAL sējumus
montē tikai lasāmajā režīmā, un pagaidu kopklase tiek noņemta beigās, vai nu
veiksmes, vai neveiksmes gadījumā.

Iestatiet `QUIRE_DRILL_REPORT` uz ceļu, lai tiktu uzrakstīta JSON atskaite, vai
nu veiksmes, vai neveiksmes gadījumā, un palaidiet to pēc grafika no Docker
servera:

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

Palaidiet to ik pēc mēneša un pirms katra jauninājuma. Neizdevusies mācība
bloķē jaunināšanu. Reizi ceturksnī ļauj kādam, kas šo darbību lapu neuzrakstīja,
veikt īstu atjaunošanu punkta laikā uz rezerves servera, izmantojot tikai šo
dokumentu.

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

Vietējie faili dzīvo `files` sējumā. Dublējiet tos kopā ar datubāzi, vienā
laikā, un atjaunojiet abus kopā:

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

Ar objektu glabātuvi ieslēdziet spaiņu versijēšanu un glabājiet 35 dienas
neaktīvās versijas; failu atjaunošana punkta laikā tad ir paša spaiņa.

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