#!/usr/bin/perl =head1 NAME B - Purges all the mysql replication log that are no longer in use. =head1 SYNOPSIS On slaves: purge_replication_log On the master: purge_replication_log --master =head1 DESCRIPTION Mysql doesn't yet automatically purge obsolete replication logs, so we must do it. This job is run periodically on both the master and slaves; the slaves connect to the master database and make an entry in a table which holds the log file that they are currently using, and the master consults this table to decide which log files are safe to purge. For safety, the purge job will refuse to purge the logs if there are slaves that are connected, but don't have entries. If slaves are neither connected, nor able to write any entries (for whatever reason), tough nuggies for them. Except the master/slave toggle, all command line options refer to the master's database, whether or not the job is invoked on the master or the slave. They are '--host', '--database', '--port', '--password', '--user', '--driver'. Slave mode is the default, '--master' should be specified when appropriate. =head1 AUTHOR Copyright (C) 2000 Joshua Spoerri for Wall Street Source $Id: purge_replication_log,v 1.2 2003/08/25 19:37:03 mdprewitt Exp $ =cut use strict; use DBI; use Getopt::Long; my ( $host, $database, $port, $password, $driver, $table, $user, $master ); $driver = 'mysql'; $database = 'mon'; $table = 'slave_status'; GetOptions( 'host=s' => \$host, 'database=s' => \$database, 'port=i' => \$port, 'password=s' => \$password, 'driver=s' => \$driver, 'table=s' => \$table, 'user=s' => \$user, 'master' => \$master, ); my $dsn = "dbi:$driver:database=$database"; $dsn .= ";host=$host" if $host; $dsn .= ";port=$port" if $port; if ( !$host ) { chomp( $host = `hostname` ); } $user = scalar getpwuid $< unless $user; my $dbh = DBI->connect( $dsn, $user, $password ) or die "couldn't connect to $dsn: $DBI::errstr"; if ( !$port ) { my $sth = $dbh->prepare(qq{ SHOW VARIABLES }) or die "couldn't prepare show variables: " . $dbh->errstr; $sth->execute() or die "couldn't execute show variables: " . $sth->errstr; while ( my $row = $sth->fetchrow_arrayref() ) { if ( $row->[0] eq 'port' ) { $port = $row->[1]; last; } } $sth->finish(); } if ($master) { #when invoked on the master, actually purge the logs #get all slaves that are currently connected my @connected_slaves; my $sth = $dbh->prepare(qq{ SHOW PROCESSLIST }) or die "couldn't prepare show process list: " . $dbh->errstr; $sth->execute() or die "couldn't execute show process list: " . $sth->errstr; while ( my $process = $sth->fetchrow_hashref ) { push @connected_slaves, $process->{Host} if $process->{Command} eq 'Binlog Dump'; } $sth->finish(); #get all slaves that have entries my @logged_slaves; my $sth = $dbh->prepare(qq{ SELECT Slave FROM $table WHERE Master like '$host:%' }) or die "couldn't prepare select in $table: " . $dbh->errstr; $sth->execute() or die "couldn't execute select in $table: " . $sth->errstr; while ( ($_) = $sth->fetchrow_array() ) { s/:.*//; push @logged_slaves, $_; } $sth->finish(); #make sure that all currently connected slaves have entries for my $slave (@connected_slaves) { my $logged; if ( $slave =~ /\./ ) { $logged = grep( $slave =~ /^$_(\.|$)/, @logged_slaves ); } else { $logged = grep( /^$slave(\.|$)/, @logged_slaves ); } die "no entry in $table for connected slave \"$slave\"" unless $logged; } # mysql> select * from slave_status; # +----------------+--------------+------------------+----------------+ # | Slave | Master | Log_File | Timestamp | # +----------------+--------------+------------------+----------------+ # | polar:3306 | fosters:3306 | fosters-bin.1033 | 20030825000205 | # | carlsberg:3306 | fosters:3306 | fosters-bin.1033 | 20030825000205 | # | bohemia:3306 | fosters:3306 | fosters-bin.1018 | 20030825000206 | # +----------------+--------------+------------------+----------------+ #get the oldest log file that appears to be in use my $sth = $dbh->prepare( qq{ SELECT Log_File FROM $table WHERE Master = '$host:$port' } ) or die "couldn't prepare select in $table: " . $dbh->errstr; $sth->execute() or die "couldn't execute select in $table: " . $sth->errstr; my @logs; while (my $log = $sth->fetchrow_array()) { push @logs, $log; } die "no slave entries for master $host:$port in $table" unless @logs; # sort the Log_File column by the number after the '.' in reverse # order to find the ealiest logfile. my $first_log = (sort {substr($b, index($b, '.')) <=> substr($a, index($a, '.')) } @logs)[0]; #purge up to (but not including) that log file print "Purging master logs to '$first_log'\n"; $dbh->do(qq{ PURGE MASTER LOGS TO '$first_log' }) or die "couldn't purge to $first_log: " . $dbh->errstr; #print(qq{would like to: "},qq{ PURGE MASTER LOGS TO '$first_log' },qq{"\n});#for testing } else { #when invoked on a slave, just record an entry for the currently active log file #get all the info for 1) connecting to the master, 2) recording our entry my ( $status, $master_host, $master_port, $log, $dsn ); my $sth = $dbh->prepare(qq{ SHOW SLAVE STATUS }) or die "couldn't prepare show slave status: " . $dbh->errstr; $sth->execute() or die "couldn't execute show slave status: " . $sth->errstr; $status = $sth->fetchrow_hashref(); $master_host = $status->{Master_Host} or die "$0 called in slave mode for nonslave"; $master_port = $status->{Master_Port}; $log = $status->{Log_File}; $dsn = "dbi:$driver:database=$database;host=$master_host;port=$master_port"; $sth->finish(); $dbh->disconnect(); my $dbh = DBI->connect( $dsn, $user, $password ) or die "couldn't connect to $dsn: " . $DBI::errstr; #don't bother to check for existence of the table; always creating if necessary eases maintenance $dbh->do( qq{ CREATE TABLE IF NOT EXISTS $table ( Slave varchar(255), Master varchar(255), Log_File varchar(255), Timestamp timestamp, UNIQUE (Slave) ) } ) or die "couldn't create $table: " . $dbh->errstr; #there should be exactly one currently active log file for each slave, #so just replace entry for what was previously active print "Saving position for host $host:$port=$log\n"; $dbh->do( qq{ REPLACE INTO $table (Slave, Master, Log_File) VALUES ('$host:$port', '$master_host:$master_port', '$log') } ) or die "couldn't replace into $table: " . $dbh->errstr; } exit 0;