org.opendaylight.controller.cluster.raft.ReplicatedLogImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. 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
*/
package org.opendaylight.controller.cluster.raft;
import static java.util.Objects.requireNonNull;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.opendaylight.controller.cluster.raft.persisted.DeleteEntries;
import org.opendaylight.controller.cluster.raft.persisted.Snapshot;
/**
* Implementation of ReplicatedLog used by the RaftActor.
*/
final class ReplicatedLogImpl extends AbstractReplicatedLogImpl {
private static final int DATA_SIZE_DIVIDER = 5;
private final RaftActorContext context;
private long dataSizeSinceLastSnapshot = 0L;
private ReplicatedLogImpl(final long snapshotIndex, final long snapshotTerm,
final List unAppliedEntries,
final RaftActorContext context) {
super(snapshotIndex, snapshotTerm, unAppliedEntries, context.getId());
this.context = requireNonNull(context);
}
static ReplicatedLog newInstance(final Snapshot snapshot, final RaftActorContext context) {
return new ReplicatedLogImpl(snapshot.getLastAppliedIndex(), snapshot.getLastAppliedTerm(),
snapshot.getUnAppliedEntries(), context);
}
static ReplicatedLog newInstance(final RaftActorContext context) {
return new ReplicatedLogImpl(-1L, -1L, Collections.emptyList(), context);
}
@Override
public boolean removeFromAndPersist(final long logEntryIndex) {
long adjustedIndex = removeFrom(logEntryIndex);
if (adjustedIndex >= 0) {
context.getPersistenceProvider().persist(new DeleteEntries(logEntryIndex), NoopProcedure.instance());
return true;
}
return false;
}
@Override
public boolean shouldCaptureSnapshot(final long logIndex) {
final ConfigParams config = context.getConfigParams();
if ((logIndex + 1) % config.getSnapshotBatchCount() == 0) {
return true;
}
final long absoluteThreshold = config.getSnapshotDataThreshold();
final long dataThreshold = absoluteThreshold != 0 ? absoluteThreshold * ConfigParams.MEGABYTE
: context.getTotalMemory() * config.getSnapshotDataThresholdPercentage() / 100;
return getDataSizeForSnapshotCheck() > dataThreshold;
}
@Override
public void captureSnapshotIfReady(final ReplicatedLogEntry replicatedLogEntry) {
if (shouldCaptureSnapshot(replicatedLogEntry.getIndex())) {
boolean started = context.getSnapshotManager().capture(replicatedLogEntry,
context.getCurrentBehavior().getReplicatedToAllIndex());
if (started && !context.hasFollowers()) {
dataSizeSinceLastSnapshot = 0;
}
}
}
private long getDataSizeForSnapshotCheck() {
if (!context.hasFollowers()) {
// When we do not have followers we do not maintain an in-memory log
// due to this the journalSize will never become anything close to the
// snapshot batch count. In fact will mostly be 1.
// Similarly since the journal's dataSize depends on the entries in the
// journal the journal's dataSize will never reach a value close to the
// memory threshold.
// By maintaining the dataSize outside the journal we are tracking essentially
// what we have written to the disk however since we no longer are in
// need of doing a snapshot just for the sake of freeing up memory we adjust
// the real size of data by the DATA_SIZE_DIVIDER so that we do not snapshot as often
// as if we were maintaining a real snapshot
return dataSizeSinceLastSnapshot / DATA_SIZE_DIVIDER;
} else {
return dataSize();
}
}
@Override
public boolean appendAndPersist(final ReplicatedLogEntry replicatedLogEntry,
final Consumer callback, final boolean doAsync) {
context.getLogger().debug("{}: Append log entry and persist {} ", context.getId(), replicatedLogEntry);
if (!append(replicatedLogEntry)) {
return false;
}
if (doAsync) {
context.getPersistenceProvider().persistAsync(replicatedLogEntry,
entry -> persistCallback(entry, callback));
} else {
context.getPersistenceProvider().persist(replicatedLogEntry, entry -> syncPersistCallback(entry, callback));
}
return true;
}
private void persistCallback(final ReplicatedLogEntry persistedLogEntry,
final Consumer callback) {
context.getExecutor().execute(() -> syncPersistCallback(persistedLogEntry, callback));
}
private void syncPersistCallback(final ReplicatedLogEntry persistedLogEntry,
final Consumer callback) {
context.getLogger().debug("{}: persist complete {}", context.getId(), persistedLogEntry);
dataSizeSinceLastSnapshot += persistedLogEntry.size();
if (callback != null) {
callback.accept(persistedLogEntry);
}
}
}