흔한 실수는 두 가지입니다. 파일 없이 복구된 데이터베이스는 깨진
페이지를 렌더링합니다. QUIRE_MASTER_KEY 없이 복구된 데이터베이스는
보관 중인 SSO, 웹훅, 연동 자격 증명을 복호화할 수 없습니다. 마스터 키
로테이션이 미해결 없이 끝날 때까지(key-rotation.md),
은퇴한 키도 여기에 포함됩니다. 둘 다 백업의 일부입니다.
백업 만들기
클러스터 전체의 기준 백업:
docker compose -f docker/compose.yaml --profile backup run --rm backup
가장 최신한 QUIRE_BACKUP_KEEP개의 기준 백업(기본 5)을 남기고, 가장 오래된
것이 더 이상 필요로 하지 않는 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
또는 스택이 예약하게 하세요. backup 프로필은 backup-scheduler(매
QUIRE_BACKUP_INTERVAL_HOURS마다 기준 백업, 기본 24)와 다음에 설명할
backup-offsite를 실행합니다.
docker compose -f docker/compose.yaml --profile backup up -d
호스트 밖의 암호화 사본
두 볼륨 모두 데이터베이스와 같은 호스트에 있고, 실패한 기계의 백업은
백업이 아닙니다. backup-offsite는 모든 기준 백업과 보관된 WAL 세그먼트를
스토리지 포트를 통해 암호화된 별도 저장소로 복사하고, 보존 규칙에 따라
거기에 보관합니다.
암호화.QUIRE_BACKUP_ENCRYPTION_KEY(또는
QUIRE_BACKUP_ENCRYPTION_KEY_FILE이 지정하는 파일)로 하는 AES-256-GCM.
32바이트이며 openssl rand -hex 32로 만듭니다. 각 파일은 고유한 nonce와
인증 태그를 가지므로 키 없이는 읽을 수 없고 어떤 변경도 감지됩니다. 키는
QUIRE_MASTER_KEY와 함께, 이 호스트와 백업 저장소 모두에서 멀리 두세요.
키가 없으면 복구는 없습니다.
어디에.QUIRE_BACKUP_STORAGE_DRIVER는 s3, azure, local
(QUIRE_BACKUP_STORAGE_ROOT의 마운트된 원격 디스크) 중 하나입니다. 설정은
파일 저장소 설정에 QUIRE_BACKUP_ 접두어를 붙인 것입니다.
QUIRE_BACKUP_S3_ENDPOINT, QUIRE_BACKUP_S3_BUCKET,
QUIRE_BACKUP_S3_ACCESS_KEY_ID 등이 있습니다. 파일과 다른 버킷을, 가능하면
다른 계정을 쓰고, 제공자가 허하면 쓸 수는 있으나 삭제할 수 없는 자격
증명을 쓰세요.
보존. 가장 최신한 QUIRE_BACKUP_OFFSITE_KEEP개의 기준 백업(기본은
QUIRE_BACKUP_KEEP, 없으면 7)과 그중 가장 오래된 것이 필요로 하는 WAL.
더 오래된 세트와 세그먼트는 저장소에서 삭제됩니다.
언제.QUIRE_BACKUP_SHIP_INTERVAL_SECONDS마다(기본 300). 전송은
멱등합니다. 이미 저장된 것은 건너뛰며, 기준 백업은 마지막에 매니페스트가
쓰여야 저장으로 칩니다.
같은 명령을 직접 실행할 수도 있습니다.
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts shipdocker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify
새 호스트에서 복구하려면 먼저 세트를 가져온 뒤, 아래 단계를 따라가되
내려받은 디렉터리로 pgbackup 볼륨을, 내려받은 wal-archive로 pgwal을
대체하세요.
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
시점 복구
데이터 손실 뒤에 사용하세요. 잘못된 가져오기, 삭제된 코스, 되돌려야 하는
축소 마이그레이션이 그렇습니다. 라이브 데이터베이스를 대체하므로, 먼저
아래 훈련으로 연습하세요.
목표 시각을 고르세요. UTC로, 손상 직전 시점입니다.
2026-09-24 09:30:00+00. 감사 로그(/admin/audit)가 그 순간을 대개
보여줍니다.
쓰는 것을 모두 멈추세요:
docker compose -f docker/compose.yaml stop web content worker scheduler collab
손상된 클러스터를 보관하세요. 복구가 검증될 때까지:
docker compose -f docker/compose.yaml stop postgresdocker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
목표보다 오래된 가장 최신한 기준 백업을 데이터 볼륨에 풀고,
대상 지정 복구를 요청하세요:
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'
복구하세요: 복구 설정과 함께 Postgres를 한 번 시작합니다. 일반 파일을
건드리지 않도록 Compose 오버라이드로 합니다.
오버라이드는 명령 전체를 대체하므로 복구가 의존하는 두 설정을 반복합니다.
max_connections는 주 서버보다 낮지 않아야 하며(그렇지 않으면
“insufficient parameter settings”로 복구가 중단됩니다), 마운트된
pg_hba.conf도 포함됩니다.
docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgresdocker compose -f docker/compose.yaml logs -f postgres # wait for "database system is ready"
아무도 들어가기 전에 확인하세요. 감사 체인
(docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts)과
잃어버린 데이터가 돌아왔는지 확인합니다.
정상으로 돌아오세요: docker compose -f docker/compose.yaml up -d.
아카이빙을 켠 채 Postgres를 재시작하며 새 WAL 타임라인이 시작됩니다.
곧바로 새로운 기준 백업을 만드세요.
프로젝트 이름 quire가 각 볼륨의 접두어가 됩니다. 정확한 이름은
docker volume ls로 확인하세요.
예약된 검증 훈련
backup-offsite는 QUIRE_BACKUP_DRILL_INTERVAL_HOURS마다(기본 168, 주
1회) 훈련도 실행하며, 하나가 실패하면 다음 주기에 다시 실행합니다. 최신한
호스트 밖 기준 백업과 그 뒤의 모든 WAL 세그먼트를 가져와 각각을 복호화하고
(키가 여전히 열리고 아무것도 변조되지 않았음을 증명), 각 파일을 매니페스트와
비교하며, 보관함이 Postgres 데이터 디렉터리인지 확인하고, 백업 이후의 WAL에
공백이 없는지 확인합니다. 보고서는 reports/drill-<time>.json으로 저장소에
그리고 서비스 로그에 쓰입니다. 실패한 훈련은 파일 이름이나 처음 누락된
세그먼트를 명시합니다.
복구 훈련
한 번도 복구되지 않은 백업은 백업이 아닙니다. 훈련은 목표 이전에 만든
최신한 완전한 기준 백업과 WAL 보관함을, 라이브와 아무것도 공유하지 않는
임시 Postgres에 복구하고 그 결과를 증명합니다.
docker/scripts/restore-drill.sh # to ninety minutes agodocker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
목표는 정확히 그 형태의 UTC입니다. 목표보다 오래된 기준 백업과 그 이후의
보관된 WAL이 필요합니다. 새 설치라면 기준 백업을 만들고 다음 보관 세그먼트
(쓰기가 있으면 최대 1분)를 기다린 뒤, 백업 이후의 시각을 목표로 고르세요.
훈련에는 호스트의 Docker와 bash만 필요합니다.
각 단계는 훈련을 실패시킵니다.
복구 가능성: 임시 클러스터가 목표까지 재생되고 열리는지.
완전성: 라이브 데이터베이스와 각 테이블의 행 수 비교
(tooling/restore-drill). 라이브 데이터베이스는 목표 이후로 진행되었으므로
테이블은 500행과 크기의 10분의 1 중 큰 만큼, 어느 방향으로든 다를 수
있습니다(쓰기는 뒤처지게 하고, 삭제는 복구본이 더 많게 합니다). 목표
이후에 만들어진 파티션은 잃어버린 테이블이 아닙니다. 더 바쁜 설치에서는
QUIRE_DRILL_MAX_BEHIND와 QUIRE_DRILL_MAX_DRIFT_RATIO로 허용치를
넓히세요. 사라졌거나 비어 있는 테이블은 실패합니다.