minor fix

This commit is contained in:
marco.locatelli@steamware.net
2026-03-31 12:17:51 +02:00
parent db31f111d3
commit c81f3c4d53
@@ -1,5 +1,5 @@
#!/bin/bash
# Backup logico per-database con retry globale e per-database, atomicità e cleanup
# Backup logico per-database con retry globale e retry per-database
# Sicuro per cluster Galera ad alto carico
# Autore: Marco + Copilot
@@ -12,212 +12,197 @@ set -euo pipefail
# Aumenta i file descriptor disponibili (mariadb-dump può aprirne molti)
ulimit -n 65536
# Directory base dove verranno create le cartelle dei backup logici
# Directory base dei backup logici
BACKUP_BASE=/var/backups/tscale01
# Prefisso per distinguere i backup logici da altri tipi di backup
# Prefisso per distinguere i backup logici
PREFIX=eqn-bck-logic-
# File di log condiviso per tutti i backup MariaDB
# File di log condiviso
LOGFILE=/var/log/mariadb-backup.log
# Lockfile usato da flock per evitare esecuzioni concorrenti dello script
# Lockfile per evitare esecuzioni concorrenti
LOCKFILE=/var/lock/mariadb-backup.lock
# Nome breve del server, utile per distinguere i log in ambienti multi-nodo
# Nome breve del server per i log
SERVER_NAME=$(hostname -s)
# Numero massimo di tentativi globali dellintero backup logico
# Numero massimo di tentativi globali dellintero backup
GLOBAL_MAX_RETRIES=5
# Secondi di attesa tra un tentativo globale e il successivo
# Attesa tra un tentativo globale e il successivo
GLOBAL_RETRY_SLEEP=30
# Numero massimo di tentativi per ogni singolo database (in caso di deadlock 1213)
# Numero massimo di tentativi per ogni database
DB_MAX_RETRIES=10
# Secondi di attesa tra un tentativo e laltro per lo stesso database
# Attesa tra un tentativo e laltro per lo stesso database
DB_RETRY_SLEEP=20
# Percorso del client mariadb (per eseguire query come SHOW DATABASES)
# Percorso del client mariadb
MYSQL_BIN=$(command -v mariadb || true)
# Percorso di mariadb-dump (per eseguire i dump logici)
# Percorso di mariadb-dump
MYSQLDUMP_BIN=$(command -v mariadb-dump || true)
# Opzioni di mariadb-dump ottimizzate per Galera:
# - --single-transaction: snapshot consistente senza lock globali
# - --quick: stream delle righe senza caricare tutto in RAM
# - --skip-lock-tables: evita lock sulle tabelle
# Opzioni ottimizzate per Galera
MYSQLDUMP_OPTS="--user=root --single-transaction --quick --skip-lock-tables"
# Assicura che il logfile esista
# Assicura che il logfile esista e abbia permessi sicuri
touch "$LOGFILE"
# Permessi restrittivi sul logfile (solo root può leggere/scrivere)
chmod 600 "$LOGFILE"
# Funzione che esegue un singolo tentativo completo di backup logico
###############################################################################
# FUNZIONE PRINCIPALE: esegue un singolo tentativo completo di backup logico
###############################################################################
run_backup() {
# Timestamp usato per nominare la directory del backup
# Timestamp per la directory del backup
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
# Directory di destinazione per questo tentativo di backup
# Directory di destinazione del backup
TARGET="$BACKUP_BASE/${PREFIX}${TIMESTAMP}"
# File temporaneo per catturare stderr di mariadb-dump
# File temporaneo per catturare errori di mariadb-dump
TMPLOG=$(mktemp /tmp/mariadb-backup-logic.XXXXXX)
# Crea la directory del backup e imposta owner e permessi
# Crea la directory del backup con permessi sicuri
mkdir -p "$TARGET"
chown mysql:mysql "$TARGET"
chmod 750 "$TARGET"
# Separatore visivo nel logfile
echo "---------------------" >> "$LOGFILE"
# Log di inizio backup logico
# Log di inizio backup
echo "[$(date '+%F %T')] [$SERVER_NAME] START logic backup $TARGET" >> "$LOGFILE"
# Elenco dei database utente (esclude schemi di sistema)
# Elenco dei database utente (escludiamo schemi di sistema)
DB_LIST=$($MYSQL_BIN -N -e "SHOW DATABASES" | grep -vE '^(information_schema|performance_schema|mysql|sys)$' || true)
# Flag globale di fallimento (0 = tutto ok, 1 = almeno un DB fallito)
# Flag di fallimento globale
FAILED=0
# Elenco dei database che hanno avuto errori
FAILED_DBS=""
# Ciclo su ogni database utente
# Ciclo su ogni database
for DB in $DB_LIST; do
# File di destinazione per il dump di questo database
# File di destinazione del dump
DUMPFILE="$TARGET/${DB}.sql"
# Log di inizio dump per il singolo database
# Log di inizio dump
echo "[$(date '+%F %T')] [$SERVER_NAME] START dump database '$DB' -> $DUMPFILE" >> "$LOGFILE"
# Contatore dei tentativi per questo database
# Tentativi per questo database
ATTEMPT=1
# Flag di successo per questo database (0 = non riuscito, 1 = riuscito)
SUCCESS=0
# Ciclo di retry per il singolo database
while [ "$ATTEMPT" -le "$DB_MAX_RETRIES" ]; do
# Pulisce il file temporaneo degli errori
# Pulisce il file temporaneo
: >"$TMPLOG"
# Esegue il dump del database, redirezionando stderr su TMPLOG
# Esegue il dump
if $MYSQLDUMP_BIN $MYSQLDUMP_OPTS "$DB" >"$DUMPFILE" 2>"$TMPLOG"; then
# Dump riuscito: log e segna successo
# Dump riuscito
echo "[$(date '+%F %T')] [$SERVER_NAME] END dump database '$DB' (attempt $ATTEMPT)" >> "$LOGFILE"
SUCCESS=1
# Esce dal ciclo di retry per questo database
break
else
# Dump fallito: log dellerrore e ultime righe del TMPLOG
# Dump fallito
echo "[$(date '+%F %T')] [$SERVER_NAME] ERROR dumping database '$DB' (attempt $ATTEMPT). See $TMPLOG" >> "$LOGFILE"
tail -n 50 "$TMPLOG" >> "$LOGFILE"
# Se lerrore è un deadlock 1213, consideriamo il problema temporaneo e riproviamo
# Deadlock 1213 → retry
if grep -q "Error 1213" "$TMPLOG"; then
echo "[$(date '+%F %T')] [$SERVER_NAME] RETRY for '$DB' in ${DB_RETRY_SLEEP}s due to deadlock (1213)" >> "$LOGFILE"
sleep "$DB_RETRY_SLEEP"
else
# Errore non recuperabile: non ha senso riprovare questo database
# Errore non recuperabile → interrompe retry
echo "[$(date '+%F %T')] [$SERVER_NAME] NON-RETRYABLE error for '$DB'" >> "$LOGFILE"
# Esce dal ciclo di retry per questo database
break
fi
fi
# Incrementa il contatore dei tentativi per questo database
# Incrementa tentativo
ATTEMPT=$((ATTEMPT + 1))
done
# Se dopo tutti i tentativi il database non è stato dumpato con successo
# Se il database non è stato dumpato correttamente
if [ "$SUCCESS" -ne 1 ]; then
# Segna che almeno un database è fallito
FAILED=1
# Aggiunge il nome del database alla lista dei falliti
FAILED_DBS="$FAILED_DBS $DB"
fi
done
# Calcola la dimensione apparente (somma dei byte logici dei file)
# Calcolo dimensioni apparenti e su disco
SIZE_APPARENT_BYTES=$(find "$TARGET" -type f -printf '%s\n' | awk '{s+=$1} END{print s+0}')
# Converte la dimensione apparente in formato leggibile (es. MiB, GiB)
SIZE_APPARENT_HUMAN=$(numfmt --to=iec --suffix=B "$SIZE_APPARENT_BYTES")
# Calcola lo spazio effettivo su disco (byte allocati)
SIZE_DISK_BYTES=$(du -s --block-size=1 "$TARGET" | cut -f1)
# Converte la dimensione su disco in formato leggibile
SIZE_DISK_HUMAN=$(numfmt --to=iec --suffix=B "$SIZE_DISK_BYTES")
# Log delle dimensioni del backup
# Log delle dimensioni
echo "[$(date '+%F %T')] [$SERVER_NAME] SIZE apparent: $SIZE_APPARENT_HUMAN ($SIZE_APPARENT_BYTES bytes)" >> "$LOGFILE"
echo "[$(date '+%F %T')] [$SERVER_NAME] SIZE on-disk: $SIZE_DISK_HUMAN ($SIZE_DISK_BYTES bytes)" >> "$LOGFILE"
# Se tutti i database sono stati dumpati correttamente
# Se tutto è andato bene
if [ "$FAILED" -eq 0 ]; then
# Log di successo complessivo
echo "[$(date '+%F %T')] [$SERVER_NAME] RESULT: OK" >> "$LOGFILE"
# Rimuove il file temporaneo degli errori
rm -f "$TMPLOG"
# Ritorna 0 (successo) al chiamante (ciclo globale)
return 0
else
# Log di fallimento complessivo con elenco dei database problematici
# Fallimento → pulizia directory
echo "[$(date '+%F %T')] [$SERVER_NAME] RESULT: FAILED, databases with errors:$FAILED_DBS" >> "$LOGFILE"
# Log della pulizia della directory incompleta
echo "[$(date '+%F %T')] [$SERVER_NAME] CLEANUP: removing incomplete backup directory $TARGET" >> "$LOGFILE"
# Rimuove la directory del backup incompleto (atomicità: o tutto o niente)
rm -rf "$TARGET"
# Rimuove il file temporaneo degli errori
rm -f "$TMPLOG"
# Ritorna 1 (fallimento) al chiamante (ciclo globale)
return 1
fi
}
###############################################################################
# BLOCCO PRINCIPALE CON FLOCK E RETRY GLOBALE
###############################################################################
(
# FLOCK: evita che due istanze dello script girino contemporaneamente
# Evita esecuzioni concorrenti
flock -n 9 || {
echo "[$(date '+%F %T')] [$SERVER_NAME] SKIP: another backup is running" >> "$LOGFILE"
exit 0
}
# Ciclo di retry globale: ripete lintero backup logico fino a GLOBAL_MAX_RETRIES volte
# Ciclo di retry globale
for GLOBAL_ATTEMPT in $(seq 1 $GLOBAL_MAX_RETRIES); do
# Log del tentativo globale corrente
# Separatore visivo per ogni tentativo globale
echo "---------------------" >> "$LOGFILE"
# Log del tentativo globale
echo "[$(date '+%F %T')] [$SERVER_NAME] GLOBAL attempt $GLOBAL_ATTEMPT/$GLOBAL_MAX_RETRIES" >> "$LOGFILE"
# Esegue un tentativo completo di backup logico
# Esegue un tentativo completo
if run_backup; then
# Se il tentativo ha successo, log e uscita dal ciclo globale
echo "[$(date '+%F %T')] [$SERVER_NAME] GLOBAL RESULT: SUCCESS" >> "$LOGFILE"
break
else
# Se il tentativo fallisce e non siamo allultimo tentativo globale
# Se non è lultimo tentativo → retry globale
if [ "$GLOBAL_ATTEMPT" -lt "$GLOBAL_MAX_RETRIES" ]; then
# Log del prossimo retry globale e attesa
echo "[$(date '+%F %T')] [$SERVER_NAME] GLOBAL RETRY in ${GLOBAL_RETRY_SLEEP}s" >> "$LOGFILE"
sleep "$GLOBAL_RETRY_SLEEP"
else
# Se anche lultimo tentativo globale fallisce, log e exit 1
# Fallimento definitivo
echo "[$(date '+%F %T')] [$SERVER_NAME] GLOBAL RESULT: FAILED after $GLOBAL_MAX_RETRIES attempts" >> "$LOGFILE"
exit 1
fi
fi
done
# Rotazione dei backup logici: rimuove le directory più vecchie di 7 giorni
# Rotazione: elimina backup logici più vecchi di 7 giorni
TO_DELETE=$(find "$BACKUP_BASE" -maxdepth 1 -type d -name "${PREFIX}*" -mtime +7)
# Se ci sono directory da cancellare
if [ -n "$TO_DELETE" ]; then
# Log delle directory che verranno rimosse
echo "[$(date '+%F %T')] [$SERVER_NAME] ROTATE: removing old logic backups:" >> "$LOGFILE"
echo "$TO_DELETE" | tr '\n' '\0' | xargs -0 -r rm -rf --
else
# Log se non c’è nulla da ruotare
echo "[$(date '+%F %T')] [$SERVER_NAME] ROTATE: no logic backups to remove (<=7 days)" >> "$LOGFILE"
fi
# File descriptor 9 associato al LOCKFILE per flock
) 9>"$LOCKFILE"