DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Performing and Managing Incremental Backups Using pg_basebackup in PostgreSQL 17
  • How to Restore a Transaction Log Backup in SQL Server
  • How to Backup SQL Server RDS to an S3 Bucket
  • Vector Storage, Indexing, and Search With MariaDB

Trending

  • Artificial Intelligence, Real Consequences: Balancing Good vs Evil AI [Infographic]
  • From Zero to Production: Best Practices for Scaling LLMs in the Enterprise
  • Performing and Managing Incremental Backups Using pg_basebackup in PostgreSQL 17
  • MySQL to PostgreSQL Database Migration: A Practical Case Study
  1. DZone
  2. Data Engineering
  3. Databases
  4. Using MariaDB Backup and MariaDB MaxScale to Scale Online

Using MariaDB Backup and MariaDB MaxScale to Scale Online

MariaDB MaxScale provides a powerful, flexible, and convenient means to build a scalable MariaDB Server cluster. Combine it with MariaDB Backup and what do you get?

By 
Anders Karlsson user avatar
Anders Karlsson
·
Jun. 07, 18 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
6.9K Views

Join the DZone community and get the full member experience.

Join For Free

This article is rather practical. What it aims to show is how we can use a script that in turn uses MariaDB Backup to back up a MariaDB Server master, how we can create a MariaDB Server slave from this backup, and then how we can script an online update to MariaDB MaxScale 2.2 to include the new MariaDB Server slave. Let's start with describing our environment.

Our Infrastructure

Infrastructure2.JPG

We currently have one MariaDB Server master, 2 MariaDB Server slaves and one instance of MariaDB MaxScale. They are all running CentOS 7.2 and the IP addresses are 192.168.142.110 (MariaDB MaxScale), 192.168.142.111 (MariaDB Server master), 192.168.142.112 and 192.168.142.113 (MariaDB Server slaves).

Setup of the MariaDB Servers

The setup of the master server is nothing really complicated in this case, but there are a few things we have to configure to use this as a master. We need to enable the binlog and set a server ID, and this means editing the /etc/my.cnf.d/server.cnf file (if you are not on CentOS or RedHat, the location might be different) and add the following to the [mysqld] section:

server_id=111
log-bin=hostonly111

We also need to adjust the [mysqld] section in the same configuration file on the slaves, for example:

server_id=112
log_bin=hostonly112
log_error=error.log
datadir=/var/lib/mysql
report-host=192.168.142.112

This has to be adjusted accordingly of course to fit your setup and also note that not all of these are strictly necessary. I will not show more of the master and slave configuration here, as this is not the goal of this article.

Setup of MariaDB MaxScale

There is a basic setup of MariaDB MaxScale that is assumed here, but note that MariaDB MaxScale, from version 2.2, stores a binary version of its configuration separately. This is useful when you use online dynamic reconfiguration, but it makes things a bit more complicated. What I am showing here is the basic MariaDB MaxScale configuration that is used to support the cluster set up as above and this is stored in the file /etc/maxscale.cnf:

# Global parameters
#
[maxscale]
threads=auto

# Server definitions
#
[server1]
type=server
address=192.168.142.111
port=3306
protocol=MariaDBBackend

[server2]
type=server
address=192.168.142.112
port=3306
protocol=MariaDBBackend

[server3]
type=server
address=192.168.142.113
port=3306
protocol=MariaDBBackend

# Monitor for the servers
#
[MariaDB-Monitor]
type=monitor
module=mariadbmon
servers=server1,server2,server3
user=myuser
passwd=mypwd
monitor_interval=1000

# Service definitions
#
[Read-Write-Service]
type=service
router=readwritesplit
servers=server1,server2,server3
user=myuser
passwd=mypwd

# This service enables the use of the MaxAdmin interface
#
[MaxAdmin-Service]
type=service
router=cli

# Listener definitions for the services
#
[Read-Write-Listener]
type=listener
service=Read-Write-Service
protocol=MariaDBClient
port=4006

[MaxAdmin-Listener]
type=listener
service=MaxAdmin-Service
protocol=maxscaled
socket=default

Note that I am not going to cover all the aspects of configuring MariaDB MaxScale here.

Backing Up the MariaDB Server Using MariaDB Backup

Mariabackup is fully documented in the MariaDB Knowledge Base, so I will not get into details. Rather, what I aim to show is a basic bash-script that runs a backup. How you run this is not really important, but it has to be run, and the compressed and archived backup has to be placed in the agreed location. Also note that the script also runs a prepare, which means that it does any recovery necessary on the backup to create a consistent copy of the running database.

The script is far from complete, but it performs the basics. It does keep old backups and does a few other things. It is intended to be run in the background, which is why it is configured using environments variables at the top of the script and not any command line arguments.

#!/bin/bash -eu
#
set -o pipefail
MDB_USER=root
MDB_PWD=
MDB_BACKUPBASE=/home/anders
MDB_BACKUPNAME=backup
MDB_ARCHIVENAME=backuparchive
MDB_BACKUPDIR=$MDB_BACKUPBASE/$MDB_BACKUPNAME
MDB_BACKUPARCHIVEPFX=$MDB_BACKUPBASE/$MDB_ARCHIVENAME
MDB_BACKUPARCHIVEDIR=""
MDB_BACKUPARCHIVETGZ=""
MDB_BACKUPLOG=/tmp/backup.log
MDB_BACKUPCMD=/usr/bin/mariabackup

if [ "$EUID" != "0" ]; then
   echo "$0 must be run as root" >&2
   exit 1
fi

# Check if the backup directory exists.
if [ -e "$MDB_BACKUPDIR" -o -e "$MDB_BACKUPDIR.tgz" ]; then
# Find a backup archive directory.
   for I in {1..10000}; do
      if [ ! -e "$MDB_BACKUPARCHIVEPFX$I" -a ! -e "$MDB_BACKUPARCHIVEPFX$I.tgz" ]; then
        MDB_BACKUPARCHIVEDIR="$MDB_BACKUPARCHIVEPFX$I"
        MDB_BACKUPARCHIVETGZ="$MDB_BACKUPARCHIVEPFX$I.tgz"
        break
      fi
   done

   # Check that a directory was found.
   if [ "x$MDB_BACKUPARCHIVEDIR" = "x" ]; then
      echo "Can't find a suitable backup archive directory" >&2
      exit 1
   fi

   if [ -e "$MDB_BACKUPDIR" ] ; then
      mv $MDB_BACKUPDIR $MDB_BACKUPARCHIVEDIR
   fi

   if [ -e "$MDB_BACKUPDIR.tgz" ] ; then
      mv $MDB_BACKUPDIR.tgz $MDB_BACKUPARCHIVETGZ
   fi
fi

echo >> $MDB_BACKUPLOG
echo "Starting backup on `date +"%Y-%m-%d %H:%M:%S"`" >> $MDB_BACKUPLOG

# Do the backup.
echo "Backup up to $MDB_BACKUPDIR" >> $MDB_BACKUPLOG
$MDB_BACKUPCMD --backup -u $MDB_USER ${MDB_PASS:+"-p$MDB_PASS"} --target-dir=$MDB_BACKUPDIR >> $MDB_BACKUPLOG 2>&1

# Prepare and make the backup consistent.
$MDB_BACKUPCMD --prepare -u $MDB_USER ${MDB_PASS:+"-p$MDB_PASS"} --target-dir=$MDB_BACKUPDIR >> $MDB_BACKUPLOG 2>&1

# Compress and archive the backup.
cd $MDB_BACKUPBASE
tar cvfz $MDB_BACKUPNAME.tgz $MDB_BACKUPNAME >> $MDB_BACKUPLOG 2>&1

As you can see, it is nothing too complicated, and the resulting backup will be placed in the archive file /home/anders/backup.tgz.

Creating a Slave From a Master Backup

The second script to present is the one that creates a slave from a master, using the content of a backup as described above. It is assumed that this slave has MariaDB Server and MariaBackup already installed and that "ssh" is configured so that files can be copied from the master, including appropriate keys. Then, it is time to have a look at the script.

The script does quite a few things. First, it figures out a suitable server_id for this slave, and then it copies a backup archive from the master server and unpacks this. For a replication slave to be set up appropriately, we need to know the GTID at the point when the backup was taken, so this is recovered from a file that MariaDB Backup generates for us that is included with the backup archive. At this point, the MariaDB Server is shut down, and the current datadir is saved.

It is then time to recover data from the backup and set up the correct ownership of the MariaDB Server directories. At this point, we can start the MariaDB Server. All we need to do next is to configure the MariaDB Server as a slave and we are all set.

The script, which will run on the server of the MariaDB Server slave, takes two options: the IP address of the MariaDB Server master of this slave, and the host IP of this MariaDB Server slave. The latter isn't as obvious as one might think, and many servers, possibly most, have several host IPs (excluding localhost there is often one connected internally and one to a Firewalled DMZ).

#!/bin/bash
#
set -o pipefail
MDB_MASTERHOST=$1
MDB_SLAVEHOST=$2
MDB_USER=root
MDB_PWD=
MDB_CNFFILE=/etc/my.cnf.d/server.cnf
MDB_REPLUSER=repl
MDB_REPLPWD=repl
MDB_DEFAULTDATADIR=/var/lib/mysql
MDB_BACKUPNAME=backup
MDB_RESTORELOG=/tmp/createslave.log
MDB_BACKUPLOC=$MDB_MASTERHOST:$MDB_BACKUPNAME.tgz
MDB_BACKUPCMD=/usr/bin/mariabackup
MDB_MASTEROPTS="-h $MDB_MASTERHOST -u $MDB_REPLUSER ${MDB_REPLPWD:+"-p$MDB_REPLPWD"} --batch --column-names=0"

if [ "$EUID" != "0" ]; then
   echo "$0 must be run as root" >&2
   exit 1
fi

if [ "$#" -lt 2 ]; then
   echo "Usage: $0 <master host> <slave host> [<slave server id>]" >&2
   exit 1
fi

# Handle server id.
if [ "$#" -gt 2 ]; then
   MDB_SLAVEID=$3
   if [ "x`echo $MDB_SLAVEID | sed "s/^[0-9]*$//"`" != "x" ]; then
      echo "Slave server id invalid. It must be numeric" >&2
      exit 1
   fi
else
# Get a server id from the master if not specified.
   MDB_SLAVEID="`mysql $MDB_MASTEROPTS -e "SHOW SLAVE HOSTS" | awk '{print $1}' | sort -n | tail -1`"
   if [ "x$MDB_SLAVEID" == "x" ]; then
      MDB_SLAVEID="`mysql $MDB_MASTEROPTS -e "SELECT @@SERVER_ID"`"
   fi
   MDB_SLAVEID=$(($MDB_SLAVEID + 1))
fi

# Check if we have mariabackup
if [ ! -e "$MDB_BACKUPCMD" ]; then
   echo "Cannot find $MDB_BACKUPCMD command. Please install it" >&2
   exit 1
fi
# Check if datadir is set, else set the default.
MDB_DATADIR="`(grep "^ *datadir *=" $MDB_CNFFILE || true) | awk -F= '{print $2}'`"
if [ "x$MDB_DATADIR" == "x" ]; then
   MDB_DATADIR=$MDB_DEFAULTDATADIR
fi

# Print to log.
echo >> $MDB_RESTORELOG
echo "Starting restore on `date +"%Y-%m-%d %H:%M:%S"`" >> $MDB_RESTORELOG

# Copy backup from master.
scp $MDB_BACKUPLOC . >> $MDB_RESTORELOG 2>&1

# Remove old backup, if one exists.
if [ -e "$MDB_BACKUPNAME" ]; then
  rm -rf $MDB_BACKUPNAME
fi

# Unpack backup.
tar xvfz $MDB_BACKUPNAME.tgz >> $MDB_RESTORELOG 2>&1

# Get the GTID from the backup
GTID_POS=`cat $MDB_BACKUPNAME/xtrabackup_binlog_info | awk '{print $3}'`
echo >> $MDB_RESTORELOG 2>&1
echo "Restoring GTID: $GTID_POS" >> $MDB_RESTORELOG 2>&1

# Get MariaDB server status
STATUS=`systemctl is-active mariadb || true`
echo "MariaDB status: $STATUS" >> $MDB_RESTORELOG 2>&1

# Stop MariaDB if it is running.
if [ "$STATUS" = "active" ]; then
   echo "Stopping MariaDB" >> $MDB_RESTORELOG 2>&1
   systemctl stop mariadb >> $MDB_RESTORELOG 2>&1
   STATUS=`systemctl is-active mariadb || true`
   if [ "$STATUS" = "active" ]; then
      echo "Error stopping MariaDB" >> $MDB_RESTORELOG 2>&1
      exit 1
   fi
fi

# Save current datadir if that exists.
if [ -e "$MDB_DATADIR" ]; then
   MDB_DATADIR_SAVE="$MDB_DATADIR`date +\"%Y%m%d_%H%M%S\"`"

   if [ -e "$MDB_DATADIR_SAVE" ]; then
      for I in {1..100000}; do
         MDB_DATADIR_SAVE="$MDB_DATADIR`date +\"%Y%m%d_%H%M%S\"`_$I"
         if [ ! -e "$MDB_DATADIR_SAVE" ]; then
            break
         fi
      done
      if [ -e "$MDB_DATADIR_SAVE" ]; then
         echo "Can't find location for saved datadir" >> $MDB_RESTORELOG 2>&1
         exit 1
      fi
   fi

# Move datadir to saved location.
   mv $MDB_DATADIR $MDB_DATADIR_SAVE
fi


# Find mysqld group in config file.
GRPLINE=`grep -n "\[mysqld\]" $MDB_CNFFILE | tail -1 | awk -F: '{print $1}'`
# If a group wasn't found, then add one.
if [ "x$GRPLINE" == "x" ]; then
   echo "[mysqld]" >> $MDB_CNFFILE
   GRPLINE=`grep -n "\[mysqld\]" $MDB_CNFFILE | awk -F: '{print $1}'`
fi

# Set up section of variables to set.
NEWCNF=""
if [ "x`grep \"^ *server[-_]id *=\" $MDB_CNFFILE`" == "x" ]; then
   NEWCNF="server_id=$MDB_SLAVEID"
fi
if [ "x`grep \"^ *datadir *=\" $MDB_CNFFILE`" == "x" ]; then
   NEWCNF="${NEWCNF}${NEWCNF:+\n}datadir=/var/lib/mysql"
fi
if [ "x`grep \"^ *report[-_]host *=\" $MDB_CNFFILE`" == "x" ]; then
   NEWCNF="${NEWCNF}${NEWCNF:+\n}report_host=$MDB_SLAVEHOST"
fi

# Set up required variables in cnf if necessary.
if [ "x$NEWCNF" != "x" ]; then
   sed -i "${GRPLINE}a$NEWCNF" $MDB_CNFFILE
fi

# Restore from backup.
$MDB_BACKUPCMD --move-back --target-dir=$PWD/$MDB_BACKUPNAME >> $MDB_RESTORELOG 2>&1

# Set correct ownership.
chown -R mysql:mysql $MDB_DATADIR
chmod 755 $MDB_DATADIR

# Start MariaDB again.
systemctl start mariadb >> $MDB_RESTORELOG 2>&1

# Get MariaDB server status
STATUS=`systemctl is-active mariadb || true`

# Stop if MariaDB is not running.
if [ "$STATUS" != "active" ]; then
   echo "Error starting MariaDB" >> $MDB_RESTORELOG 2>&1
   exit 1
fi

# Set up node as slave.
mysql -u $MDB_USER ${MDB_PWD:+"-p$MDB_PWD"} -e "SET GLOBAL gtid_slave_pos = '$GTID_POS'" >> $MDB_RESTORELOG 2>&1
mysql -u $MDB_USER ${MDB_PWD:+"-p$MDB_PWD"} -e "CHANGE MASTER TO MASTER_HOST='$MDB_MASTERHOST',\ 
  MASTER_USER='$MDB_REPLUSER', MASTER_PASSWORD='$MDB_REPLPWD', MASTER_USE_GTID=current_pos" >> \ 
  $MDB_RESTORELOG 2>&1
mysql -u $MDB_USER ${MDB_PWD:+"-p$MDB_PWD"} -e "START SLAVE" >> $MDB_RESTORELOG 2>&

After running the script above, we have yet another slave attached to the master, but one thing remains, which is to tell MariaDB MaxScale to route reads also to the newly attached server.

Automated and Online Reconfiguration of MariaDB MaxScale

The last step is to add our new slave to the MariaDB MaxScale configuration. The way this script works is that it attaches to the master and uses that to determine which slaves exist. This is compared to the slaves that MariaDB MaxScale knows about and if one is missing, it is added. Among the programs used by this script, curl and above all jq, are used to parse JSON. These tools have to be installed to use this script.

#!/bin/bash
#
MAX_HOST=localhost
MAX_PORT=8989
MAX_USER=admin
MAX_PWD=mariadb
MDB_PORT=3306
MDB_USER=myuser
MDB_PWD=mypwd
SERVERID_PREFIX=server
CURL_OPTS="--user $MAX_USER:$MAX_PWD --silent"
if [ "x`which curl 2> /dev/null`" == "x" ]; then
   echo "Cannot find curl program" 1>&2
   exit 1
fi
if [ "x`which mysql 2> /dev/null`" == "x" ]; then
   echo "Cannot find mysql program" 1>&2
   exit 1
fi
if [ "x`which jq 2> /dev/null`" == "x" ]; then
   echo "Cannot find jq program" 1>&2
   exit 1
fi

#
# Function to add a server.
# Arguments:
# Server address
# Server node name
#
function addserver {
   curl $CURL_OPTS -H "Content-Type:application/json" http://$MAX_HOST:$MAX_PORT/v1/servers -d '{
   "data": {
      "id": "'$2'",
      "type": "servers",
      "attributes": {
         "parameters": {
            "address": "'$1'",
             "port": 3306,
             "protocol": "MariaDBBackend"
         }
      },
      "relationships": {
         "services" : {
            "data": ['$SERVICELIST']
         },
         "monitors" : {
            "data": ['$MONITORLIST']
         }
      }
   }
}'
}

#
# Function to generate a suitable server_id
#
function get_serverid {
   for I in {1..10000}; do
      found=0
      for S in $SERVERIDS; do
         if [ "$SERVERID_PREFIX$I" == "$S" ]; then
            found=1
            break
         fi
      done
      if [ $found -eq 0 ]; then
         echo "$SERVERID_PREFIX$I"
         break
      fi
   done
   return 0
}

MASTER=`curl $CURL_OPTS http://$MAX_HOST:$MAX_PORT/v1/servers | jq --raw-output '.data[].attributes | select(.state == "Master, Running") | .parameters.address'`
MASTERID=`curl $CURL_OPTS http://$MAX_HOST:$MAX_PORT/v1/servers | jq --raw-output '.data[] | select(.attributes.state == "Master, Running") | .id'`
if [ "x$MASTER" == "x" ]; then
   echo "Cannot find a master node" 1>&2
   exit 1
fi
MASTER_SERVICES=`curl $CURL_OPTS http://$MAX_HOST:$MAX_PORT/v1/servers/$MASTERID | jq --raw-output '.data.relationships.services.data[].id'`
MASTER_MONITORS=`curl $CURL_OPTS http://$MAX_HOST:$MAX_PORT/v1/servers/$MASTERID | jq --raw-output '.data.relationships.monitors.data[].id'`
SERVERS=`curl $CURL_OPTS --silent http://$MAX_HOST:$MAX_PORT/v1/servers | jq --raw-output '.data[].attributes.parameters.address' | sort`
SERVERIDS=`curl $CURL_OPTS --silent http://$MAX_HOST:$MAX_PORT/v1/servers | jq --raw-output '.data[].id' | sort`
SLAVES=`mysql -h $MASTER -P $MDB_PORT -u $MDB_USER -p$MDB_PWD -e "show processlist" --batch | grep "Binlog Dump" | awk '{sub(/:[0-9]*/, "", $3); print $3;}'`

# Create JSON list of services.
SERVICELIST=""
for S in $MASTER_SERVICES; do
   SERVICELIST="${SERVICELIST:+$SERVICELIST,}{\"id\":\"$S\",\"type\":\"services\"}"
done

# Create JSON list of monitors.
MONITORLIST=""
for S in $MASTER_MONITORS; do
   MONITORLIST="${MONITORLIST:+$MONITORLIST,}{\"id\":\"$S\",\"type\":\"monitors\"}"
done

# Loop for all slaves and see if they are defined in maxscale.
for S in $SLAVES; do
   found=0
   for SE in $SERVERS; do
      if [ "$S" == "$SE" ]; then
         found=1
         break;
      fi
   done

# If server is not found in maxscale, then add it.
   if [ $found -eq 0 ]; then
      echo "Server $S not found in MaxScale. Adding"
      SRVID=$(get_serverid)
      echo $SRVID
      addserver $S $SRVID
      SERVERIDS="$SERVERIDS $SRVID"
   fi
done

Conclusion

MariaDB MaxScale provides a powerful, flexible, and convenient means to build a scalable MariaDB Server cluster, be it Galera or a Replicated Cluster. MariaDB Backup, on the other hand, is a powerful and flexible online backup solution for MariaDB Server. Combining these technologies means that a powerful and scalable environment can easily be built, and it is scalable and flexible without downtime.

References

  • jq documentation
  • curl documentation
  • MariaDB MaxScale REST API

Happy SQL'ing!

MariaDB Backup master Archive file

Published at DZone with permission of Anders Karlsson, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Performing and Managing Incremental Backups Using pg_basebackup in PostgreSQL 17
  • How to Restore a Transaction Log Backup in SQL Server
  • How to Backup SQL Server RDS to an S3 Bucket
  • Vector Storage, Indexing, and Search With MariaDB

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: