Package com.persistit

Source Code of com.persistit.BackupTask

/**
* Copyright © 2011-2012 Akiban Technologies, Inc.  All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This program may also be available under different license terms.
* For more information, see www.akiban.com or contact licensing@akiban.com.
*
* Contributors:
* Akiban Technologies, Inc.
*/

package com.persistit;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import com.persistit.CLI.Arg;
import com.persistit.CLI.Cmd;
import com.persistit.CheckpointManager.Checkpoint;
import com.persistit.Management.JournalInfo;
import com.persistit.Management.VolumeInfo;
import com.persistit.util.Util;

/**
* <p>
* Task to perform and/or control hot (concurrent) backup. Generally the process
* of copying large data files is best done by third party utility software.
* This class provides built-in ability to copy database files, but production
* configurations will likely use only the control mechanisms of this class and
* will run external programs to perform data compression and copying.
* </p>
* <p>
* To perform a concurrent backup, Persistit needs to ensure that the state of
* copied volume files plus journal files is sufficient to ensure a consistent,
* fast recovery process. This is done by setting Persistit to
* <code>appendOnly</code> mode, which causes Persistit not to modify its Volume
* files and not to delete any journal files. A snapshot of the journal file and
* volume provides a reliable source for recovery.
* </p>
* <p>
* This class provides a {@link Task} implementation that sets and resets the
* <code>appendOnly</code> flag, provides a list a files needing to be copied to
* form a complete backup, and as a convenience, a mechanism to compress and
* copy them.
* </p>
*
* @author peter
*
*/
public class BackupTask extends Task {

    private final static int BUFFER_SIZE = 1024 * 1024;
    private final static int PROGRESS_MARK_AT = 100 * 1000 * 1000;
    private boolean _start;
    private boolean _end;
    private boolean _showFiles;
    private boolean _compressed;
    private boolean _checkpoint;
    private boolean _copyback;
    private String _toFile;
    final List<String> _files = new ArrayList<String>();
    private volatile String _backupStatus;

    @Cmd("backup")
    static Task setupTask(@Arg("file|string|Archive file path") String file,
            @Arg("_flag|a|Start appendOnly mode") boolean start, @Arg("_flag|e|End appendOnly mode") boolean end,
            @Arg("_flag|c|Request checkpoint before backup") boolean checkpoint,
            @Arg("_flag|z|Compress output to ZIP format") boolean compressed,
            @Arg("_flag|f|Emit a list of files that need to be copied") boolean showFiles,
            @Arg("_flag|y|Copyback pages before starting") boolean copyback) throws Exception {
        final BackupTask task = new BackupTask();
        task._toFile = file;
        task._start = start;
        task._end = end;
        task._showFiles = showFiles;
        task._compressed = compressed;
        task._checkpoint = checkpoint;
        task._copyback = copyback;
        return task;
    }

    private void validate() {
        if (_toFile == null) {
            _toFile = "";
        }
    }

    @Override
    protected void runTask() throws Exception {
        validate();
        final Management management = _persistit.getManagement();
        boolean wasAppendOnly = management.getJournalInfo().isAppendOnly();
        if (_checkpoint) {
            postMessage("Waiting for checkpoint", 0);
            final Checkpoint cp = _persistit.checkpoint();
            if (cp == null) {
                postMessage("Checkpoint failed", 0);
            } else {
                postMessage("Checkpoint " + cp + " written", 0);
            }
        }
        if (_copyback && !wasAppendOnly) {
            postMessage("Copying back pages from journal", 0);
            long start = _persistit.getJournalManager().getCopiedPageCount();
            _persistit.copyBackPages();
            long end = _persistit.getJournalManager().getCopiedPageCount();
            postMessage((end - start) + " pages copied", 0);
        }
        try {
            if (_showFiles || !_toFile.isEmpty()) {
                management.setAppendOnly(true);
                populateBackupFiles();
                if (!_toFile.isEmpty()) {
                    doBackup();
                }
            }
        } catch (Exception e) {
            _backupStatus = "Failed: " + e;
        } finally {
            management.setAppendOnly(_start ? true : _end ? false : wasAppendOnly);
        }
    }

    @Override
    protected void postMessage(final String message, int level) {
        super.postMessage(message, level);
        _backupStatus = message;
    }

    private void populateBackupFiles() throws Exception {
        final VolumeInfo[] volumes = _persistit.getManagement().getVolumeInfoArray();
        for (final VolumeInfo info : volumes) {
            if (!info.isTransient()) {
                _files.add(info.getPath());
            }
        }
        final JournalInfo info = _persistit.getManagement().getJournalInfo();
        final long baseAddress = info.getBaseAddress();
        final long currentAddress = info.getCurrentJournalAddress();
        final long blockSize = info.getBlockSize();
        String path = JournalManager.fileToPath(new File(info.getCurrentJournalFile()));
        for (long generation = baseAddress / blockSize; generation <= currentAddress / blockSize; generation++) {
            File file = JournalManager.generationToFile(path, generation);
            _files.add(file.getAbsolutePath());
        }
        final StringBuilder sb = new StringBuilder();
        for (final String file : _files) {
            sb.append(file);
            sb.append(Util.NEW_LINE);
        }
        _backupStatus = sb.toString();
    }

    /**
     * A convenience method for backing up relatively small amounts of data from
     * within Persistit. In production it is expected that the actual file
     * copies required for backup will be performed be third-party utilities.
     *
     * @throws Exception
     */
    private void doBackup() throws Exception {
        final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(_toFile),
                BUFFER_SIZE));
        try {
            final byte[] buffer = new byte[65536];
            zos.setLevel(_compressed ? ZipOutputStream.DEFLATED : ZipOutputStream.STORED);
            long size = 0;
            for (final String file : _files) {
                size += new File(file).length();
            }
            postMessage("Total size of files in backup set: " + formatedSize(size), 0);
            for (final String path : _files) {
                final File file = new File(path);
                postMessage("Backing up " + path + " size=" + formatedSize(file.length()), 1);
                final ZipEntry ze = new ZipEntry(path);
                ze.setSize(file.length());
                ze.setTime(file.lastModified());
                zos.putNextEntry(ze);
                long progress = 0;
                long fileSize = 0;
                final BufferedInputStream is = new BufferedInputStream(new FileInputStream(file), BUFFER_SIZE);
                try {
                    int readCount = 0;
                    while ((readCount = is.read(buffer, 0, buffer.length)) != -1) {
                        zos.write(buffer, 0, readCount);
                        progress += readCount;
                        fileSize += readCount;
                        if (progress > PROGRESS_MARK_AT) {
                            progress -= PROGRESS_MARK_AT;
                            appendMessage(" (" + formatedSize(fileSize) + ")", 1);
                        }
                        poll();
                    }
                } finally {
                    is.close();
                }
            }
            postMessage("Backup of " + _files.size() + " files to " + _toFile + " completed", 0);
        } finally {
            zos.close();
        }
    }

    /**
     * A convenience method for unit tests to unzip a backup created by
     * {@link #doBackup()}. In production it is expected a backup produced by
     * {@link #doBackup()} will be restored via an external unzip utility.
     *
     * @throws Exception
     */
    public void doRestore(final String path) throws Exception {
        final File zipFile = new File(path);
        final byte[] buffer = new byte[65536];
        postMessage("Unzipping files from " + zipFile + " size=" + formatedSize(zipFile.length()), 0);
        final ZipInputStream zis = new ZipInputStream(
                new BufferedInputStream(new FileInputStream(zipFile), BUFFER_SIZE));
        ZipEntry ze;
        while ((ze = zis.getNextEntry()) != null) {
            postMessage("Unzipping " + ze, 0);
            final File file = new File(ze.getName());
            if (file.exists()) {
                rename(file);
            }
            long progress = 0;
            long fileSize = 0;
            final OutputStream os = new BufferedOutputStream(new FileOutputStream(file, false));
            int writeCount = 0;
            while ((writeCount = zis.read(buffer)) != -1) {
                os.write(buffer, 0, writeCount);
                progress += writeCount;
                fileSize += writeCount;
                if (progress > PROGRESS_MARK_AT) {
                    progress -= PROGRESS_MARK_AT;
                    appendMessage(" " + formatedSize(fileSize), 1);
                }
            }
            os.close();
        }
        zis.close();
    }

    private void rename(final File file) throws Exception {
        for (int k = 0; k < 1000; k++) {
            final String candidate = k == 0 ? file.getAbsolutePath() + "~" : file.getAbsoluteFile() + "~" + k;
            final File newFile = new File(candidate);
            if (!newFile.exists()) {
                file.renameTo(newFile);
                return;
            }
        }
        throw new IOException("Unable to rename file " + file);
    }

    private String formatedSize(final long size) {
        long value = size;
        int scale = 0;
        while (value > 9999) {
            value = (value + 499) / 1000;
            scale++;
        }
        return String.format("%,d", value) + " KMGTPE".substring(scale, scale + 1);
    }

    @Override
    public String getStatus() {
        return _backupStatus;
    }

    public List<String> getFileList() {
        return _files;
    }
}
TOP

Related Classes of com.persistit.BackupTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.