def check_frm_timestamp(self, fname, path): ifnot self.skip_check_frm_timestamp and fname.endswith('.frm'): if os.path.getmtime(path) > self.start_backup_time:
logger.error('FRM file %s was updated after starting backups. ' 'Schema could have changed and the resulting copy may ' 'not be valid. Aborting. ' '(backup time: %s, file modified time: %s)',
path, datetime.datetime.fromtimestamp(self.start_backup_time).strftime('%Y-%m-%d %H:%M:%S'),
datetime.datetime.fromtimestamp(os.path.getmtime(path)).strftime('%Y-%m-%d %H:%M:%S')) raise Exception("Inconsistent frm file timestamp");
def process(self):
os.chdir(self.datadir) for db in self.get_databases():
logger.info("Starting MySQL misc file traversal from database %s..", db)
self.process_db(db) for f in self.get_files(db): if self.match(f):
rel_path = os.path.join(db, f)
self.check_frm_timestamp(f, rel_path)
self.process_file(rel_path)
logger.info("Traversing misc files from data directory..") for f in self.get_files(""):
should_skip = False for e in exclude_files: if f.startswith(e) or f.endswith(e):
logger.info("Skipping %s", f)
should_skip = True break ifnot should_skip:
self.process_file(f)
def match(self, filename): if self.regex.match(filename): returnTrue else: returnFalse
def get_databases(self):
dbs = []
dirs = [ d for d in os.listdir(self.datadir) \ ifnot os.path.isfile(os.path.join(self.datadir,d))] for db in dirs: ifnot db.startswith('.') andnot self._is_socket(db) andnot db == "#rocksdb":
dbs.append(db) return dbs
def get_files(self, db):
dbdir = self.datadir + "/" + db return [ f for f in os.listdir(dbdir) \ if os.path.isfile(os.path.join(dbdir,f))]
# RocksDB backup class RocksDBBackup():
source_dir = None
writer = None # sst files sent in this backup round
sent_sst = {} # target sst files in this backup round
target_sst = {} # sst files sent in all backup rounds
total_sent_sst= {} # sum of sst file size sent in this backup round
sent_sst_size = 0 # sum of target sst file size in this backup round # if sent_sst_size becomes equal to target_sst_size, # it means the backup round finished backing up all sst files
target_sst_size = 0 # sum of all sst file size sent all backup rounds
total_sent_sst_size= 0 # sum of all target sst file size from all backup rounds
total_target_sst_size = 0
show_progress_size_interval= 1073741824# 1GB
wal_files= []
manifest_files= []
finished= False
for f in os.listdir(self.source_dir): if f.endswith(rocksdb_data_suffix): # exactly the same file (same size) was sent in previous backup rounds if prev isnotNoneand f in prev.total_sent_sst and int(os.stat(f).st_size) == prev.total_sent_sst[f]: continue
sst[f]= int(os.stat(f).st_size)
self.target_sst_size = self.target_sst_size + os.stat(f).st_size elif is_manifest(f):
self.manifest_files.append(f) elif f.endswith(rocksdb_wal_suffix):
self.wal_files.append(f)
self.target_sst= collections.OrderedDict(sorted(sst.items()))
def do_backup_manifest(self): for f in self.manifest_files:
self.do_backup_single(f)
def do_backup_wal(self): for f in self.wal_files:
self.do_backup_single(f)
# this is the last snapshot round. backing up all the rest files def do_backup_final(self):
logger.info("Backup WAL..")
self.do_backup_wal()
logger.info("Backup Manifest..")
self.do_backup_manifest()
self.do_cleanup()
self.finished= True
def do_cleanup(self):
shutil.rmtree(self.source_dir)
logger.info("Cleaned up checkpoint from %s", self.source_dir)
def start_backup_round(self, backup_round, prev_backup): def signal_handler(*args):
logger.info("Got signal. Exit") if b isnotNone:
logger.info("Cleaning up snapshot directory..")
b.do_cleanup()
sys.exit(1)
b = None try:
signal.signal(signal.SIGINT, signal_handler)
w = None ifnot opts.output_stream: raise Exception("Currently only streaming backup is supported.")
def cleanup(self, snapshot_dir, server_log): if server_log:
server_log.seek(0)
logger.info("WDT server log:")
logger.info(server_log.read())
server_log.close() if snapshot_dir:
logger.info("Cleaning up snapshot dir %s", snapshot_dir)
shutil.rmtree(snapshot_dir)
# construct receiver cmd, using the data directory as recovery-id. # we delete the current file because it is not append-only, therefore not # resumable.
remote_cmd = ( "ssh {0} rm -f {1}; " "{2} -directory {3} -enable_download_resumption " "-recovery_id {4} -start_port 0 -abort_after_seconds {5} {6}"
).format(opts.destination,
current_path,
wdt_bin,
opts.backupdir,
self.datadir,
opts.checkpoint_interval,
opts.extra_wdt_receiver_options)
logger.info("WDT remote cmd %s", remote_cmd)
server_log = tempfile.TemporaryFile()
remote_process = subprocess.Popen(remote_cmd.split(),
stdout=subprocess.PIPE,
stderr=server_log)
wdt_url = remote_process.stdout.readline().strip() ifnot wdt_url: raise Exception("Unable to get connection url from wdt receiver")
sender_cmd = ( "{0} -connection_url \'{1}\' -directory {2} -app_name=myrocks " "-avg_mbytes_per_sec {3} " "-enable_download_resumption -abort_after_seconds {4} {5}"
).format(wdt_bin,
wdt_url,
snapshot_dir,
opts.avg_mbytes_per_sec,
opts.checkpoint_interval,
opts.extra_wdt_sender_options)
sender_status = os.system(sender_cmd) >> 8
remote_status = remote_process.wait()
self.cleanup(snapshot_dir, server_log) # TODO: handle retryable and non-retyable errors differently return (sender_status == 0and remote_status == 0)
except Exception as e:
logger.error(e)
logger.error(traceback.format_exc())
self.cleanup(snapshot_dir, server_log)
sys.exit(1)
def backup_using_wdt(): ifnot opts.destination:
logger.error("Must provide remote destination when using WDT")
sys.exit(1)
# TODO: detect whether WDT is installed
logger.info("Backing up myrocks to %s using WDT", opts.destination)
wdt_backup = WDTBackup(opts.datadir)
finished = False
backup_round = 1 whilenot finished:
start_time = time.time()
finished = wdt_backup.backup_with_timeout(backup_round)
end_time = time.time()
duration_seconds = end_time - start_time if (not finished) and (duration_seconds < opts.checkpoint_interval): # round finished before timeout
sleep_duration = (opts.checkpoint_interval - duration_seconds)
logger.info("Sleeping for %f seconds", sleep_duration)
time.sleep(sleep_duration)
backup_round = backup_round + 1
logger.info("Finished myrocks backup using WDT")
def init_logger(): global logger
logger = logging.getLogger('myrocks_hotbackup')
logger.setLevel(logging.INFO)
h1= logging.StreamHandler(sys.stderr)
f = logging.Formatter("%(asctime)s.%(msecs)03d %(levelname)s %(message)s", "%Y-%m-%d %H:%M:%S")
h1.setFormatter(f)
logger.addHandler(h1)
backup_wdt_usage = ("Backup using WDT: myrocks_hotbackup " "--user=root --password=pw --stream=wdt " "--checkpoint_dir=<directory where temporary backup hard links " "are created> --destination=<remote host name> --backup_dir=" "<remote directory name>. This has to be executed at the src " "host.")
backup_usage= "Backup: set -o pipefail; myrocks_hotbackup --user=root --password=pw --port=3306 --checkpoint_dir=<directory where temporary backup hard links are created> | ssh -o NoneEnabled=yes remote_server 'tar -xi -C <directory on remote server where backups will be sent>' . You need to execute backup command on a server where you take backups."
move_back_usage= "Move-Back: myrocks_hotbackup --move_back --datadir=<dest mysql datadir> --rocksdb_datadir=<dest rocksdb datadir> --rocksdb_waldir=<dest rocksdb wal dir> --backup_dir=<where backup files are stored> . You need to execute move-back command on a server where backup files are sent."
def parse_options(): global opts
parser = OptionParser(usage = "\n\n" + backup_usage + "\n\n" + \
backup_wdt_usage + "\n\n" + move_back_usage)
parser.add_option('-i', '--interval', type='int', dest='checkpoint_interval',
default=300,
help='Number of seconds to renew checkpoint')
parser.add_option('-c', '--checkpoint_dir', type='string', dest='checkpoint_directory',
default='/data/mysql/backup/snapshot',
help='Local directory name where checkpoints will be created.')
parser.add_option('-d', '--datadir', type='string', dest='datadir',
default=None,
help='backup mode: src MySQL datadir. move_back mode: dest MySQL datadir')
parser.add_option('-s', '--stream', type='string', dest='output_stream',
default='tar',
help='Setting streaming backup options. Currently tar, WDT ' 'and xbstream are supported. Default is tar')
parser.add_option('--destination', type='string', dest='destination',
default='',
help='Remote server name. Only used for WDT mode so far.')
parser.add_option('--avg_mbytes_per_sec', type='int',
dest='avg_mbytes_per_sec',
default=500,
help='Average backup rate in MBytes/sec. WDT only.')
parser.add_option('--extra_wdt_sender_options', type='string',
dest='extra_wdt_sender_options',
default='',
help='Extra options for WDT sender')
parser.add_option('--extra_wdt_receiver_options', type='string',
dest='extra_wdt_receiver_options',
default='',
help='Extra options for WDT receiver')
parser.add_option('-u', '--user', type='string', dest='mysql_user',
default='root',
help='MySQL user name')
parser.add_option('-p', '--password', type='string', dest='mysql_password',
default='',
help='MySQL password name')
parser.add_option('-P', '--port', type='int', dest='mysql_port',
default=3306,
help='MySQL port number')
parser.add_option('-S', '--socket', type='string', dest='mysql_socket',
default=None,
help='MySQL socket path. Takes precedence over --port.')
parser.add_option('-m', '--move_back', action='store_true', dest='move_back',
default=False,
help='Moving MyRocks backup files to proper locations.')
parser.add_option('-r', '--rocksdb_datadir', type='string', dest='rocksdb_datadir',
default=None,
help='RocksDB target data directory where backup data files will be moved. Must be empty.')
parser.add_option('-w', '--rocksdb_waldir', type='string', dest='rocksdb_waldir',
default=None,
help='RocksDB target data directory where backup wal files will be moved. Must be empty.')
parser.add_option('-b', '--backup_dir', type='string', dest='backupdir',
default=None,
help='backup mode for WDT: Remote directory to store ' 'backup. move_back mode: Locations where backup ' 'files are stored.')
parser.add_option('-f', '--skip_check_frm_timestamp',
dest='skip_check_frm_timestamp',
action='store_true', default=False,
help='skipping to check if frm files are updated after starting backup.')
parser.add_option('-D', '--debug_signal_file', type='string', dest='debug_signal_file',
default=None,
help='debugging purpose: waiting until the specified file is created')
opts, args = parser.parse_args()
def create_moveback_dir(directory): ifnot os.path.exists(directory):
os.makedirs(directory) else: for f in os.listdir(directory):
logger.error("Directory %s has file or directory %s!", directory, f) raise
os.chdir(opts.backupdir) for f in os.listdir(opts.backupdir): if os.path.isfile(os.path.join(opts.backupdir,f)): if f.endswith(rocksdb_wal_suffix):
shutil.move(f, opts.rocksdb_waldir) elif f.endswith(rocksdb_data_suffix) or is_manifest(f):
shutil.move(f, opts.rocksdb_datadir) else:
shutil.move(f, opts.datadir) else: #directory if f.endswith('.rocksdb'): continue
shutil.move(f, opts.datadir)
def start_backup():
logger.info("Starting backup.")
runner = BackupRunner(opts.datadir)
b = None
backup_round= 1 whileTrue:
b = runner.start_backup_round(backup_round, b)
backup_round = backup_round + 1 if b.finished isTrue:
b.print_backup_report()
logger.info("RocksDB Backup Done.") break if opts.debug_signal_file: whilenot os.path.exists(opts.debug_signal_file):
logger.info("Waiting until %s is created..", opts.debug_signal_file)
time.sleep(1)
runner.backup_mysql()
logger.info("All Backups Done.")
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.