001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: DbBackup.java,v 1.10.2.3 2008/01/07 15:14:17 cwl Exp $
007: */
008:
009: package com.sleepycat.je.util;
010:
011: import com.sleepycat.je.DatabaseException;
012: import com.sleepycat.je.DbInternal;
013: import com.sleepycat.je.Environment;
014: import com.sleepycat.je.dbi.EnvironmentImpl;
015: import com.sleepycat.je.log.FileManager;
016: import com.sleepycat.je.utilint.DbLsn;
017:
018: /**
019: * DbBackup is a helper class for stopping and restarting JE background
020: * activity in an open environment in order to simplify backup operations. It
021: * also lets the application create a backup which can support restoring the
022: * environment to a specific point in time.
023: * <p>
024: * <b>Backing up without DbBackup:</b>
025: * Because JE has an append only log file architecture, it is always possible
026: * to do a hot backup without the use of DbBackup by copying all log files
027: * (.jdb files) to your archival location. As long as the log files are copied
028: * in alphabetical order, (numerical in effect) <i>and</i> all log files are
029: * copied, the environment can be successfully backed up without any need to
030: * stop database operations or background activity. This means that your
031: * backup operation must do a loop to check for the creation of new log files
032: * before deciding that the backup is finished. For example:
033: * <pre>
034: * time files in activity
035: * environment
036: *
037: * t0 000000001.jdb Backup starts copying file 1
038: * 000000003.jdb
039: * 000000004.jdb
040: *
041: * t1 000000001.jdb JE log cleaner migrates portion of file 3 to newly
042: * 000000004.jdb created file 5 and deletes file 3. Backup finishes
043: * 000000005.jdb file 1, starts copying file 4. Backup MUST include
044: * file 5 for a consistent backup!
045: *
046: * t2 000000001.jdb Backup finishes copying file 4, starts and finishes
047: * 000000004.jdb file 5, has caught up. Backup ends.
048: * 000000005.jdb
049: *</pre>
050: * <p>
051: * In the example above, the backup operation must be sure to copy file 5,
052: * which came into existence after the backup had started. If the backup
053: * stopped operations at file 4, the backup set would include only file 1 and
054: * 4, omitting file 3, which would be an inconsistent set.
055: * <p>
056: * Also note that log file 5 may not have filled up before it was copied to
057: * archival storage. On the next backup, there might be a newer, larger version
058: * of file 5, and that newer version should replace the older file 5 in archive
059: * storage.
060: * <p>
061: * <b>Backup up with DbBackup</b>
062: * <p>
063: * DbBackup helps simplify application backup by defining the set of files that
064: * must be copied for each backup operation. If the environment directory has
065: * read/write protection, the application must pass DbBackup an open,
066: * read/write environment handle.
067: * <p>
068: * When entering backup mode, JE
069: * determines the set of log files needed for a consistent backup, and freezes
070: * all changes to those files. The application can copy that defined set of
071: * files and finish operation without checking for the ongoing creation of new
072: * files. Also, there will be no need to check for a newer version of the last
073: * file on the next backup.
074: * <p>
075: * In the example above, if DbBackupHelper was used at t0, the application
076: * would only have to copy files 1, 3 and 4 to back up. On a subsequent backup,
077: * the application could start its copying at file 5. There would be no need
078: * to check for a newer version of file 4.
079: * <p>
080: * An example usage:
081: * <pre>
082: *
083: * Environment env = new Environment(...);
084: * DbBackup backupHelper = new DbBackup(env);
085: *
086: * // Find the file number of the last file in the previous backup
087: * // persistently, by either checking the backup archive, or saving
088: * // state in a persistent file.
089: * long lastFileCopiedInPrevBackup = ...
090: *
091: * // Start backup, find out what needs to be copied.
092: * backupHelper.startBackup();
093: * try {
094: * String[] filesForBackup =
095: * backupHelper.getLogFilesInBackupSet(lastFileCopiedInPrevBackup);
096: *
097: * // Copy the files to archival storage.
098: * myApplicationCopyMethod(filesForBackup)
099:
100: * // Update our knowlege of the last file saved in the backup set,
101: * // so we can copy less on the next backup
102: * lastFileCopiedInPrevBackup = backupHelper.getLastFileInBackupSet();
103: * myApplicationSaveLastFile(lastFileCopiedInBackupSet);
104: * } finally {
105: * // Remember to exit backup mode, or all log files won't be cleaned
106: * // and disk usage will bloat.
107: * backupHelper.endBackup();
108: * }
109: */
110: public class DbBackup {
111:
112: private EnvironmentImpl envImpl;
113: private boolean backupStarted;
114: private long lastFileInBackupSet = -1;
115: private boolean envIsReadOnly;
116:
117: /**
118: * DbBackup must be created with an open, valid environment handle.
119: * If the environment directory has read/write permissions, the environment
120: * handle must be configured for read/write.
121: */
122: public DbBackup(Environment env) throws DatabaseException {
123:
124: /* Check that the Environment is open. */
125: env.checkHandleIsValid();
126: envImpl = DbInternal.envGetEnvironmentImpl(env);
127: FileManager fileManager = envImpl.getFileManager();
128:
129: /*
130: * If the environment is writable, we need a r/w environment handle
131: * in order to flip the file.
132: */
133: envIsReadOnly = fileManager.checkEnvHomePermissions(true);
134: if ((!envIsReadOnly) && envImpl.isReadOnly()) {
135: throw new DatabaseException(this .getClass().getName()
136: + " requires a read/write Environment handle");
137: }
138: }
139:
140: /**
141: * Start backup mode in order to determine the definitive backup set needed
142: * for this point in time. After calling this method, log cleaning will be
143: * disabled until endBackup() is called. Be sure to call endBackup() to
144: * re-enable log cleaning or disk space usage will bloat.
145: */
146: public synchronized void startBackup() throws DatabaseException {
147:
148: if (backupStarted) {
149: throw new DatabaseException(this .getClass().getName()
150: + ".startBackup was already called");
151: }
152:
153: backupStarted = true;
154:
155: try {
156: /* Prevent any file deletions. */
157: envImpl.getCleaner().setDeleteProhibited();
158:
159: FileManager fileManager = envImpl.getFileManager();
160:
161: /*
162: * Flip the log so that we can know that the list of files
163: * corresponds to a given point.
164: */
165: if (envIsReadOnly) {
166: lastFileInBackupSet = fileManager.getLastFileNum()
167: .longValue();
168: } else {
169: long newFileNum = envImpl.forceLogFileFlip();
170: lastFileInBackupSet = DbLsn.getFileNumber(newFileNum) - 1;
171: }
172: } catch (DatabaseException e) {
173: backupStarted = false;
174: throw e;
175: }
176: }
177:
178: /**
179: * End backup mode, thereby re-enabling normal JE log cleaning.
180: */
181: public synchronized void endBackup() throws DatabaseException {
182:
183: checkBackupStarted();
184:
185: try {
186: envImpl.getCleaner().clearDeleteProhibited();
187: } finally {
188: backupStarted = false;
189: }
190: }
191:
192: /**
193: * Can only be called in backup mode, after startBackup() has been called.
194: *
195: * @return the file number of the last file in the current backup set.
196: * Save this value to reduce the number of files that must be copied at
197: * the next backup session.
198: */
199: public synchronized long getLastFileInBackupSet()
200: throws DatabaseException {
201:
202: checkBackupStarted();
203: return lastFileInBackupSet;
204: }
205:
206: /**
207: * Get the list of all files that are needed for the environment at the
208: * point of time when backup mode started. Can only be called in backup
209: * mode, after startBackup() has been called.
210: *
211: * @return the names of all files in the backup set, sorted in alphabetical
212: * order.
213: */
214: public synchronized String[] getLogFilesInBackupSet()
215: throws DatabaseException {
216:
217: checkBackupStarted();
218: return envImpl.getFileManager().listFiles(0,
219: lastFileInBackupSet);
220: }
221:
222: /**
223: * Get the minimum list of files that must be copied for this backup. This
224: * consists of the set of backup files that are greater than the last file
225: * copied in the previous backup session. Can only be called in backup
226: * mode, after startBackup() has been called.
227: *
228: * @param lastFileCopiedInPrevBackup file number of last file copied in the
229: * last backup session, obtained from getLastFileInBackupSet().
230: *
231: * @return the names of all the files in the backup set that come after
232: * lastFileCopiedInPrevBackup.
233: */
234: public synchronized String[] getLogFilesInBackupSet(
235: long lastFileCopiedInPrevBackup) throws DatabaseException {
236:
237: checkBackupStarted();
238: FileManager fileManager = envImpl.getFileManager();
239: return fileManager.listFiles(lastFileCopiedInPrevBackup + 1,
240: lastFileInBackupSet);
241: }
242:
243: private void checkBackupStarted() throws DatabaseException {
244:
245: if (!backupStarted) {
246: throw new DatabaseException(this .getClass().getName()
247: + ".startBackup was not called");
248: }
249: }
250: }
|