仙台の山奥で自転車に乗ったり転んだり

愛車の GIOS でサイクリングしたりポタリングしたり、それをブログに記録してみたり。ロードバイクや自転車や坂のことを書いてみたり。ときたまプログラムのことを忘れないようにメモってみたり。

PostgreSQLのメンテナンススクリプト

VACUUMを実行するバッチのスクリプト

常態的に日次で実行したいので、「FULL」のオプションはつけていません。
「FULL」のオプションがないので、テーブルのロックなどは発生しないと考えています。
不定期的なサーバメンテナンスのタイミングで、手動で「FULL」のVACUUMを実行することで、日次では回収しないディスク領域の改修など対応します。
同時実行の排他に「lockfile」コマンドを使用しているので、「procmail」パッケージに依存しています。

#!/bin/sh

set -e
set -u

POSTGRESQLUSER="postgres"
POSTGRESQLHOST="localhost"

LOCKFILE="/var/run/postgresql_vacuum.lock"
RETRIES=3
SLEEPTIME=15
# 1 hours (60 * 60 * 1 = 3600sec)
LOCKTIMEOUT=3600

# Require lockfile command in procmail package.
lockfile -${SLEEPTIME} -r $RETRIES -l $LOCKTIMEOUT $LOCKFILE >/dev/null 2>&1
if [ $? -ne 0 ] ; then
    # lock failed.
    ERRMSG="PostgreSQL vacuum - Still running."
    echo $ERRMSG
    exit 1
fi

SELFID=`id | sed -e 's/uid=//' -e 's/(.*//'`
if [ $SELFID -ne 0 ]; then
    # failed
    ERRMSG="You are not root, You cannot execute this script."
    echo $ERRMSG 1>&2
    exit 100
fi

DATABASES=`
    psql \
        --host=${POSTGRESQLHOST} \
        --user=${POSTGRESQLUSER} \
        --list \
        --tuples-only \
        --pset="format=unaligned" \
        --pset="fieldsep=," \
    | sed 's!^postgres=CTc/postgres$!!g' \
    | cut --delimiter=',' --fields=1 \
    | sed 's/^template[01]$//g' \
    | sed '/^$/d'
`
if [ $? -ne 0 ]; then
    # failed
    ERRMSG="Failed to listup the databases on the PostgreSQL server"
    echo $ERRMSG 1>&2
    exit 200
fi

for DBNAME in $DATABASES
do
    # Not lock table because it is not in full mode.
    # Analyze with vacuum.
    vacuumdb \
        --host=${POSTGRESQLHOST} \
        --username=${POSTGRESQLUSER} \
        --analyze \
        --quiet \
        $DBNAME
    if [ $? -eq 0 ]; then
        echo "Successful in vacuum the PostgreSQL database: ${DBNAME}"
    else
        # warning
        ERRMSG="Failed in vacuum the PostgreSQL database: ${DBNAME}"
        echo $ERRMSG 1>&2
    fi
done

echo "Vacuum the database on PostgreSQL server was completed."
バックアップを実行するバッチのスクリプト

バックアップは必要になった時の用途にあわせて、ロールやテーブル空間などの情報も含めたデータベースクラスタの全内容のダンプと、個別のデータベースのダンプをとります。
部分的な復元などリストアする際に何かと都合がいいので、ダンプはpg_dumpのカスタムダンプ書式で書き出しています。
ついでに古いバックアップファイルの掃除もしますが、楽で便利だなと思ったので「tmpwatch」コマンドを使ってみました。
こちらも、同時実行の排他に「lockfile」コマンドを使用しているので、「procmail」パッケージに依存しています。

#!/bin/sh

set -e
set -u

POSTGRESQLUSER="postgres"
POSTGRESQLHOST="localhost"

BACKUPDIR="/srv/postgresql-backup-datastore"
# 25 days (24 * 25 = 600 hours)
SAVED=600

LOCKFILE="/var/run/postgresql_backup.lock"
RETRIES=3
SLEEPTIME=15
# 2 hours (60 * 60 * 2 = 7200sec)
LOCKTIMEOUT=7200

# Require lockfile command in procmail package.
lockfile -${SLEEPTIME} -r $RETRIES -l $LOCKTIMEOUT $LOCKFILE >/dev/null 2>&1
if [ $? -ne 0 ] ; then
    # lock failed.
    ERRMSG="PostgreSQL backup - Still running."
    echo $ERRMSG
    exit 1
fi

SELFID=`id | sed -e 's/uid=//' -e 's/(.*//'`
if [ $SELFID -ne 0 ]; then
    # failed
    ERRMSG="You are not root, You cannot execute this script."
    echo $ERRMSG 1>&2
    exit 100
fi

TMPDIR=/tmp/postgresql-backup.$$
mkdir -p $TMPDIR

trap "exit 1" HUP INT PIPE QUIT TERM
trap "rm -f ${TMPDIR}/*.dump; rmdir ${TMPDIR}; rm -f ${LOCKFILE}" EXIT

DATABASES=`
    psql \
        --host=${POSTGRESQLHOST} \
        --user=${POSTGRESQLUSER} \
        --list \
        --tuples-only \
        --pset="format=unaligned" \
        --pset="fieldsep=," \
    | sed 's!^postgres=CTc/postgres$!!g' \
    | cut --delimiter=',' --fields=1 \
    | sed 's/^template[01]$//g' \
    | sed '/^$/d'
`
if [ $? -ne 0 ]; then
    # failed
    ERRMSG="Failed to listup the databases on the PostgreSQL server."
    echo $ERRMSG 1>&2
    exit 200
fi

TODAY=`date +%Y%m%d`

BACKUP=${TMPDIR}/all-databases.${TODAY}.dump
pg_dumpall \
    --host=${POSTGRESQLHOST} \
    --username=${POSTGRESQLUSER} \
    > $BACKUP
if [ $? -eq 0 ]; then
    echo "Successful in the preparation of the all databases backup."
else
    ERRMSG="Failed in the preparation of the all databases backup."
    echo $ERRMSG 1>&2
    exit 201
fi

for DBNAME in $DATABASES
do
    BACKUP=${TMPDIR}/${DBNAME}.${TODAY}.dump
    pg_dump \
        --host=${POSTGRESQLHOST} \
        --username=${POSTGRESQLUSER} \
        --format=custom \
        $DBNAME > $BACKUP
    if [ $? -eq 0 ]; then
        echo "Successful in the preparation of the database backup: ${DBNAME}"
    else
        # warning
        $ERRMSG "Failed in the preparation of the database backup: ${DBNAME}"
        echo $ERRMSG 1>&2
    fi
done

if [ ! -d $BACKUPDIR ]; then
    # failed
    ERRMSG="Datastore of backup does not exist: ${BACKUPDIR}"
    echo $ERRMSG 1>&2
    exit 202
fi

/usr/sbin/tmpwatch $SAVED $BACKUPDIR
mv ${TMPDIR}/*.dump ${BACKUPDIR}/

echo "Backup of the database on PostgreSQL server was completed."