package lsr.paxos;
import static lsr.common.ProcessDescriptor.processDescriptor;
import lsr.common.MovingAverage;
import lsr.common.SingleThreadDispatcher;
import lsr.paxos.storage.LogListener;
import lsr.paxos.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is informed when the log size is changed, asking the state machine
* (if necessary) for a snapshot.
*
* If a snapshot is created by the state machine, SnapshotMaintainer writes it
* to storage and truncates logs.
*/
public class SnapshotMaintainer implements LogListener {
private final Storage storage;
/** Current snapshot size estimate */
private MovingAverage snapshotByteSizeEstimate = new MovingAverage(0.75,
processDescriptor.firstSnapshotSizeEstimate);
/**
* After how many new instances we are recalculating if snapshot is needed.
* By default it's 1/5 of instances for last snapshot.
*/
private int samplingRate = processDescriptor.minSnapshotSampling;
/** Instance, by which we calculated last time if we need snapshot */
private int lastSamplingInstance = 0;
private final SingleThreadDispatcher paxosDispatcher;
private final SnapshotProvider snapshotProvider;
/** Indicates if we asked for snapshot */
private boolean askedForSnapshot = false;
/** if we forced for snapshot */
private boolean forcedSnapshot = false;
public SnapshotMaintainer(Storage storage, SingleThreadDispatcher dispatcher,
SnapshotProvider replica) {
this.storage = storage;
this.paxosDispatcher = dispatcher;
this.snapshotProvider = replica;
}
/** Receives a snapshot from state machine, records it and truncates the log */
public void onSnapshotMade(final Snapshot snapshot) {
// Called by the Replica thread. Queue it for execution on the Paxos
// dispatcher.
paxosDispatcher.submit(new Runnable() {
public void run() {
logger.debug("Snapshot made. next instance: {}", snapshot.getNextInstanceId());
int previousSnapshotInstanceId = 0;
Snapshot lastSnapshot = storage.getLastSnapshot();
if (lastSnapshot != null) {
previousSnapshotInstanceId = lastSnapshot.getNextInstanceId();
if (previousSnapshotInstanceId > snapshot.getNextInstanceId()) {
logger.warn("Got snapshot older than current one! Dropping.");
return;
}
}
storage.setLastSnapshot(snapshot);
storage.getLog().truncateBelow(previousSnapshotInstanceId);
askedForSnapshot = forcedSnapshot = false;
snapshotByteSizeEstimate.add(snapshot.getValue().length);
if (logger.isDebugEnabled(processDescriptor.logMark_OldBenchmark))
logger.debug(
processDescriptor.logMark_OldBenchmark,
"Snapshot received from state machine for: {} (previous: {}) New size estimate: {}",
snapshot.getNextInstanceId(), previousSnapshotInstanceId,
snapshotByteSizeEstimate.get());
samplingRate = Math.max(
(snapshot.getNextInstanceId() - previousSnapshotInstanceId) / 5,
processDescriptor.minSnapshotSampling);
}
});
}
/**
* Decides if a snapshot needs to be requested based on the current size of
* the log
*/
public void logSizeChanged(int newsize) {
assert paxosDispatcher.amIInDispatcher() : "Only Dispatcher thread allowed. Called from " +
Thread.currentThread().getName();
if (askedForSnapshot && forcedSnapshot) {
return;
}
if ((storage.getLog().getNextId() - lastSamplingInstance) < samplingRate) {
return;
}
lastSamplingInstance = storage.getLog().getNextId();
Snapshot lastSnapshot = storage.getLastSnapshot();
int lastSnapshotInstance = lastSnapshot == null ? 0 : lastSnapshot.getNextInstanceId();
long logByteSize = storage.getLog().byteSizeBetween(lastSnapshotInstance,
storage.getFirstUncommitted());
logger.debug("Calculated log size for {}", logByteSize);
// Don't do a snapshot if the log is too small
if (logByteSize < processDescriptor.snapshotMinLogSize) {
return;
}
double sizeRatio = logByteSize / snapshotByteSizeEstimate.get();
if (!askedForSnapshot) {
if (sizeRatio < processDescriptor.snapshotAskRatio) {
return;
}
logger.debug("Asking state machine for shapshot");
snapshotProvider.askForSnapshot();
askedForSnapshot = true;
return;
}
if (!forcedSnapshot) {
if (sizeRatio < processDescriptor.snapshotForceRatio) {
return;
}
logger.debug("Forcing state machine to do shapshot");
snapshotProvider.forceSnapshot();
forcedSnapshot = true;
return;
}
}
private final static Logger logger = LoggerFactory.getLogger(SnapshotMaintainer.class);
}