Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| mysql_backups_using_replication [13.11.2025 00:13] – [Backup and Monitoring] Pascal Suter | mysql_backups_using_replication [13.11.2025 09:20] (current) – [Backup and Monitoring] Pascal Suter | ||
|---|---|---|---|
| Line 3: | Line 3: | ||
| in my case i had a docker-compose.yml which contains the php web app and a mysql server service. i first copied the service in the '' | in my case i had a docker-compose.yml which contains the php web app and a mysql server service. i first copied the service in the '' | ||
| - | < | + | < |
| mysql: | mysql: | ||
| # image: mysql:8.0 | # image: mysql:8.0 | ||
| Line 40: | Line 40: | ||
| for the primary server, make sure the config contains the following settings: | for the primary server, make sure the config contains the following settings: | ||
| - | < | + | < |
| [mysqld] | [mysqld] | ||
| server-id = 1 | server-id = 1 | ||
| Line 58: | Line 58: | ||
| and on the replica the settings should look like this: | and on the replica the settings should look like this: | ||
| - | < | + | < |
| [mysqld] | [mysqld] | ||
| server-id = 2 | server-id = 2 | ||
| Line 64: | Line 64: | ||
| relay_log = relay-bin | relay_log = relay-bin | ||
| - | # keep binlog | + | # disable binary logging as we don't need this on the replica |
| - | log_bin = mysql-bin | + | skip-log-bin |
| - | binlog_format = ROW | + | |
| + | # GTID functionality is needed for replication | ||
| gtid_mode = ON | gtid_mode = ON | ||
| enforce_gtid_consistency = ON | enforce_gtid_consistency = ON | ||
| - | log_slave_updates = ON | ||
| # make it read-only for safety | # make it read-only for safety | ||
| Line 77: | Line 76: | ||
| # don't auto-start replication until we finish setup | # don't auto-start replication until we finish setup | ||
| + | # then comment this out | ||
| skip_slave_start = ON | skip_slave_start = ON | ||
| </ | </ | ||
| Line 126: | Line 126: | ||
| you can also see the the executed and retrieved Gtid dataset, they should update as the master is writing data to the db. | you can also see the the executed and retrieved Gtid dataset, they should update as the master is writing data to the db. | ||
| + | now remove the '' | ||
| ===== Backup and Monitoring ===== | ===== Backup and Monitoring ===== | ||
| now lets create a backup script. this script will first check if the replication is still running and updated before it shuts down the replica container and creates a tar.gz file of the database data directory. on success, it will notify a uptime kuma push monitor. | now lets create a backup script. this script will first check if the replication is still running and updated before it shuts down the replica container and creates a tar.gz file of the database data directory. on success, it will notify a uptime kuma push monitor. | ||
| Line 153: | Line 154: | ||
| # Directory where your docker-compose.yml lives | # Directory where your docker-compose.yml lives | ||
| - | COMPOSE_DIR="/ | + | COMPOSE_DIR="/ |
| # Name of the replica service in docker-compose | # Name of the replica service in docker-compose | ||
| Line 167: | Line 168: | ||
| MAX_BACKUPS=24 | MAX_BACKUPS=24 | ||
| - | # Uptime Kuma | + | # Uptime Kuma entity ID |
| - | UPTIME_SERVER=" | + | ENTITY_ID=" |
| - | ENTITY_ID=" | + | |
| # Monitoring DB user (created on the PRIMARY, replicated to replica) | # Monitoring DB user (created on the PRIMARY, replicated to replica) | ||
| MONITOR_USER=" | MONITOR_USER=" | ||
| - | MONITOR_PASSWORD=" | + | MONITOR_PASSWORD=" |
| # State file to track last Executed_Gtid_Set | # State file to track last Executed_Gtid_Set | ||
| Line 210: | Line 210: | ||
| io_running=$(printf ' | io_running=$(printf ' | ||
| - | sql_running=$(printf ' | + | sql_running=$(printf ' |
| seconds_behind=$(printf ' | seconds_behind=$(printf ' | ||
| - | executed_gtid=$(printf ' | + | executed_gtid=$( |
| + | | ||
| + | sed -n 's/^ *Executed_Gtid_Set: | ||
| + | ) | ||
| echo " | echo " | ||
| Line 254: | Line 256: | ||
| msg=" | msg=" | ||
| - | local url=" | + | local url=" |
| echo " | echo " | ||
| Line 293: | Line 295: | ||
| docker-compose stop " | docker-compose stop " | ||
| - | # 3) Create tar.bz2 backup | + | # 3) Create tar.gz backup |
| TIMESTAMP=" | TIMESTAMP=" | ||
| - | BACKUP_FILE=" | + | BACKUP_FILE=" |
| - | echo " | + | echo " |
| - | tar -cjf " | + | tar -czf " |
| echo " | echo " | ||
| Line 304: | Line 306: | ||
| # 4) Rotate old backups | # 4) Rotate old backups | ||
| echo " | echo " | ||
| - | mapfile -t BACKUPS < <(ls -1t " | + | mapfile -t BACKUPS < <(ls -1t " |
| if (( ${# | if (( ${# | ||
| Line 315: | Line 317: | ||
| echo " | echo " | ||
| </ | </ | ||
| + | **NOTE** you may want to use gz instead of bz2 in case it takes too long | ||
| + | |||
| + | here is the cron job: | ||
| + | 0 */2 * * * / | ||
| + | add the dbbackup.log to your logrotate config | ||
| + | |||
| + | ===== replica check script ===== | ||
| + | here is a script to quickly check if the replica is working and up to date.. useful when messing around with the config for example or for monitoring purposes other than through the bakcup script: | ||
| + | |||
| + | <code bash checkReplica.sh> | ||
| + | # | ||
| + | set -euo pipefail | ||
| + | |||
| + | ######################################## | ||
| + | # Settings | ||
| + | ######################################## | ||
| + | |||
| + | # Directory where your docker-compose.yml lives | ||
| + | COMPOSE_DIR="/ | ||
| + | |||
| + | # Name of the replica service in docker-compose | ||
| + | REPLICA_SERVICE=" | ||
| + | |||
| + | # Monitoring DB user (created on PRIMARY, replicated to replica) | ||
| + | MONITOR_USER=" | ||
| + | MONITOR_PASSWORD=" | ||
| + | |||
| + | ######################################## | ||
| + | # Colors | ||
| + | ######################################## | ||
| + | |||
| + | if [ -t 1 ]; then | ||
| + | RED=" | ||
| + | GREEN=" | ||
| + | YELLOW=" | ||
| + | BOLD=" | ||
| + | RESET=" | ||
| + | else | ||
| + | RED=""; | ||
| + | fi | ||
| + | |||
| + | ######################################## | ||
| + | # Helper | ||
| + | ######################################## | ||
| + | |||
| + | # Extract " | ||
| + | # " | ||
| + | get_field() { | ||
| + | local key=" | ||
| + | printf ' | ||
| + | | sed -n " | ||
| + | | head -n1 | ||
| + | } | ||
| + | |||
| + | ######################################## | ||
| + | # Main | ||
| + | ######################################## | ||
| + | |||
| + | cd " | ||
| + | |||
| + | # Try SHOW REPLICA STATUS first (MySQL 8+), fall back to SHOW SLAVE STATUS (older) | ||
| + | STATUS=$(docker-compose -f " | ||
| + | mysql -u" | ||
| + | |||
| + | if [[ -z " | ||
| + | STATUS=$(docker-compose -f " | ||
| + | mysql -u" | ||
| + | fi | ||
| + | |||
| + | if [[ -z " | ||
| + | echo " | ||
| + | echo " | ||
| + | echo " | ||
| + | exit 1 | ||
| + | fi | ||
| + | |||
| + | # IO / SQL thread status (new names first, fall back to old) | ||
| + | IO_Running=$(get_field " | ||
| + | if [[ -z " | ||
| + | IO_Running=$(get_field " | ||
| + | fi | ||
| + | |||
| + | SQL_Running=$(get_field " | ||
| + | if [[ -z " | ||
| + | SQL_Running=$(get_field " | ||
| + | fi | ||
| + | |||
| + | # Seconds behind (new name first, fall back) | ||
| + | Seconds_Behind=$(get_field " | ||
| + | if [[ -z " | ||
| + | Seconds_Behind=$(get_field " | ||
| + | fi | ||
| + | |||
| + | # GTID sets – allow indent before the key | ||
| + | Executed_Gtid_Set=$( | ||
| + | printf ' | ||
| + | | sed -n ' | ||
| + | | head -n1 | ||
| + | ) | ||
| + | Retrieved_Gtid_Set=$( | ||
| + | printf ' | ||
| + | | sed -n ' | ||
| + | | head -n1 | ||
| + | ) | ||
| + | |||
| + | # Errors (names are the same on old/new) | ||
| + | Last_IO_Error=$(get_field " | ||
| + | Last_SQL_Error=$(get_field " | ||
| + | |||
| + | ######################################## | ||
| + | # Determine overall status | ||
| + | ######################################## | ||
| + | |||
| + | overall_color=" | ||
| + | overall_text=" | ||
| + | |||
| + | if [[ " | ||
| + | overall_color=" | ||
| + | overall_text=" | ||
| + | elif [[ -z " | ||
| + | overall_color=" | ||
| + | overall_text=" | ||
| + | elif [[ " | ||
| + | # More than 5 minutes behind -> warn | ||
| + | overall_color=" | ||
| + | overall_text=" | ||
| + | fi | ||
| + | |||
| + | ######################################## | ||
| + | # Output | ||
| + | ######################################## | ||
| + | |||
| + | echo | ||
| + | echo " | ||
| + | echo " | ||
| + | echo | ||
| + | |||
| + | echo " | ||
| + | echo " | ||
| + | |||
| + | if [[ -z " | ||
| + | echo " | ||
| + | else | ||
| + | echo " | ||
| + | fi | ||
| + | |||
| + | echo | ||
| + | echo " | ||
| + | echo " | ||
| + | echo " | ||
| + | echo " | ||
| + | echo | ||
| + | |||
| + | if [[ -n " | ||
| + | echo " | ||
| + | echo " | ||
| + | echo | ||
| + | fi | ||
| + | |||
| + | if [[ -n " | ||
| + | echo " | ||
| + | echo " | ||
| + | echo | ||
| + | fi | ||
| + | </ | ||
| + | |||