MySQL: Difference between revisions
mNo edit summary |
mNo edit summary |
||
Line 53: | Line 53: | ||
See the history of this page for information on the previous replication setup. |
See the history of this page for information on the previous replication setup. |
||
=== Backups === |
|||
We use [[https://mariadb.com/kb/en/mariabackup-overview/ mariabackup]] to take periodic backups. It is currently installed and configured on both caffeine and coffee. |
|||
==== Installation ==== |
|||
In the example below, we will be installing mariabackup on coffee, and sending the backups to corn-syrup. |
|||
First, install the mariadb-backup package: |
|||
<pre> |
|||
apt install mariadb-backup |
|||
</pre> |
|||
Next, create an SSH key pair for the mysql user: |
|||
<pre> |
|||
mkdir /var/mariadb |
|||
chown mysql:mysql /var/mariadb |
|||
su -s /bin/bash mysql |
|||
cd /var/mariadb |
|||
mkdir .ssh |
|||
chmod 700 .ssh |
|||
# Choose /var/mariadb/.ssh/id_ed25519 for the path |
|||
ssh-keygen -t ed25519 |
|||
</pre> |
|||
Paste the public key (/var/mariadb/.ssh/id_ed25519.pub) into /users/syscom/.ssh/authorized_keys on corn-syrup: |
|||
<pre> |
|||
restrict ssh-ed25519 AAAAC3Nza... mysql@coffee |
|||
</pre> |
|||
Also create the folder <code>/users/syscom/backups/coffee/mariabackup</code>. We will store the backups here. |
|||
We will use a hacky bash script to try to emulate the same behaviour as pgBackRest. We will compress and stream each backup to a folder on corn-syrup in the format <code>1701678356-F</code>, where the number is a Unix epoch timestamp and the letter at the end is one of F, D or I (for full, differential or incremental backups). Full backups do not depend on any other backups. Differential backups depend on the latest full backup before them. Incremental backups depend on the latest backup before them (of any type). |
|||
On coffee, paste the following into e.g. /var/mariadb/bin/backup-mariadb.sh: |
|||
<pre> |
|||
#!/bin/bash |
|||
RETENTION_FULL=2 |
|||
RETENTION_DIFF=4 |
|||
SSH_KEY=/var/mariadb/.ssh/id_ed25519 |
|||
SSH_USER=syscom |
|||
SSH_HOST=corn-syrup |
|||
SSH_FOLDER=/users/$SSH_USER/backups/$(hostname)/mariabackup |
|||
SSH_ARGS="-i $SSH_KEY -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" |
|||
SSH="ssh $SSH_ARGS $SSH_USER@$SSH_HOST" |
|||
set -euxo pipefail |
|||
# $USER doesn't seem to be defined when we run this from cron |
|||
if [ "$(id -un)" != mysql ]; then |
|||
echo "This script should run as the mysql user" >&2 |
|||
exit 1 |
|||
fi |
|||
if [ $# -ne 1 ]; then |
|||
echo "Usage: $0 <full|diff|incr>" >&2 |
|||
exit 1 |
|||
fi |
|||
backup_type=$1 |
|||
if [ "$backup_type" = full ]; then |
|||
backup_type_letter=F |
|||
elif [ "$backup_type" = diff ]; then |
|||
backup_type_letter=D |
|||
elif [ "$backup_type" = incr ]; then |
|||
backup_type_letter=I |
|||
else |
|||
echo "Backup type must be one of 'full', 'diff' or 'incr'" >&2 |
|||
exit 1 |
|||
fi |
|||
if ! pgrep mariadbd >/dev/null; then |
|||
echo "MariaDB is not running" >&2 |
|||
exit 1 |
|||
fi |
|||
if pgrep mariabackup >/dev/null; then |
|||
echo "mariabackup is already running" >&2 |
|||
exit 1 |
|||
fi |
|||
# Delete temporary files left behind by previous run, if there are any |
|||
$SSH -- "rm -rf $SSH_FOLDER/*.tmp" |
|||
# Get a list of all backups in chronological order |
|||
mapfile -t backups < <($SSH -- "/bin/ls -1 $SSH_FOLDER | grep -P '^\\d+-[FDI]$' | sort") |
|||
incremental_basedir_args= |
|||
old_checkpoint_dir=$(mktemp -d) |
|||
new_checkpoint_dir=$(mktemp -d) |
|||
trap "rm -rf $old_checkpoint_dir $new_checkpoint_dir" EXIT |
|||
if [ "$backup_type" = diff -o "$backup_type" = incr ]; then |
|||
# Find a backup which we can use as a base. |
|||
# For incr, this can be any type; for diff, this must be a full backup. |
|||
base_backup= |
|||
for ((i=${#backups[@]}-1; i>=0; i--)); do |
|||
backup=${backups[i]} |
|||
if [ $backup_type = incr ] || [[ $backup =~ -F$ ]]; then |
|||
base_backup=$backup |
|||
break |
|||
fi |
|||
done |
|||
if [ -z "$base_backup" ]; then |
|||
echo "Could not find base backup for $backup_type type" >&2 |
|||
exit 1 |
|||
fi |
|||
# Copy the xtrabackup_checkpoints file from the base backup into a |
|||
# temporary directory, and use it in the mariabackup command. |
|||
scp $SSH_ARGS "$SSH_USER@$SSH_HOST:$SSH_FOLDER/$base_backup/xtrabackup_*" $old_checkpoint_dir/ |
|||
incremental_basedir_args="--incremental-basedir=$old_checkpoint_dir" |
|||
fi |
|||
compress_level=6 |
|||
if [ $backup_type = full ]; then |
|||
# Use a lower compression level to go faster |
|||
compress_level=5 |
|||
fi |
|||
foldername="$(date +%s)-$backup_type_letter" |
|||
# First copy to a temporary dir, then rename the temporary dir to the |
|||
# desired dir name (in case our process gets killed) |
|||
mariabackup --user=mysql --backup $incremental_basedir_args --stream=xbstream --extra-lsndir=$new_checkpoint_dir \ |
|||
| nice xz -$compress_level -T0 \ |
|||
| $SSH -- "cd $SSH_FOLDER && mkdir $foldername.tmp && cat > $foldername.tmp/data.xb.xz" |
|||
scp $SSH_ARGS $new_checkpoint_dir/* $SSH_USER@$SSH_HOST:$SSH_FOLDER/$foldername.tmp/ |
|||
$SSH -- "mv $SSH_FOLDER/$foldername.tmp $SSH_FOLDER/$foldername" |
|||
# Delete old backups |
|||
if [ $backup_type = incr ]; then |
|||
# We don't delete backups when making an incr backup, since we only |
|||
# have retention limits for full and diff |
|||
exit |
|||
fi |
|||
if [ $backup_type = full ]; then |
|||
retention=$RETENTION_FULL |
|||
else |
|||
retention=$RETENTION_DIFF |
|||
fi |
|||
num_backups_of_same_type=1 |
|||
backups_to_delete=() |
|||
for ((i=${#backups[@]}-1; i>=0; i--)); do |
|||
backup=${backups[i]} |
|||
if ! [[ $backup =~ -${backup_type_letter}$ ]]; then |
|||
continue |
|||
fi |
|||
((num_backups_of_same_type++)) |
|||
if [ $num_backups_of_same_type -lt $retention ]; then |
|||
continue |
|||
fi |
|||
if [ $backup_type = full ]; then |
|||
# Delete everything before the last full backup which we want to |
|||
# keep |
|||
pat='^' |
|||
else |
|||
# Delete all the diff and incr backups before the last diff backup |
|||
# which we want to keep |
|||
pat='-[DI]$' |
|||
fi |
|||
for ((j=$i-1; j>=0; j--)); do |
|||
backup=${backups[j]} |
|||
if [[ $backup =~ $pat ]]; then |
|||
backups_to_delete+=($backup) |
|||
fi |
|||
done |
|||
break |
|||
done |
|||
if [ ${#backups_to_delete[@]} -eq 0 ]; then |
|||
echo "No backups to delete" >&2 |
|||
exit |
|||
fi |
|||
$SSH -- "cd $SSH_FOLDER && rm -r ${backups_to_delete[@]}" |
|||
</pre> |
|||
The script should be invoked with exactly one argument which must be one of "full", "diff" or "incr". |
|||
[[Category:Software]] |
[[Category:Software]] |
Revision as of 13:15, 5 December 2023
For members
Note: the database on caffeine is actually MariaDB, not MySQL. Although they are mostly compatible, there are some incompatibilities to be aware of. See [MariaDB versus MySQL: Compatibility] for details.
Creating databases
Users can create their own MySQL databases through ceo. Users emailing syscom asking for a MySQL database should be directed to do so. The process is as follows:
- SSH into any CSC machine.
- Run ceo.
- Select "Create MySQL database" and follow the instructions.
- Login info will be stored in ceo-mysql-info in your home directory.
- You can now connect to the MySQL database (from caffeine only).
Deleting databases
Users can delete their own MySQL databases.
SSH into caffeine.
mysql -u yourusernamehere -p Enter password: ****** DROP DATABASE database name goes here
Login info and database name was created on database creation in ceo-mysql-info in your home directory.
For syscom
Creating a database manually
To create a MySQL database manually on caffeine, first connect to the database as root:
$ mysql -uroot -p Enter password: ******
Then run the following SQL statements:
CREATE USER 'someuser'@'localhost' IDENTIFIED VIA unix_socket; CREATE USER 'someuser'@'%' IDENTIFIED BY 'longrandompassword'; CREATE DATABASE someuser; GRANT ALL PRIVILEGES ON someusername.* to 'someuser'@'localhost' IDENTIFIED VIA unix_socket; GRANT ALL PRIVILEGES ON someusername.* to 'someuser'@'%';
This will allow users to connect locally without a password, and connect remotely with a password.
For random passwords run pwgen -s 20 1
. For the administrative passwords see /users/sysadmin/passwords/mysql.
Write a file (usually ~club/mysql) to the club's homedir readable only by them containing the following:
Username: clubuserid Password: longrandompassword Hostname: localhost
Try not to send passwords via plaintext email.
Replication
See the history of this page for information on the previous replication setup.
Backups
We use [mariabackup] to take periodic backups. It is currently installed and configured on both caffeine and coffee.
Installation
In the example below, we will be installing mariabackup on coffee, and sending the backups to corn-syrup.
First, install the mariadb-backup package:
apt install mariadb-backup
Next, create an SSH key pair for the mysql user:
mkdir /var/mariadb chown mysql:mysql /var/mariadb su -s /bin/bash mysql cd /var/mariadb mkdir .ssh chmod 700 .ssh # Choose /var/mariadb/.ssh/id_ed25519 for the path ssh-keygen -t ed25519
Paste the public key (/var/mariadb/.ssh/id_ed25519.pub) into /users/syscom/.ssh/authorized_keys on corn-syrup:
restrict ssh-ed25519 AAAAC3Nza... mysql@coffee
Also create the folder /users/syscom/backups/coffee/mariabackup
. We will store the backups here.
We will use a hacky bash script to try to emulate the same behaviour as pgBackRest. We will compress and stream each backup to a folder on corn-syrup in the format 1701678356-F
, where the number is a Unix epoch timestamp and the letter at the end is one of F, D or I (for full, differential or incremental backups). Full backups do not depend on any other backups. Differential backups depend on the latest full backup before them. Incremental backups depend on the latest backup before them (of any type).
On coffee, paste the following into e.g. /var/mariadb/bin/backup-mariadb.sh:
#!/bin/bash RETENTION_FULL=2 RETENTION_DIFF=4 SSH_KEY=/var/mariadb/.ssh/id_ed25519 SSH_USER=syscom SSH_HOST=corn-syrup SSH_FOLDER=/users/$SSH_USER/backups/$(hostname)/mariabackup SSH_ARGS="-i $SSH_KEY -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" SSH="ssh $SSH_ARGS $SSH_USER@$SSH_HOST" set -euxo pipefail # $USER doesn't seem to be defined when we run this from cron if [ "$(id -un)" != mysql ]; then echo "This script should run as the mysql user" >&2 exit 1 fi if [ $# -ne 1 ]; then echo "Usage: $0 <full|diff|incr>" >&2 exit 1 fi backup_type=$1 if [ "$backup_type" = full ]; then backup_type_letter=F elif [ "$backup_type" = diff ]; then backup_type_letter=D elif [ "$backup_type" = incr ]; then backup_type_letter=I else echo "Backup type must be one of 'full', 'diff' or 'incr'" >&2 exit 1 fi if ! pgrep mariadbd >/dev/null; then echo "MariaDB is not running" >&2 exit 1 fi if pgrep mariabackup >/dev/null; then echo "mariabackup is already running" >&2 exit 1 fi # Delete temporary files left behind by previous run, if there are any $SSH -- "rm -rf $SSH_FOLDER/*.tmp" # Get a list of all backups in chronological order mapfile -t backups < <($SSH -- "/bin/ls -1 $SSH_FOLDER | grep -P '^\\d+-[FDI]$' | sort") incremental_basedir_args= old_checkpoint_dir=$(mktemp -d) new_checkpoint_dir=$(mktemp -d) trap "rm -rf $old_checkpoint_dir $new_checkpoint_dir" EXIT if [ "$backup_type" = diff -o "$backup_type" = incr ]; then # Find a backup which we can use as a base. # For incr, this can be any type; for diff, this must be a full backup. base_backup= for ((i=${#backups[@]}-1; i>=0; i--)); do backup=${backups[i]} if [ $backup_type = incr ] || [[ $backup =~ -F$ ]]; then base_backup=$backup break fi done if [ -z "$base_backup" ]; then echo "Could not find base backup for $backup_type type" >&2 exit 1 fi # Copy the xtrabackup_checkpoints file from the base backup into a # temporary directory, and use it in the mariabackup command. scp $SSH_ARGS "$SSH_USER@$SSH_HOST:$SSH_FOLDER/$base_backup/xtrabackup_*" $old_checkpoint_dir/ incremental_basedir_args="--incremental-basedir=$old_checkpoint_dir" fi compress_level=6 if [ $backup_type = full ]; then # Use a lower compression level to go faster compress_level=5 fi foldername="$(date +%s)-$backup_type_letter" # First copy to a temporary dir, then rename the temporary dir to the # desired dir name (in case our process gets killed) mariabackup --user=mysql --backup $incremental_basedir_args --stream=xbstream --extra-lsndir=$new_checkpoint_dir \ | nice xz -$compress_level -T0 \ | $SSH -- "cd $SSH_FOLDER && mkdir $foldername.tmp && cat > $foldername.tmp/data.xb.xz" scp $SSH_ARGS $new_checkpoint_dir/* $SSH_USER@$SSH_HOST:$SSH_FOLDER/$foldername.tmp/ $SSH -- "mv $SSH_FOLDER/$foldername.tmp $SSH_FOLDER/$foldername" # Delete old backups if [ $backup_type = incr ]; then # We don't delete backups when making an incr backup, since we only # have retention limits for full and diff exit fi if [ $backup_type = full ]; then retention=$RETENTION_FULL else retention=$RETENTION_DIFF fi num_backups_of_same_type=1 backups_to_delete=() for ((i=${#backups[@]}-1; i>=0; i--)); do backup=${backups[i]} if ! [[ $backup =~ -${backup_type_letter}$ ]]; then continue fi ((num_backups_of_same_type++)) if [ $num_backups_of_same_type -lt $retention ]; then continue fi if [ $backup_type = full ]; then # Delete everything before the last full backup which we want to # keep pat='^' else # Delete all the diff and incr backups before the last diff backup # which we want to keep pat='-[DI]$' fi for ((j=$i-1; j>=0; j--)); do backup=${backups[j]} if [[ $backup =~ $pat ]]; then backups_to_delete+=($backup) fi done break done if [ ${#backups_to_delete[@]} -eq 0 ]; then echo "No backups to delete" >&2 exit fi $SSH -- "cd $SSH_FOLDER && rm -r ${backups_to_delete[@]}"
The script should be invoked with exactly one argument which must be one of "full", "diff" or "incr".