mirror of https://github.com/apache/lucene.git
SOLR-2578: ReplicationHandler's backup command now supports a 'numberToKeep' param that can be used to delete all but the most recent N backups.
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1202969 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
873f199924
commit
2a94133a2c
|
@ -396,6 +396,10 @@ New Features
|
||||||
* SOLR-1023: StatsComponent now supports date fields and string fields.
|
* SOLR-1023: StatsComponent now supports date fields and string fields.
|
||||||
(Chris Male, Mark Holland, Gunnlaugur Thor Briem, Ryan McKinley)
|
(Chris Male, Mark Holland, Gunnlaugur Thor Briem, Ryan McKinley)
|
||||||
|
|
||||||
|
* SOLR-2578: ReplicationHandler's backup command now supports a 'numberToKeep'
|
||||||
|
param that can be used to delete all but the most recent N backups.
|
||||||
|
(James Dyer via hossman)
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
|
||||||
}
|
}
|
||||||
// This command does not give the current index version of the master
|
// This command does not give the current index version of the master
|
||||||
// It gives the current 'replicateable' index version
|
// It gives the current 'replicateable' index version
|
||||||
if (command.equals(CMD_INDEX_VERSION)) {
|
if (command.equals(CMD_INDEX_VERSION)) {
|
||||||
IndexCommit commitPoint = indexCommitPoint; // make a copy so it won't change
|
IndexCommit commitPoint = indexCommitPoint; // make a copy so it won't change
|
||||||
if (commitPoint != null && replicationEnabled.get()) {
|
if (commitPoint != null && replicationEnabled.get()) {
|
||||||
//
|
//
|
||||||
|
@ -202,10 +202,10 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
|
||||||
} else if (CMD_ENABLE_REPL.equalsIgnoreCase(command)) {
|
} else if (CMD_ENABLE_REPL.equalsIgnoreCase(command)) {
|
||||||
replicationEnabled.set(true);
|
replicationEnabled.set(true);
|
||||||
rsp.add(STATUS, OK_STATUS);
|
rsp.add(STATUS, OK_STATUS);
|
||||||
} else if (CMD_DISABLE_REPL.equalsIgnoreCase(command)) {
|
} else if (CMD_DISABLE_REPL.equalsIgnoreCase(command)) {
|
||||||
replicationEnabled.set(false);
|
replicationEnabled.set(false);
|
||||||
rsp.add(STATUS, OK_STATUS);
|
rsp.add(STATUS, OK_STATUS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<NamedList<Object>> getCommits() {
|
private List<NamedList<Object>> getCommits() {
|
||||||
|
@ -296,16 +296,17 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
|
||||||
|
|
||||||
private void doSnapShoot(SolrParams params, SolrQueryResponse rsp, SolrQueryRequest req) {
|
private void doSnapShoot(SolrParams params, SolrQueryResponse rsp, SolrQueryRequest req) {
|
||||||
try {
|
try {
|
||||||
|
int numberToKeep = params.getInt(NUMBER_BACKUPS_TO_KEEP, Integer.MAX_VALUE);
|
||||||
IndexDeletionPolicyWrapper delPolicy = core.getDeletionPolicy();
|
IndexDeletionPolicyWrapper delPolicy = core.getDeletionPolicy();
|
||||||
IndexCommit indexCommit = delPolicy.getLatestCommit();
|
IndexCommit indexCommit = delPolicy.getLatestCommit();
|
||||||
|
|
||||||
if(indexCommit == null) {
|
if(indexCommit == null) {
|
||||||
indexCommit = req.getSearcher().getIndexReader().getIndexCommit();
|
indexCommit = req.getSearcher().getIndexReader().getIndexCommit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// small race here before the commit point is saved
|
// small race here before the commit point is saved
|
||||||
new SnapShooter(core, params.get("location")).createSnapAsync(indexCommit, this);
|
new SnapShooter(core, params.get("location")).createSnapAsync(indexCommit, numberToKeep, this);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Exception during creating a snapshot", e);
|
LOG.warn("Exception during creating a snapshot", e);
|
||||||
rsp.add("exception", e);
|
rsp.add("exception", e);
|
||||||
|
@ -354,7 +355,7 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
|
||||||
rsp.add("status", "unable to get file names for given indexversion");
|
rsp.add("status", "unable to get file names for given indexversion");
|
||||||
rsp.add("exception", e);
|
rsp.add("exception", e);
|
||||||
LOG.warn("Unable to get file names for indexCommit version: "
|
LOG.warn("Unable to get file names for indexCommit version: "
|
||||||
+ version, e);
|
+ version, e);
|
||||||
}
|
}
|
||||||
rsp.add(CMD_GET_FILE_LIST, result);
|
rsp.add(CMD_GET_FILE_LIST, result);
|
||||||
if (confFileNameAlias.size() < 1)
|
if (confFileNameAlias.size() < 1)
|
||||||
|
@ -1146,4 +1147,6 @@ public class ReplicationHandler extends RequestHandlerBase implements SolrCoreAw
|
||||||
public static final String OK_STATUS = "OK";
|
public static final String OK_STATUS = "OK";
|
||||||
|
|
||||||
public static final String NEXT_EXECUTION_AT = "nextExecutionAt";
|
public static final String NEXT_EXECUTION_AT = "nextExecutionAt";
|
||||||
|
|
||||||
|
public static final String NUMBER_BACKUPS_TO_KEEP = "numberToKeep";
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,14 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.lucene.index.IndexCommit;
|
import org.apache.lucene.index.IndexCommit;
|
||||||
|
@ -59,19 +64,23 @@ public class SnapShooter {
|
||||||
}
|
}
|
||||||
lockFactory = new SimpleFSLockFactory(snapDir);
|
lockFactory = new SimpleFSLockFactory(snapDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
void createSnapAsync(final IndexCommit indexCommit, final ReplicationHandler replicationHandler) {
|
void createSnapAsync(final IndexCommit indexCommit, final ReplicationHandler replicationHandler) {
|
||||||
|
createSnapAsync(indexCommit, Integer.MAX_VALUE, replicationHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void createSnapAsync(final IndexCommit indexCommit, final int numberToKeep, final ReplicationHandler replicationHandler) {
|
||||||
replicationHandler.core.getDeletionPolicy().saveCommitPoint(indexCommit.getVersion());
|
replicationHandler.core.getDeletionPolicy().saveCommitPoint(indexCommit.getVersion());
|
||||||
|
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
createSnapshot(indexCommit, replicationHandler);
|
createSnapshot(indexCommit, numberToKeep, replicationHandler);
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void createSnapshot(final IndexCommit indexCommit, ReplicationHandler replicationHandler) {
|
void createSnapshot(final IndexCommit indexCommit, int numberToKeep, ReplicationHandler replicationHandler) {
|
||||||
|
|
||||||
NamedList<Object> details = new NamedList<Object>();
|
NamedList<Object> details = new NamedList<Object>();
|
||||||
details.add("startTime", new Date().toString());
|
details.add("startTime", new Date().toString());
|
||||||
|
@ -79,6 +88,9 @@ public class SnapShooter {
|
||||||
String directoryName = null;
|
String directoryName = null;
|
||||||
Lock lock = null;
|
Lock lock = null;
|
||||||
try {
|
try {
|
||||||
|
if(numberToKeep<Integer.MAX_VALUE) {
|
||||||
|
deleteOldBackups(numberToKeep);
|
||||||
|
}
|
||||||
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT, Locale.US);
|
SimpleDateFormat fmt = new SimpleDateFormat(DATE_FMT, Locale.US);
|
||||||
directoryName = "snapshot." + fmt.format(new Date());
|
directoryName = "snapshot." + fmt.format(new Date());
|
||||||
lock = lockFactory.makeLock(directoryName + ".lock");
|
lock = lockFactory.makeLock(directoryName + ".lock");
|
||||||
|
@ -100,8 +112,8 @@ public class SnapShooter {
|
||||||
LOG.error("Exception while creating snapshot", e);
|
LOG.error("Exception while creating snapshot", e);
|
||||||
details.add("snapShootException", e.getMessage());
|
details.add("snapShootException", e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
replicationHandler.core.getDeletionPolicy().releaseCommitPoint(indexCommit.getVersion());
|
replicationHandler.core.getDeletionPolicy().releaseCommitPoint(indexCommit.getVersion());
|
||||||
replicationHandler.snapShootDetails = details;
|
replicationHandler.snapShootDetails = details;
|
||||||
if (lock != null) {
|
if (lock != null) {
|
||||||
try {
|
try {
|
||||||
lock.release();
|
lock.release();
|
||||||
|
@ -111,6 +123,46 @@ public class SnapShooter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void deleteOldBackups(int numberToKeep) {
|
||||||
|
File[] files = new File(snapDir).listFiles();
|
||||||
|
List<OldBackupDirectory> dirs = new ArrayList<OldBackupDirectory>();
|
||||||
|
for(File f : files) {
|
||||||
|
OldBackupDirectory obd = new OldBackupDirectory(f);
|
||||||
|
if(obd.dir != null) {
|
||||||
|
dirs.add(obd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(dirs);
|
||||||
|
int i=1;
|
||||||
|
for(OldBackupDirectory dir : dirs) {
|
||||||
|
if( i > numberToKeep-1 ) {
|
||||||
|
SnapPuller.delTree(dir.dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private class OldBackupDirectory implements Comparable<OldBackupDirectory>{
|
||||||
|
File dir;
|
||||||
|
Date timestamp;
|
||||||
|
final Pattern dirNamePattern = Pattern.compile("^snapshot[.](.*)$");
|
||||||
|
|
||||||
|
OldBackupDirectory(File dir) {
|
||||||
|
if(dir.isDirectory()) {
|
||||||
|
Matcher m = dirNamePattern.matcher(dir.getName());
|
||||||
|
if(m.find()) {
|
||||||
|
try {
|
||||||
|
this.dir = dir;
|
||||||
|
this.timestamp = new SimpleDateFormat(DATE_FMT).parse(m.group(1));
|
||||||
|
} catch(Exception e) {
|
||||||
|
this.dir = null;
|
||||||
|
this.timestamp = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int compareTo(OldBackupDirectory that) {
|
||||||
|
return that.timestamp.compareTo(this.timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static final String SNAP_DIR = "snapDir";
|
public static final String SNAP_DIR = "snapDir";
|
||||||
public static final String DATE_FMT = "yyyyMMddHHmmss";
|
public static final String DATE_FMT = "yyyyMMddHHmmss";
|
||||||
|
@ -135,7 +187,7 @@ public class SnapShooter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyFile(File source, File destination, boolean preserveFileDate)
|
public void copyFile(File source, File destination, boolean preserveFileDate)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
// check source exists
|
// check source exists
|
||||||
if (!source.exists()) {
|
if (!source.exists()) {
|
||||||
String message = "File " + source + " does not exist";
|
String message = "File " + source + " does not exist";
|
||||||
|
@ -185,7 +237,7 @@ public class SnapShooter {
|
||||||
|
|
||||||
if (source.length() != destination.length()) {
|
if (source.length() != destination.length()) {
|
||||||
String message = "Failed to copy full contents from " + source + " to "
|
String message = "Failed to copy full contents from " + source + " to "
|
||||||
+ destination;
|
+ destination;
|
||||||
throw new IOException(message);
|
throw new IOException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,9 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.lucene.index.IndexReader;
|
import org.apache.lucene.index.IndexReader;
|
||||||
import org.apache.lucene.search.IndexSearcher;
|
import org.apache.lucene.search.IndexSearcher;
|
||||||
|
@ -753,7 +755,7 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
|
||||||
volatile String fail = null;
|
volatile String fail = null;
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
String masterUrl = "http://localhost:" + masterJetty.getLocalPort() + "/solr/replication?command=" + ReplicationHandler.CMD_BACKUP;
|
String masterUrl = "http://localhost:" + masterJetty.getLocalPort() + "/solr/replication?command=" + ReplicationHandler.CMD_BACKUP + "&" + ReplicationHandler.NUMBER_BACKUPS_TO_KEEP + "=1";
|
||||||
URL url;
|
URL url;
|
||||||
InputStream stream = null;
|
InputStream stream = null;
|
||||||
try {
|
try {
|
||||||
|
@ -768,14 +770,18 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
BackupThread backupThread = new BackupThread();
|
|
||||||
backupThread.start();
|
|
||||||
|
|
||||||
File dataDir = new File(master.getDataDir());
|
|
||||||
class CheckStatus extends Thread {
|
class CheckStatus extends Thread {
|
||||||
volatile String fail = null;
|
volatile String fail = null;
|
||||||
volatile String response = null;
|
volatile String response = null;
|
||||||
volatile boolean success = false;
|
volatile boolean success = false;
|
||||||
|
volatile String backupTimestamp = null;
|
||||||
|
final String lastBackupTimestamp;
|
||||||
|
final Pattern p = Pattern.compile("<str name=\"snapshotCompletedAt\">(.*?)</str>");
|
||||||
|
|
||||||
|
CheckStatus(String lastBackupTimestamp) {
|
||||||
|
this.lastBackupTimestamp = lastBackupTimestamp;
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
String masterUrl = "http://localhost:" + masterJetty.getLocalPort() + "/solr/replication?command=" + ReplicationHandler.CMD_DETAILS;
|
String masterUrl = "http://localhost:" + masterJetty.getLocalPort() + "/solr/replication?command=" + ReplicationHandler.CMD_DETAILS;
|
||||||
|
@ -786,7 +792,14 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
|
||||||
stream = url.openStream();
|
stream = url.openStream();
|
||||||
response = IOUtils.toString(stream, "UTF-8");
|
response = IOUtils.toString(stream, "UTF-8");
|
||||||
if(response.contains("<str name=\"status\">success</str>")) {
|
if(response.contains("<str name=\"status\">success</str>")) {
|
||||||
success = true;
|
Matcher m = p.matcher(response);
|
||||||
|
if(!m.find()) {
|
||||||
|
fail("could not find the completed timestamp in response.");
|
||||||
|
}
|
||||||
|
backupTimestamp = m.group(1);
|
||||||
|
if(!backupTimestamp.equals(lastBackupTimestamp)) {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
stream.close();
|
stream.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -797,48 +810,67 @@ public class TestReplicationHandler extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
int waitCnt = 0;
|
|
||||||
CheckStatus checkStatus = new CheckStatus();
|
File[] snapDir = new File[2];
|
||||||
while(true) {
|
String firstBackupTimestamp = null;
|
||||||
checkStatus.run();
|
for(int i=0 ; i<2 ; i++) {
|
||||||
if(checkStatus.fail != null) {
|
BackupThread backupThread = new BackupThread();
|
||||||
fail(checkStatus.fail);
|
backupThread.start();
|
||||||
|
|
||||||
|
File dataDir = new File(master.getDataDir());
|
||||||
|
|
||||||
|
int waitCnt = 0;
|
||||||
|
CheckStatus checkStatus = new CheckStatus(firstBackupTimestamp);
|
||||||
|
while(true) {
|
||||||
|
checkStatus.run();
|
||||||
|
if(checkStatus.fail != null) {
|
||||||
|
fail(checkStatus.fail);
|
||||||
|
}
|
||||||
|
if(checkStatus.success) {
|
||||||
|
if(i==0) {
|
||||||
|
firstBackupTimestamp = checkStatus.backupTimestamp;
|
||||||
|
Thread.sleep(1000); //ensure the next backup will have a different timestamp.
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Thread.sleep(200);
|
||||||
|
if(waitCnt == 10) {
|
||||||
|
fail("Backup success not detected:" + checkStatus.response);
|
||||||
|
}
|
||||||
|
waitCnt++;
|
||||||
}
|
}
|
||||||
if(checkStatus.success) {
|
|
||||||
break;
|
if(backupThread.fail != null) {
|
||||||
|
fail(backupThread.fail);
|
||||||
}
|
}
|
||||||
Thread.sleep(200);
|
|
||||||
if(waitCnt == 10) {
|
File[] files = dataDir.listFiles(new FilenameFilter() {
|
||||||
fail("Backup success not detected:" + checkStatus.response);
|
|
||||||
}
|
public boolean accept(File dir, String name) {
|
||||||
waitCnt++;
|
if(name.startsWith("snapshot")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertEquals(1, files.length);
|
||||||
|
snapDir[i] = files[0];
|
||||||
|
Directory dir = new SimpleFSDirectory(snapDir[i].getAbsoluteFile());
|
||||||
|
IndexReader reader = IndexReader.open(dir);
|
||||||
|
IndexSearcher searcher = new IndexSearcher(reader);
|
||||||
|
TopDocs hits = searcher.search(new MatchAllDocsQuery(), 1);
|
||||||
|
assertEquals(nDocs, hits.totalHits);
|
||||||
|
reader.close();
|
||||||
|
searcher.close();
|
||||||
|
dir.close();
|
||||||
|
}
|
||||||
|
if(snapDir[0].exists()) {
|
||||||
|
fail("The first backup should have been cleaned up because " + ReplicationHandler.NUMBER_BACKUPS_TO_KEEP + " was set to 1");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(backupThread.fail != null) {
|
for(int i=0 ; i< snapDir.length ; i++) {
|
||||||
fail(backupThread.fail);
|
AbstractSolrTestCase.recurseDelete(snapDir[i]); // clean up the snap dir
|
||||||
}
|
}
|
||||||
|
|
||||||
File[] files = dataDir.listFiles(new FilenameFilter() {
|
|
||||||
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
if(name.startsWith("snapshot")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assertEquals(1, files.length);
|
|
||||||
File snapDir = files[0];
|
|
||||||
Directory dir = new SimpleFSDirectory(snapDir.getAbsoluteFile());
|
|
||||||
IndexReader reader = IndexReader.open(dir);
|
|
||||||
IndexSearcher searcher = new IndexSearcher(reader);
|
|
||||||
TopDocs hits = searcher.search(new MatchAllDocsQuery(), 1);
|
|
||||||
|
|
||||||
assertEquals(nDocs, hits.totalHits);
|
|
||||||
searcher.close();
|
|
||||||
reader.close();
|
|
||||||
dir.close();
|
|
||||||
AbstractSolrTestCase.recurseDelete(snapDir); // clean up the snap dir
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* character copy of file using UTF-8 */
|
/* character copy of file using UTF-8 */
|
||||||
|
|
Loading…
Reference in New Issue