- Posted on
- • Artificial Intelligence
Artificial Intelligence Database Backups
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Database Backups: Smart, Fast, and Verifiable on Linux (with Bash)
If your AI app is impressive but you can’t restore its data quickly, it’s a demo—not a product. AI workloads stitch together relational data (users, metadata), vector indexes (embeddings), caches, and logs. Losing any of it can break search, personalization, or chat memory. This guide shows how to build “intelligent” database backups on Linux with Bash: encrypted, deduplicated, offsite, and self-checking—plus practical recipes for PostgreSQL (including pgvector), MySQL/MariaDB, Redis, and even popular vector DBs.
You’ll get:
A clear baseline you can deploy in minutes
3–5 actionable steps and scripts to make your backups smarter
Restore-tested examples and optional anomaly detection
Install commands for apt, dnf, and zypper where needed
Why “Artificial Intelligence” Backups?
AI systems add challenges that traditional backups often miss:
Multiple datastores: relational (PostgreSQL), vector DBs (pgvector, Qdrant, Milvus), caches/queues (Redis), and feature stores.
Fast-changing, large data: embeddings, chat memory, and event logs grow fast; naive tarballs become slow and costly.
Restore matters more than backup: you need predictable RTO/RPO and quick verification that data is consistent (e.g., embeddings align with doc versions).
“Intelligence” here means:
Automated, testable workflows (cron/systemd timers)
Deduplication and encryption by default
Basic anomaly detection (catch suspiciously small/large dumps)
Easy offsite sync and quick restore paths
Clear, searchable logs
Below is a battle-tested baseline you can adapt for most AI stacks.
Step 1: Install the essentials
We’ll use restic (encrypted, deduplicated backups), rclone (for cloud/object storage remotes), and standard DB clients. We’ll also use jq for parsing JSON logs.
Apt (Debian/Ubuntu):
sudo apt update
sudo apt install -y restic rclone postgresql-client mariadb-client redis-tools jq cron
Dnf (Fedora/RHEL/CentOS Stream):
sudo dnf install -y restic rclone postgresql mariadb redis jq cronie
Zypper (openSUSE/SLE):
sudo zypper refresh
sudo zypper install -y restic rclone postgresql mariadb-client redis jq cron
Notes:
Package names vary slightly by distro/version. If you don’t see a “-client” variant, the base package often includes the CLI.
Ensure your DB clients match your server major version (especially PostgreSQL) for the best compatibility.
Step 2: Initialize an encrypted, deduplicated backup repo
Use a local path, S3-compatible bucket, or any rclone remote (Backblaze B2, Wasabi, etc.). Example with rclone to S3:
1) Configure rclone:
rclone config
# Create a remote named "s3" (or any name), set provider/keys/region.
2) Initialize a restic repo on that remote:
export RESTIC_REPOSITORY=rclone:s3:your-bucket-name/path
export RESTIC_PASSWORD="long-unique-passphrase" # store securely (e.g., /etc/ai-backup/restic.pw with 0600)
restic init
Tip: Store secrets in root-readable files and source them in scripts instead of in shell history.
Step 3: Bash script to dump databases and push to restic (with basic anomaly checks)
Save as /usr/local/bin/ai-backup.sh and make it executable.
#!/usr/bin/env bash
set -euo pipefail
# Configuration (move secrets to /etc/ai-backup/env and chmod 0600)
: "${RESTIC_REPOSITORY:=rclone:s3:your-bucket-name/path}"
: "${RESTIC_PASSWORD:?Set RESTIC_PASSWORD}"
BACKUP_ROOT="/var/backups/ai-db"
STATE_DIR="/var/lib/ai-backup"
LOG_DIR="/var/log/ai-backup"
mkdir -p "$BACKUP_ROOT" "$STATE_DIR" "$LOG_DIR"
# Databases (edit to your env)
PG_URI="${PG_URI:-postgresql://backup_user:password@localhost:5432/yourdb}"
MYSQL_URI="${MYSQL_URI:-root:password@tcp(127.0.0.1:3306)/yourdb?tls=false}"
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
REDIS_PORT="${REDIS_PORT:-6379}"
# Optional: Qdrant collection to snapshot (comment if not used)
QDRANT_URL="${QDRANT_URL:-http://127.0.0.1:6333}"
QDRANT_COLLECTION="${QDRANT_COLLECTION:-}"
DATE=$(date +%F_%H-%M-%S)
RUN_DIR="$BACKUP_ROOT/$DATE"
mkdir -p "$RUN_DIR"
log() { echo "[$(date -Is)] $*" | tee -a "$LOG_DIR/run-$DATE.log"; }
anomaly_check() {
# Simple z-score anomaly detector for file sizes
# Keeps rolling mean/stdev in state files per key.
local key="$1" file="$2"
local size; size=$(stat -c%s "$file" 2>/dev/null || echo 0)
local statf="$STATE_DIR/${key}.stats"
# Format: count mean m2 (for Welford's algorithm)
if [[ -f "$statf" ]]; then
read -r count mean m2 < "$statf"
else
count=0; mean=0; m2=0
fi
local new_count=$((count+1))
# Welford update
awk -v c="$count" -v m="$mean" -v m2="$m2" -v x="$size" -v f="$statf" '
function abs(v){return v<0?-v:v}
BEGIN{
if(c==0){nc=1; nmean=x; nm2=0; z=0}
else{
nc=c+1
delta=x-m
nmean=m + delta/nc
delta2=x-nmean
nm2=m2 + delta*delta2
if(c>1){var= m2/(c-1); sd=(var>0)?sqrt(var):0} else {sd=0}
z = (sd>0)? (x-m)/sd : 0
}
# Write new stats
print nc, nmean, nm2 > f
# Print z-score for caller
printf "%.3f\n", z
}
' > "$STATE_DIR/.tmpz"
local zscore; zscore=$(cat "$STATE_DIR/.tmpz"); rm -f "$STATE_DIR/.tmpz"
if awk -v z="$zscore" 'BEGIN{exit !(z>3 || z<-3)}'; then
log "ANOMALY: size for $key = $size bytes (z-score $zscore)"
return 1
fi
return 0
}
dump_postgres() {
local out="$RUN_DIR/postgres.dump"
# -Fc = custom format, good for pg_restore
PGPASSWORD="$(python3 - <<'PY'
import os, urllib.parse
u=urllib.parse.urlparse(os.environ["PG_URI"])
print((u.password or ""))
PY
)" pg_dump -Fc "$PG_URI" > "$out"
log "PostgreSQL dump complete: $out ($(stat -c%s "$out") bytes)"
anomaly_check "postgres" "$out" || true
}
dump_mysql() {
local out="$RUN_DIR/mysql.sql"
# Parse DSN-like MYSQL_URI; alternatively set MYSQL_USER/MYSQL_PWD/MYSQL_DB explicitly.
local user host port db pass
# Fallback if envs provided
if [[ -n "${MYSQL_USER:-}" ]]; then
user="$MYSQL_USER"; pass="${MYSQL_PWD:-}"; host="${MYSQL_HOST:-127.0.0.1}"; port="${MYSQL_PORT:-3306}"; db="${MYSQL_DB:-}"
else
# Very simple parse; customize as needed.
user="root"; pass="password"; host="127.0.0.1"; port="3306"; db="yourdb"
fi
MYSQL_PWD="$pass" mysqldump --single-transaction --routines --triggers -h "$host" -P "$port" -u "$user" "$db" > "$out"
log "MySQL/MariaDB dump complete: $out ($(stat -c%s "$out") bytes)"
anomaly_check "mysql" "$out" || true
}
dump_sqlite() {
# If you have local SQLite feature stores, back them up safely
local dbfile="${SQLITE_DBFILE:-/var/lib/app/features.db}"
if [[ -f "$dbfile" ]]; then
local out="$RUN_DIR/sqlite-$(basename "$dbfile").sql"
sqlite3 "$dbfile" ".backup '$RUN_DIR/$(basename "$dbfile").backup'"
sqlite3 "$dbfile" ".dump" > "$out"
log "SQLite dump complete: $out"
anomaly_check "sqlite" "$out" || true
fi
}
dump_redis() {
local out="$RUN_DIR/redis.rdb"
# Redis 7+: redis-cli --rdb writes an RDB snapshot via the CLI
if redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" --rdb "$out" >/dev/null 2>&1; then
log "Redis RDB snapshot complete: $out ($(stat -c%s "$out") bytes)"
anomaly_check "redis" "$out" || true
else
log "WARN: redis-cli --rdb failed; consider BGSAVE + copying dump.rdb"
fi
}
snapshot_qdrant() {
# Optional: snapshot a Qdrant collection (vector DB) via HTTP API
[[ -z "$QDRANT_COLLECTION" ]] && return 0
local resp="$RUN_DIR/qdrant-${QDRANT_COLLECTION}-resp.json"
curl -fsS -X POST "$QDRANT_URL/collections/$QDRANT_COLLECTION/snapshots" \
-H 'Content-Type: application/json' -d '{}' -o "$resp"
local path
path=$(jq -r '.result.location // empty' "$resp")
if [[ -n "$path" && -f "$path" ]]; then
local out="$RUN_DIR/qdrant-${QDRANT_COLLECTION}.snapshot"
cp -a "$path" "$out"
log "Qdrant snapshot complete: $out"
anomaly_check "qdrant_${QDRANT_COLLECTION}" "$out" || true
else
log "WARN: Qdrant snapshot response did not include local path; check $resp"
fi
}
backup_with_restic() {
# Tag with hostname and purpose for easy filtering
restic backup --tag ai-db,"$(hostname)" --json "$RUN_DIR" | tee -a "$LOG_DIR/restic-$DATE.jsonl" >/dev/null
# Prune/forget old backups (tune per your RPO)
restic forget --prune --keep-hourly 24 --keep-daily 7 --keep-weekly 4 --keep-monthly 6
log "Restic backup + retention complete"
}
main() {
dump_postgres || { log "ERROR: PostgreSQL dump failed"; exit 1; }
dump_mysql || { log "ERROR: MySQL/MariaDB dump failed"; exit 1; }
dump_sqlite || true
dump_redis || true
snapshot_qdrant || true
backup_with_restic
log "Backup run finished successfully"
}
main
Make it executable:
sudo install -m 0750 -o root -g root /usr/local/bin/ai-backup.sh
Create an env file for secrets (root-only):
sudo mkdir -p /etc/ai-backup
sudo bash -c 'cat >/etc/ai-backup/env <<EOF
RESTIC_REPOSITORY=rclone:s3:your-bucket-name/path
RESTIC_PASSWORD=long-unique-passphrase
PG_URI=postgresql://backup_user:password@localhost:5432/yourdb
MYSQL_USER=root
MYSQL_PWD=yourpass
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_DB=yourdb
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
# QDRANT_URL=http://127.0.0.1:6333
# QDRANT_COLLECTION=docs
EOF'
sudo chmod 0600 /etc/ai-backup/env
Step 4: Schedule and rotate like a pro (cron or systemd timers)
Cron (simple):
sudo bash -c 'cat >/etc/cron.d/ai-backup <<EOF
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Run hourly at minute 17
17 * * * * root source /etc/ai-backup/env && /usr/local/bin/ai-backup.sh
EOF'
Systemd timer (more control and journald logs):
/etc/systemd/system/ai-backup.service:
[Unit]
Description=AI database backup
[Service]
Type=oneshot
EnvironmentFile=/etc/ai-backup/env
ExecStart=/usr/local/bin/ai-backup.sh
User=root
Group=root
Nice=10
IOSchedulingClass=best-effort
/etc/systemd/system/ai-backup.timer:
[Unit]
Description=Run AI database backup hourly
[Timer]
OnCalendar=hourly
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
Enable:
sudo systemctl daemon-reload
sudo systemctl enable --now ai-backup.timer
Step 5: Test restores (the only test that matters)
Example: restore a PostgreSQL pgvector DB dump:
# List snapshots
restic snapshots --tag ai-db
# Restore the latest backup to a temp dir
mkdir -p /tmp/restore-test
restic restore latest --target /tmp/restore-test
# Load the dump into a new database
createdb -h localhost -U postgres restore_ai
pg_restore -h localhost -U postgres -d restore_ai /tmp/restore-test/*/postgres.dump
# Validate: check table counts and vector index health
psql -h localhost -U postgres -d restore_ai -c "SELECT COUNT(*) FROM your_table;"
psql -h localhost -U postgres -d restore_ai -c "\dx" # confirm pgvector installed
MySQL/MariaDB:
mysql -h 127.0.0.1 -u root -p -e "CREATE DATABASE restore_ai;"
mysql -h 127.0.0.1 -u root -p restore_ai < /tmp/restore-test/*/mysql.sql
Redis:
# Warning: Restoring Redis will overwrite current data. Use a test instance.
redis-server --daemonize yes --port 6380 --dbfilename temp.rdb --dir /tmp/restore-test/*/
# Or copy the RDB into a throwaway instance/data-dir and start it.
Qdrant (if used): follow vendor docs to load a snapshot for your version. If you used the HTTP snapshot, restoring generally involves placing the snapshot file into Qdrant’s snapshot directory and using the API to load it.
Pro tip: Add a “restore game day” to your calendar each month. Automate a throwaway restore to verify RTO.
Real-world example: RAG app with pgvector and Redis
Data: documents and metadata in PostgreSQL (pgvector for embeddings), Redis for session/chat state.
Backup: hourly with ai-backup.sh, offsite via restic+rclone.
Outcome: When an embedding job misfired and truncated vectors, anomaly detection flagged a tiny Postgres dump (z-score < -3). Team paused the pipeline, restored previous snapshot to a temp DB, compared row counts and average vector norms, then rolled forward safely.
Optional: Summarize backup logs with an LLM (local)
If you want AI to summarize last backup logs locally, you can use ollama (local models). Note: ollama does not have apt/dnf/zypper packages at this time; install via script:
Install ollama (generic Linux):
curl -fsSL https://ollama.com/install.sh | sh
Pull a small model and summarize:
ollama pull mistral
tail -100 /var/log/ai-backup/run-$(ls -1t /var/log/ai-backup | grep '^run-' | head -1 | sed 's/run-//;s/\.log$//').log \
| ollama run mistral "Summarize this backup log, highlight errors and anomalies:"
Keep this optional. Never put secrets in logs you feed to models.
Troubleshooting and tips
Permissions: give your backup user least privilege (e.g., a Postgres role with CONNECT and backup-related rights).
Consistency:
- PostgreSQL: use
pg_dump -Fcwith--no-ownerif restoring across clusters. - MySQL/MariaDB: use
--single-transactionfor InnoDB to avoid locks. - Redis: prefer
redis-cli --rdbon Redis 7+. On older versions, useBGSAVEand copy dump.rdb atomically.
- PostgreSQL: use
Retention tuning: adjust
restic forgetto your RPO/RTO and storage budget.Speed-ups:
- Run dumps on a replica to avoid load on primaries.
- Use restic caches:
export RESTIC_CACHE_DIR=/var/cache/restic.
Security: store
RESTIC_PASSWORDin a root-only file; use IAM roles/instance profiles for cloud storage where possible.
Conclusion and Call To Action
AI systems deserve AI-grade backups: automated, encrypted, deduplicated, anomaly-aware, and restore-tested. You now have:
A working baseline with restic + rclone
Bash recipes for PostgreSQL/pgvector, MySQL/MariaDB, Redis, and Qdrant snapshots
Scheduling via cron/systemd
Quick restore steps and basic anomaly detection
Your next steps: 1) Deploy the script in staging today; run a restore test. 2) Tune retention and schedules to your RPO/RTO. 3) Add more datastores (Milvus, Elasticsearch/OpenSearch, etc.) using the same pattern. 4) Schedule a monthly automated restore-and-verify job.
If you can restore it blindfolded, you’re production-ready.