Add configurable retries for locked SQLite backups
docker-build / Build image (push) Successful in 11s

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.
This commit is contained in:
2026-09-01 12:15:38 +07:00
parent 3183bf5267
commit f166f95b44
5 changed files with 58 additions and 6 deletions
+3
View File
@@ -3,6 +3,9 @@ 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
+22
View File
@@ -14,6 +14,9 @@ services:
- DELETE_AFTER
- KEEP_LAST
- TZ
- SQLITE_TIMEOUT
- BACKUP_RETRIES
- BACKUP_RETRY_DELAY
- MINIO_PATH
- MINIO_ACCOUNT_ID
- MINIO_APPLICATION_KEY
@@ -31,6 +34,9 @@ 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
@@ -58,6 +64,22 @@ HEALTHCHECK_UUID=00000000-0000-0000-0000-000000000000
`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` пуст — пинги не отправляются, бекап работает как обычно.
+1 -1
View File
@@ -1 +1 @@
1.2.1
1.2.2
+3
View File
@@ -14,6 +14,9 @@ services:
- DELETE_AFTER
- KEEP_LAST
- TZ
- SQLITE_TIMEOUT
- BACKUP_RETRIES
- BACKUP_RETRY_DELAY
- MINIO_PATH
- MINIO_ACCOUNT_ID
- MINIO_APPLICATION_KEY
+29 -5
View File
@@ -27,7 +27,7 @@ hc_ping() {
|| log "WARN: healthcheck ping failed: ${url}"
}
cleanup() { rm -f "$DUMP" "$ARCHIVE"; }
cleanup() { rm -f "$DUMP" "${DUMP}-journal" "${DUMP}-wal" "${DUMP}-shm" "$ARCHIVE"; }
fail() {
log "ERROR: $*"
@@ -43,10 +43,34 @@ fail() {
hc_ping start
log "backup started: $DB_FILE -> ${DEST}${BACKUP_FILE}"
# 1. Consistent snapshot
rm -f "$DUMP"
sqlite3 "$DB_FILE" ".backup '$DUMP'" || fail "sqlite3 .backup failed"
[ -s "$DUMP" ] || fail "dump is empty"
# 1. Consistent snapshot.
# A live writer (grafana, or a previous pod that has not died yet) holds the
# write lock, so `.backup` gets "database is locked". `.timeout` makes sqlite
# wait instead of failing instantly; the loop covers longer write bursts.
SQLITE_TIMEOUT="${SQLITE_TIMEOUT:-60000}"
BACKUP_RETRIES="${BACKUP_RETRIES:-5}"
BACKUP_RETRY_DELAY="${BACKUP_RETRY_DELAY:-30}"
ATTEMPT=1
while : ; do
rm -f "$DUMP" "${DUMP}-journal" "${DUMP}-wal" "${DUMP}-shm"
ERR=$(sqlite3 -cmd ".timeout $SQLITE_TIMEOUT" "$DB_FILE" ".backup '$DUMP'" 2>&1)
RC=$?
if [ "$RC" -eq 0 ] && [ -s "$DUMP" ]; then
break
fi
[ -n "$ERR" ] || ERR="dump is empty"
if [ "$ATTEMPT" -ge "$BACKUP_RETRIES" ]; then
fail "sqlite3 .backup failed after ${ATTEMPT} attempts: $ERR"
fi
log "WARN: .backup attempt ${ATTEMPT}/${BACKUP_RETRIES} failed: ${ERR}; retrying in ${BACKUP_RETRY_DELAY}s"
sleep "$BACKUP_RETRY_DELAY"
ATTEMPT=$((ATTEMPT + 1))
done
# 2. Verify the snapshot before shipping it anywhere
INTEGRITY=$(sqlite3 "$DUMP" "PRAGMA integrity_check;" 2>&1) \