Add SQLITE_TIMEOUT, BACKUP_RETRIES, and BACKUP_RETRY_DELAY environment variables to handle "database is locked" errors during backup. Retry the sqlite3 .backup command with configurable timeouts and delays, and clean up any WAL/SHM files that may interfere with subsequent attempts. Bump version to 1.2.2.
4.7 KiB
бекапирование sqlite в minio
services:
sqlite_backup:
image: git.realmanual.ru/pub/sqlite-backup-s3
container_name: sqlite_backup
restart: always
volumes:
- ./var/lib/data/:/data
environment:
- DB_FILE
- CRONTAB
- DELETE_AFTER
- KEEP_LAST
- TZ
- SQLITE_TIMEOUT
- BACKUP_RETRIES
- BACKUP_RETRY_DELAY
- MINIO_PATH
- MINIO_ACCOUNT_ID
- MINIO_APPLICATION_KEY
- MINIO_ENDPOINT
- MINIO_LOCATION
- HEALTHCHECK_URL
- HEALTHCHECK_UUID
пример .env
DB_FILE=/data/db.sqlite
CRONTAB="00 06 * * *"
DELETE_AFTER=10
KEEP_LAST=10
TZ=Asia/Novosibirsk
SQLITE_TIMEOUT=60000
BACKUP_RETRIES=5
BACKUP_RETRY_DELAY=30
MINIO_PATH=myminio://sqlite/master/
MINIO_ACCOUNT_ID=account
MINIO_APPLICATION_KEY=key
MINIO_ENDPOINT=https://s3.domain.ru
MINIO_LOCATION=ru-nsk
# необязательно: пинг в healthchecks.io (в т.ч. self-hosted)
HEALTHCHECK_URL=https://hc.domain.ru/ping
HEALTHCHECK_UUID=00000000-0000-0000-0000-000000000000
- myminio - системно! не менять
- проверьте настройки локации, не должно быть пусто
- DELETE_AFTER - в днях
- KEEP_LAST - минимальное число копий, которые сохраняются всегда
Ротация
Удаление старых бекапов работает по двум условиям одновременно:
- бекап старше
DELETE_AFTERдней - и при этом не входит в
KEEP_LASTсамых свежих копий
То есть последние KEEP_LAST бекапов не удаляются никогда, даже если они старше DELETE_AFTER дней. Это защищает от потери всех копий, если контейнер долго стоял и новые бекапы не создавались.
KEEP_LAST по умолчанию 10, если переменная не задана.
Заблокированная база
Если базу пишет живой процесс (grafana, или не добитый старый под), sqlite3 .backup падает с Error: database is locked.
Бекап это переживает: соединение открывается с .timeout SQLITE_TIMEOUT (мс) — sqlite ждёт освобождения блокировки вместо мгновенной ошибки. Если за это время писатель не отпустил базу, попытка повторяется BACKUP_RETRIES раз с паузой BACKUP_RETRY_DELAY секунд.
| Переменная | Default | Что делает |
|---|---|---|
SQLITE_TIMEOUT |
60000 |
сколько мс ждать блокировку в рамках одной попытки |
BACKUP_RETRIES |
5 |
сколько попыток снять дамп |
BACKUP_RETRY_DELAY |
30 |
пауза между попытками, сек |
Максимальное время ожидания с дефолтами: 60s * 5 + 30s * 4 = 7 минут. После этого — exit 1 и пинг /fail.
Если база залочена постоянно — это не про бекап, а про то, что старый под не умер. Ретраи только прикрывают короткие окна.
Healthchecks
Если HEALTHCHECK_URL пуст — пинги не отправляются, бекап работает как обычно.
Итоговый адрес: ${HEALTHCHECK_URL}/${HEALTHCHECK_UUID}
| Момент | Запрос |
|---|---|
| старт бекапа | .../${HEALTHCHECK_UUID}/start |
дамп снят, проверен PRAGMA integrity_check, запакован, залит в s3, размер в s3 сверен с локальным, старое удалено |
.../${HEALTHCHECK_UUID} |
| любая ошибка на любом шаге | .../${HEALTHCHECK_UUID}/fail |
Для self-hosted инстанса HEALTHCHECK_URL — это адрес его ping-эндпоинта, например https://hc.domain.ru/ping.
Как передаётся окружение в cron
crond из busybox запускает джобы с пустым окружением, поэтому entrypoint.sh
сохраняет env контейнера в /tmp/container.env (права 600), а cron-запись
вызывает /scripts/backup.sh --from-cron, который его подгружает.
Ручной запуск (docker exec ... /scripts/backup.sh) использует то окружение,
с которым его вызвали.