Skip to content

Commit c454d78

Browse files
authored
RATIS-2228. Refactor the offered map in LogAppenderBase.nextAppendEntriesRequest (#1201)
1 parent 539e804 commit c454d78

File tree

4 files changed

+142
-77
lines changed

4 files changed

+142
-77
lines changed

ratis-server/src/main/java/org/apache/ratis/server/leader/LogAppenderBase.java

Lines changed: 128 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,104 @@
3737
import org.apache.ratis.util.SizeInBytes;
3838
import org.apache.ratis.util.TimeDuration;
3939

40+
import java.util.Collection;
4041
import java.util.Collections;
4142
import java.util.HashMap;
4243
import java.util.List;
4344
import java.util.Map;
4445
import java.util.Objects;
45-
import java.util.Optional;
4646
import java.util.concurrent.CompletableFuture;
47-
import java.util.concurrent.TimeUnit;
4847
import java.util.concurrent.atomic.AtomicBoolean;
4948
import java.util.function.LongUnaryOperator;
5049

5150
/**
5251
* An abstract implementation of {@link LogAppender}.
5352
*/
5453
public abstract class LogAppenderBase implements LogAppender {
54+
/** For buffering log entries to create an {@link EntryList}. */
55+
private static class EntryBuffer {
56+
/** A queue for limiting the byte size, number of elements and poll time. */
57+
private final DataQueue<EntryWithData> queue;
58+
/** A map for releasing {@link ReferenceCountedObject}s. */
59+
private final Map<Long, ReferenceCountedObject<EntryWithData>> references = new HashMap<>();
60+
61+
EntryBuffer(Object name, RaftProperties properties) {
62+
final SizeInBytes bufferByteLimit = RaftServerConfigKeys.Log.Appender.bufferByteLimit(properties);
63+
final int bufferElementLimit = RaftServerConfigKeys.Log.Appender.bufferElementLimit(properties);
64+
this.queue = new DataQueue<>(name, bufferByteLimit, bufferElementLimit, EntryWithData::getSerializedSize);
65+
}
66+
67+
boolean putNew(long index, ReferenceCountedObject<EntryWithData> retained) {
68+
if (!queue.offer(retained.get())) {
69+
retained.release();
70+
return false;
71+
}
72+
final ReferenceCountedObject<EntryWithData> previous = references.put(index, retained);
73+
Preconditions.assertNull(previous, () -> "previous with index " + index);
74+
return true;
75+
}
76+
77+
void releaseAllAndClear() {
78+
for (ReferenceCountedObject<EntryWithData> ref : references.values()) {
79+
ref.release();
80+
}
81+
references.clear();
82+
queue.clear();
83+
}
84+
85+
EntryList pollList(long heartbeatWaitTimeMs) throws RaftLogIOException {
86+
final List<LogEntryProto> protos;
87+
try {
88+
protos = queue.pollList(heartbeatWaitTimeMs, EntryWithData::getEntry, null);
89+
} catch (Exception e) {
90+
releaseAllAndClear();
91+
throw e;
92+
} finally {
93+
for (EntryWithData entry : queue) {
94+
// Remove and release remaining entries.
95+
final ReferenceCountedObject<EntryWithData> removed = references.remove(entry.getIndex());
96+
Objects.requireNonNull(removed, "removed == null");
97+
removed.release();
98+
}
99+
queue.clear();
100+
}
101+
return new EntryList(protos, references);
102+
}
103+
}
104+
105+
/** Storing log entries and their references. */
106+
private static class EntryList {
107+
private final List<LogEntryProto> protos;
108+
private final Collection<ReferenceCountedObject<EntryWithData>> references;
109+
110+
EntryList(List<LogEntryProto> protos, Map<Long, ReferenceCountedObject<EntryWithData>> references) {
111+
Preconditions.assertSame(references.size(), protos.size(), "#entries");
112+
this.protos = Collections.unmodifiableList(protos);
113+
this.references = Collections.unmodifiableCollection(references.values());
114+
}
115+
116+
List<LogEntryProto> getProtos() {
117+
return protos;
118+
}
119+
120+
void retain() {
121+
for (ReferenceCountedObject<EntryWithData> ref : references) {
122+
ref.retain();
123+
}
124+
}
125+
126+
void release() {
127+
for (ReferenceCountedObject<EntryWithData> ref : references) {
128+
ref.release();
129+
}
130+
}
131+
}
132+
55133
private final String name;
56134
private final RaftServer.Division server;
57135
private final LeaderState leaderState;
58136
private final FollowerInfo follower;
59137

60-
private final DataQueue<EntryWithData> buffer;
61138
private final int snapshotChunkMaxSize;
62139

63140
private final LogAppenderDaemon daemon;
@@ -75,9 +152,6 @@ protected LogAppenderBase(RaftServer.Division server, LeaderState leaderState, F
75152
final RaftProperties properties = server.getRaftServer().getProperties();
76153
this.snapshotChunkMaxSize = RaftServerConfigKeys.Log.Appender.snapshotChunkSizeMax(properties).getSizeInt();
77154

78-
final SizeInBytes bufferByteLimit = RaftServerConfigKeys.Log.Appender.bufferByteLimit(properties);
79-
final int bufferElementLimit = RaftServerConfigKeys.Log.Appender.bufferElementLimit(properties);
80-
this.buffer = new DataQueue<>(this, bufferByteLimit, bufferElementLimit, EntryWithData::getSerializedSize);
81155
this.daemon = new LogAppenderDaemon(this);
82156
this.eventAwaitForSignal = new AwaitForSignal(name);
83157

@@ -210,13 +284,13 @@ protected LongUnaryOperator getNextIndexForError(long newNextIndex) {
210284
final long n = oldNextIndex <= 0L ? oldNextIndex : Math.min(oldNextIndex - 1, newNextIndex);
211285
if (m > n) {
212286
if (m > newNextIndex) {
213-
LOG.info("Set nextIndex to matchIndex + 1 (= " + m + ")");
287+
LOG.info("{}: Set nextIndex to matchIndex + 1 (= {})", name, m);
214288
}
215289
return m;
216290
} else if (oldNextIndex <= 0L) {
217291
return oldNextIndex; // no change.
218292
} else {
219-
LOG.info("Decrease nextIndex to " + n);
293+
LOG.info("{}: Decrease nextIndex to {}", name, n);
220294
return n;
221295
}
222296
};
@@ -227,18 +301,18 @@ public AppendEntriesRequestProto newAppendEntriesRequest(long callId, boolean he
227301
throw new UnsupportedOperationException("Use nextAppendEntriesRequest(" + callId + ", " + heartbeat +") instead.");
228302
}
229303

230-
/**
231-
* Create a {@link AppendEntriesRequestProto} object using the {@link FollowerInfo} of this {@link LogAppender}.
232-
* The {@link AppendEntriesRequestProto} object may contain zero or more log entries.
233-
* When there is zero log entries, the {@link AppendEntriesRequestProto} object is a heartbeat.
234-
*
235-
* @param callId The call id of the returned request.
236-
* @param heartbeat the returned request must be a heartbeat.
237-
*
238-
* @return a retained reference of {@link AppendEntriesRequestProto} object.
239-
* Since the returned reference is retained, the caller must call {@link ReferenceCountedObject#release()}}
240-
* after use.
241-
*/
304+
/**
305+
* Create a {@link AppendEntriesRequestProto} object using the {@link FollowerInfo} of this {@link LogAppender}.
306+
* The {@link AppendEntriesRequestProto} object may contain zero or more log entries.
307+
* When there is zero log entries, the {@link AppendEntriesRequestProto} object is a heartbeat.
308+
*
309+
* @param callId The call id of the returned request.
310+
* @param heartbeat the returned request must be a heartbeat.
311+
*
312+
* @return a retained reference of {@link AppendEntriesRequestProto} object.
313+
* Since the returned reference is retained,
314+
* the caller must call {@link ReferenceCountedObject#release()}} after use.
315+
*/
242316
protected ReferenceCountedObject<AppendEntriesRequestProto> nextAppendEntriesRequest(long callId, boolean heartbeat)
243317
throws RaftLogIOException {
244318
final long heartbeatWaitTimeMs = getHeartbeatWaitTimeMs();
@@ -253,56 +327,23 @@ protected ReferenceCountedObject<AppendEntriesRequestProto> nextAppendEntriesReq
253327
return ref;
254328
}
255329

256-
Preconditions.assertTrue(buffer.isEmpty(), () -> "buffer has " + buffer.getNumElements() + " elements.");
257-
258330
final long snapshotIndex = follower.getSnapshotIndex();
259-
final long leaderNext = getRaftLog().getNextIndex();
260331
final long followerNext = follower.getNextIndex();
261-
final long halfMs = heartbeatWaitTimeMs/2;
262-
final Map<Long, ReferenceCountedObject<EntryWithData>> offered = new HashMap<>();
263-
for (long next = followerNext; leaderNext > next && getHeartbeatWaitTimeMs() - halfMs > 0; next++) {
264-
final ReferenceCountedObject<EntryWithData> entryWithData;
265-
try {
266-
entryWithData = getRaftLog().retainEntryWithData(next);
267-
if (!buffer.offer(entryWithData.get())) {
268-
entryWithData.release();
269-
break;
270-
}
271-
offered.put(next, entryWithData);
272-
} catch (Exception e){
273-
for (ReferenceCountedObject<EntryWithData> ref : offered.values()) {
274-
ref.release();
275-
}
276-
offered.clear();
277-
throw e;
278-
}
279-
}
280-
if (buffer.isEmpty()) {
332+
final EntryBuffer entryBuffer = readLogEntries(followerNext, heartbeatWaitTimeMs);
333+
if (entryBuffer == null) {
281334
return null;
282335
}
283336

284-
final List<LogEntryProto> protos;
285-
try {
286-
protos = buffer.pollList(getHeartbeatWaitTimeMs(), EntryWithData::getEntry,
287-
(entry, time, exception) -> LOG.warn("Failed to get {} in {}",
288-
entry, time.toString(TimeUnit.MILLISECONDS, 3), exception));
289-
} catch (RaftLogIOException e) {
290-
for (ReferenceCountedObject<EntryWithData> ref : offered.values()) {
291-
ref.release();
292-
}
293-
offered.clear();
294-
throw e;
295-
} finally {
296-
for (EntryWithData entry : buffer) {
297-
// Release remaining entries.
298-
Optional.ofNullable(offered.remove(entry.getIndex())).ifPresent(ReferenceCountedObject::release);
299-
}
300-
buffer.clear();
301-
}
337+
final EntryList entryList = entryBuffer.pollList(heartbeatWaitTimeMs);
338+
final List<LogEntryProto> protos = entryList.getProtos();
302339
assertProtos(protos, followerNext, previous, snapshotIndex);
303340
AppendEntriesRequestProto appendEntriesProto =
304341
leaderState.newAppendEntriesRequestProto(follower, protos, previous, callId);
305-
return ReferenceCountedObject.delegateFrom(offered.values(), appendEntriesProto);
342+
final ReferenceCountedObject<AppendEntriesRequestProto> ref = ReferenceCountedObject.wrap(
343+
appendEntriesProto, entryList::retain, entryList::release);
344+
ref.retain();
345+
entryList.release();
346+
return ref;
306347
}
307348

308349
private void assertProtos(List<LogEntryProto> protos, long nextIndex, TermIndex previous, long snapshotIndex) {
@@ -324,6 +365,31 @@ private void assertProtos(List<LogEntryProto> protos, long nextIndex, TermIndex
324365
}
325366
}
326367

368+
private EntryBuffer readLogEntries(long followerNext, long heartbeatWaitTimeMs) throws RaftLogIOException {
369+
final RaftLog raftLog = getRaftLog();
370+
final long leaderNext = raftLog.getNextIndex();
371+
final long halfMs = heartbeatWaitTimeMs/2;
372+
EntryBuffer entryBuffer = null;
373+
for (long next = followerNext; leaderNext > next && getHeartbeatWaitTimeMs() - halfMs > 0; next++) {
374+
final ReferenceCountedObject<EntryWithData> retained;
375+
try {
376+
retained = raftLog.retainEntryWithData(next);
377+
if (entryBuffer == null) {
378+
entryBuffer = new EntryBuffer(name, server.getRaftServer().getProperties());
379+
}
380+
if (!entryBuffer.putNew(next, retained)) {
381+
break;
382+
}
383+
} catch (Exception e) {
384+
if (entryBuffer != null) {
385+
entryBuffer.releaseAllAndClear();
386+
}
387+
throw e;
388+
}
389+
}
390+
return entryBuffer;
391+
}
392+
327393
@Override
328394
public InstallSnapshotRequestProto newInstallSnapshotNotificationRequest(TermIndex firstAvailableLogTermIndex) {
329395
Preconditions.assertTrue(firstAvailableLogTermIndex.getIndex() >= 0);

ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/LogSegment.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.apache.ratis.server.raftlog.RaftLogIOException;
2727
import org.apache.ratis.server.storage.RaftStorage;
2828
import org.apache.ratis.thirdparty.com.google.common.annotations.VisibleForTesting;
29-
import org.apache.ratis.thirdparty.com.google.common.cache.CacheLoader;
3029
import org.apache.ratis.thirdparty.com.google.protobuf.CodedOutputStream;
3130
import org.apache.ratis.util.FileUtils;
3231
import org.apache.ratis.util.JavaUtils;
@@ -266,15 +265,14 @@ private void assertSegment(long expectedStart, int expectedEntryCount, boolean c
266265
*
267266
* In the future we can make the cache loader configurable if necessary.
268267
*/
269-
class LogEntryLoader extends CacheLoader<LogRecord, ReferenceCountedObject<LogEntryProto>> {
268+
class LogEntryLoader {
270269
private final SegmentedRaftLogMetrics raftLogMetrics;
271270

272271
LogEntryLoader(SegmentedRaftLogMetrics raftLogMetrics) {
273272
this.raftLogMetrics = raftLogMetrics;
274273
}
275274

276-
@Override
277-
public ReferenceCountedObject<LogEntryProto> load(LogRecord key) throws IOException {
275+
ReferenceCountedObject<LogEntryProto> load(TermIndex key) throws IOException {
278276
final File file = getFile();
279277
// note the loading should not exceed the endIndex: it is possible that
280278
// the on-disk log file should be truncated but has not been done yet.
@@ -285,17 +283,16 @@ public ReferenceCountedObject<LogEntryProto> load(LogRecord key) throws IOExcept
285283
try {
286284
final TermIndex ti = TermIndex.valueOf(entry);
287285
putEntryCache(ti, entryRef, Op.LOAD_SEGMENT_FILE);
288-
if (ti.equals(key.getTermIndex())) {
286+
if (ti.equals(key)) {
287+
entryRef.retain();
289288
toReturn.set(entryRef);
290-
} else {
291-
entryRef.release();
292289
}
293-
} catch (Exception e) {
290+
} finally {
294291
entryRef.release();
295292
}
296293
});
297294
loadingTimes.incrementAndGet();
298-
return Objects.requireNonNull(toReturn.get());
295+
return Objects.requireNonNull(toReturn.get(), () -> "toReturn == null for " + key);
299296
}
300297
}
301298

@@ -492,8 +489,8 @@ ReferenceCountedObject<LogEntryProto> getEntryFromCache(TermIndex ti) {
492489
/**
493490
* Acquire LogSegment's monitor so that there is no concurrent loading.
494491
*/
495-
synchronized ReferenceCountedObject<LogEntryProto> loadCache(LogRecord record) throws RaftLogIOException {
496-
ReferenceCountedObject<LogEntryProto> entry = entryCache.get(record.getTermIndex());
492+
synchronized ReferenceCountedObject<LogEntryProto> loadCache(TermIndex ti) throws RaftLogIOException {
493+
final ReferenceCountedObject<LogEntryProto> entry = entryCache.get(ti);
497494
if (entry != null) {
498495
try {
499496
entry.retain();
@@ -504,7 +501,7 @@ synchronized ReferenceCountedObject<LogEntryProto> loadCache(LogRecord record) t
504501
}
505502
}
506503
try {
507-
return cacheLoader.load(record);
504+
return cacheLoader.load(ti);
508505
} catch (Exception e) {
509506
throw new RaftLogIOException(e);
510507
}

ratis-server/src/main/java/org/apache/ratis/server/raftlog/segmented/SegmentedRaftLog.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ public ReferenceCountedObject<LogEntryProto> retainLog(long index) throws RaftLo
304304
if (record == null) {
305305
return null;
306306
}
307-
final ReferenceCountedObject<LogEntryProto> entry = segment.getEntryFromCache(record.getTermIndex());
307+
final TermIndex ti = record.getTermIndex();
308+
final ReferenceCountedObject<LogEntryProto> entry = segment.getEntryFromCache(ti);
308309
if (entry != null) {
309310
try {
310311
entry.retain();
@@ -319,7 +320,7 @@ public ReferenceCountedObject<LogEntryProto> retainLog(long index) throws RaftLo
319320
// the entry is not in the segment's cache. Load the cache without holding the lock.
320321
getRaftLogMetrics().onRaftLogCacheMiss();
321322
cacheEviction.signal();
322-
return segment.loadCache(record);
323+
return segment.loadCache(ti);
323324
}
324325

325326
@Override

ratis-test/src/test/java/org/apache/ratis/server/raftlog/segmented/TestLogSegment.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,15 @@ static void checkLogSegment(LogSegment segment, long start, long end,
139139
long offset = SegmentedRaftLogFormat.getHeaderLength();
140140
for (long i = start; i <= end; i++) {
141141
LogSegment.LogRecord record = segment.getLogRecord(i);
142+
Assertions.assertNotNull(record);
142143
final TermIndex ti = record.getTermIndex();
143144
Assertions.assertEquals(i, ti.getIndex());
144145
Assertions.assertEquals(term, ti.getTerm());
145146
Assertions.assertEquals(offset, record.getOffset());
146147

147148
ReferenceCountedObject<LogEntryProto> entry = segment.getEntryFromCache(ti);
148149
if (entry == null) {
149-
entry = segment.loadCache(record);
150+
entry = segment.loadCache(ti);
150151
}
151152
offset += getEntrySize(entry.get(), Op.WRITE_CACHE_WITHOUT_STATE_MACHINE_CACHE);
152153
}

0 commit comments

Comments
 (0)