Skip to content

Quiescence Script Examples

Example quiescence scripts for PostgreSQL, MySQL, MariaDB, and SQLite. Read Application-Consistent Backups first for how the agent runs these (timing, permissions, timeout) before adapting them to your setup.

PostgreSQL

PostgreSQL brackets a backup with pg_backup_start()/pg_backup_stop() on 15+, or pg_start_backup()/pg_stop_backup() on older versions — the two work differently enough to need separate scripts.

PostgreSQL 15+

Warning

pg_backup_start()'s non-exclusive backup mode ends when the calling session closes. A plain psql -c "SELECT pg_backup_start(...)" call closes its connection as soon as the statement returns, ending backup mode before the snapshot is taken. This script holds one psql session open across PRE and POST via a named pipe.

#!/bin/bash
set -euo pipefail

RUN_DIR="/run/slide-quiescence-postgresql"
CMD_FIFO="$RUN_DIR/cmd.fifo"
OUT_FILE="$RUN_DIR/out.log"

case "$1" in
  PRE)
    mkdir -p "$RUN_DIR"
    rm -f "$CMD_FIFO" "$OUT_FILE"
    mkfifo "$CMD_FIFO"

    # FIFO as psql's stdin avoids EOF; backgrounded, it keeps backup mode
    # open until POST tells it to quit.
    sudo -u postgres psql --quiet --no-psqlrc <>"$CMD_FIFO" >"$OUT_FILE" 2>&1 &

    exec 8>"$CMD_FIFO"
    echo "SELECT pg_backup_start(label => 'slide-quiescence', fast => true);" >&8
    echo "SELECT 'slide-backup-started';" >&8
    exec 8>&-

    # Poll for confirmation, staying under the 30s script timeout.
    for _ in $(seq 1 20); do
      grep -q slide-backup-started "$OUT_FILE" && exit 0
      sleep 0.5
    done
    echo "postgresql backup mode was not confirmed in time" >&2
    exit 1
    ;;
  POST)
    exec 8>"$CMD_FIFO"
    echo "SELECT pg_backup_stop();" >&8
    echo "\\q" >&8
    exec 8>&-
    rm -rf "$RUN_DIR"
    ;;
esac

sudo -u postgres assumes peer auth (default on most installs) — see Authenticating without peer auth otherwise.

PostgreSQL Pre-15

Exclusive backup mode persists via an on-disk backup_label file rather than the session, so plain psql -c calls work fine — no FIFO needed:

case "$1" in
  PRE)
    sudo -u postgres psql -c "SELECT pg_start_backup('slide-quiescence', true);"
    ;;
  POST)
    sudo -u postgres psql -c "SELECT pg_stop_backup();"
    ;;
esac

Authenticating without peer auth

Use a .pgpass file instead of embedding credentials, owned by root, mode 0600, format hostname:port:database:username:password. Export these before either script above, and drop sudo -u postgres:

export PGPASSFILE="/etc/slide/agent/quiescence.d/.pgpass"
export PGHOST="localhost"
export PGUSER="postgres"

MySQL/MariaDB

FLUSH TABLES WITH READ LOCK requires the RELOAD privilege. Identical for MySQL and MariaDB.

Warning

The read lock releases the instant its connection closes, and mysql --batch fully buffers stdout when it isn't a TTY — so this script needs both --unbuffered and a connection held open via a named pipe across PRE and POST.

Example script

#!/bin/bash
set -euo pipefail

RUN_DIR="/run/slide-quiescence-mysql"
CMD_FIFO="$RUN_DIR/cmd.fifo"
OUT_FILE="$RUN_DIR/out.log"

case "$1" in
  PRE)
    mkdir -p "$RUN_DIR"
    rm -f "$CMD_FIFO" "$OUT_FILE"
    mkfifo "$CMD_FIFO"

    # FIFO as the client's stdin avoids EOF; backgrounded, it keeps
    # holding the lock until POST tells it to quit. --unbuffered stops
    # mysql from delaying the confirmation query's output.
    mysql --defaults-extra-file=/etc/slide/agent/quiescence.d/.my.cnf \
      --unbuffered --batch <>"$CMD_FIFO" >"$OUT_FILE" 2>&1 &

    exec 8>"$CMD_FIFO"
    echo "FLUSH TABLES WITH READ LOCK;" >&8
    echo "SELECT 'slide-lock-acquired';" >&8
    exec 8>&-

    # Poll for confirmation, staying under the 30s script timeout.
    for _ in $(seq 1 20); do
      grep -q slide-lock-acquired "$OUT_FILE" && exit 0
      sleep 0.5
    done
    echo "mysql read lock was not acquired in time" >&2
    exit 1
    ;;
  POST)
    exec 8>"$CMD_FIFO"
    echo "UNLOCK TABLES;" >&8
    echo "\\q" >&8
    exec 8>&-
    rm -rf "$RUN_DIR"
    ;;
esac

Authenticating

# /etc/slide/agent/quiescence.d/.my.cnf
[client]
user=root
password=your-password-here

.my.cnf must be owned by root, mode 0600.

SQLite

In WAL mode, a snapshot taken mid-write can catch the main file and -wal file out of sync. Checkpointing first folds the WAL back into the main file.

Example script

#!/bin/bash
set -euo pipefail

DATABASES=(
  "/var/lib/myapp/data.db"
)

case "$1" in
  PRE)
    for db in "${DATABASES[@]}"; do
      sqlite3 "$db" "PRAGMA busy_timeout=5000; PRAGMA wal_checkpoint(TRUNCATE);"
    done
    ;;
  POST)
    # No-op — nothing to resume.
    ;;
esac

List every database file your application uses in DATABASES. wal_checkpoint(TRUNCATE) is a no-op if the database isn't in WAL mode, so this works either way.