Skip to content

Commit ba770b5

Browse files
committed
Keep one commit whose max_seqno is no_ops_performed
If a 6.x node with a 5.x index is promoted to be a primary, it will flush a new index commit to make sure translog operations without seqno will never be replayed (see IndexShard#updateShardState). However the global checkpoint is still UNASSIGNED and the max_seqno of both commits are NO_OPS_PERFORMED. If the combined deletion policy considers the first commit as a safe commit, we will send the first commit without replaying translog between these commits to the replica in a peer-recovery. This causes the replica missing those operations. To prevent this, we should not keep more than one commit whose max_seqno is NO_OPS_PERFORMED. Once we can retain a safe commit, a NO_OPS_PERFORMED commit will be deleted just as other commits. Relates #28038
1 parent 0c0dc3c commit ba770b5

File tree

2 files changed

+59
-0
lines changed

2 files changed

+59
-0
lines changed

server/src/main/java/org/elasticsearch/index/engine/CombinedDeletionPolicy.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ private static int indexOfKeptCommits(List<? extends IndexCommit> commits, long
126126
return Math.min(commits.size() - 1, i + 1);
127127
}
128128
final long maxSeqNoFromCommit = Long.parseLong(commitUserData.get(SequenceNumbers.MAX_SEQ_NO));
129+
// If a 6.x node with a 5.x index is promoted to be a primary, it will flush a new index commit to
130+
// make sure translog operations without seqno will never be replayed (see IndexShard#updateShardState).
131+
// However the global checkpoint is still UNASSIGNED and the max_seqno of both commits are NO_OPS_PERFORMED.
132+
// If this policy considers the first commit as a safe commit, we will send the first commit without replaying
133+
// translog between these commits to the replica in a peer-recovery. This causes the replica missing those operations.
134+
// To prevent this, we should not keep more than one commit whose max_seqno is NO_OPS_PERFORMED.
135+
// Once we can retain a safe commit, a NO_OPS_PERFORMED commit will be deleted just as other commits.
136+
if (maxSeqNoFromCommit == SequenceNumbers.NO_OPS_PERFORMED) {
137+
return i;
138+
}
129139
if (maxSeqNoFromCommit <= globalCheckpoint) {
130140
return i;
131141
}

server/src/test/java/org/elasticsearch/index/engine/CombinedDeletionPolicyTests.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import static org.elasticsearch.index.translog.TranslogDeletionPolicies.createTranslogDeletionPolicy;
4343
import static org.hamcrest.Matchers.contains;
4444
import static org.hamcrest.Matchers.equalTo;
45+
import static org.hamcrest.Matchers.greaterThan;
4546
import static org.mockito.Mockito.mock;
4647
import static org.mockito.Mockito.never;
4748
import static org.mockito.Mockito.times;
@@ -149,6 +150,54 @@ public void testLegacyIndex() throws Exception {
149150
assertThat(translogPolicy.getTranslogGenerationOfLastCommit(), equalTo(safeTranslogGen));
150151
}
151152

153+
public void testKeepSingleNoOpsCommits() throws Exception {
154+
final AtomicLong globalCheckpoint = new AtomicLong(randomLong());
155+
final UUID translogUUID = UUID.randomUUID();
156+
TranslogDeletionPolicy translogPolicy = createTranslogDeletionPolicy();
157+
CombinedDeletionPolicy indexPolicy = new CombinedDeletionPolicy(OPEN_INDEX_AND_TRANSLOG, translogPolicy, globalCheckpoint::get);
158+
159+
final List<IndexCommit> commitList = new ArrayList<>();
160+
final int numOfNoOpsCommits = between(1, 10);
161+
long lastNoopTranslogGen = 0;
162+
for (int i = 0; i < numOfNoOpsCommits; i++) {
163+
lastNoopTranslogGen += between(1, 20);
164+
commitList.add(mockIndexCommit(SequenceNumbers.NO_OPS_PERFORMED, translogUUID, lastNoopTranslogGen));
165+
}
166+
// Keep only one no_ops commit.
167+
indexPolicy.onCommit(commitList);
168+
assertThat(translogPolicy.getMinTranslogGenerationForRecovery(), equalTo(lastNoopTranslogGen));
169+
assertThat(translogPolicy.getTranslogGenerationOfLastCommit(), equalTo(lastNoopTranslogGen));
170+
for (int i = 0; i < numOfNoOpsCommits - 1; i++) {
171+
verify(commitList.get(i), times(1)).delete();
172+
}
173+
verify(commitList.get(commitList.size() - 1), never()).delete();
174+
// Add a some good commits.
175+
final int numOfGoodCommits = between(1, 5);
176+
long maxSeqNo = 0;
177+
long lastTranslogGen = lastNoopTranslogGen;
178+
for (int i = 0; i < numOfGoodCommits; i++) {
179+
maxSeqNo += between(1, 1000);
180+
lastTranslogGen += between(1, 20);
181+
commitList.add(mockIndexCommit(maxSeqNo, translogUUID, lastTranslogGen));
182+
}
183+
// If the global checkpoint is still unassigned, we should still keep one NO_OPS_PERFORMED commit.
184+
globalCheckpoint.set(SequenceNumbers.UNASSIGNED_SEQ_NO);
185+
indexPolicy.onCommit(commitList);
186+
assertThat(translogPolicy.getMinTranslogGenerationForRecovery(), equalTo(lastNoopTranslogGen));
187+
assertThat(translogPolicy.getTranslogGenerationOfLastCommit(), equalTo(lastTranslogGen));
188+
for (int i = 0; i < numOfNoOpsCommits - 1; i++) {
189+
verify(commitList.get(i), times(2)).delete();
190+
}
191+
verify(commitList.get(numOfNoOpsCommits - 1), never()).delete();
192+
// Delete no-ops commit if global checkpoint advanced enough.
193+
final long lower = Long.parseLong(commitList.get(numOfNoOpsCommits).getUserData().get(SequenceNumbers.MAX_SEQ_NO));
194+
globalCheckpoint.set(randomLongBetween(lower, Long.MAX_VALUE));
195+
indexPolicy.onCommit(commitList);
196+
assertThat(translogPolicy.getMinTranslogGenerationForRecovery(), greaterThan(lastNoopTranslogGen));
197+
assertThat(translogPolicy.getTranslogGenerationOfLastCommit(), equalTo(lastTranslogGen));
198+
verify(commitList.get(numOfNoOpsCommits - 1), times(1)).delete();
199+
}
200+
152201
public void testDeleteInvalidCommits() throws Exception {
153202
final AtomicLong globalCheckpoint = new AtomicLong(randomNonNegativeLong());
154203
TranslogDeletionPolicy translogPolicy = createTranslogDeletionPolicy();

0 commit comments

Comments
 (0)