Skip to content

Commit 50240da

Browse files
committed
Adjust translog operation assertions for synthetic source
1 parent d008ada commit 50240da

File tree

12 files changed

+242
-74
lines changed

12 files changed

+242
-74
lines changed

muted-tests.yml

-3
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,6 @@ tests:
236236
- class: org.elasticsearch.xpack.esql.action.EsqlNodeFailureIT
237237
method: testFailureLoadingFields
238238
issue: https://github.com/elastic/elasticsearch/issues/118000
239-
- class: org.elasticsearch.smoketest.SmokeTestMultiNodeClientYamlTestSuiteIT
240-
method: test {yaml=indices.create/20_synthetic_source/create index with use_synthetic_source}
241-
issue: https://github.com/elastic/elasticsearch/issues/119191
242239
- class: org.elasticsearch.index.mapper.AbstractShapeGeometryFieldMapperTests
243240
method: testCartesianBoundsBlockLoader
244241
issue: https://github.com/elastic/elasticsearch/issues/119201

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,8 @@ private Translog openTranslog(
661661
translogDeletionPolicy,
662662
globalCheckpointSupplier,
663663
engineConfig.getPrimaryTermSupplier(),
664-
persistedSequenceNumberConsumer
664+
persistedSequenceNumberConsumer,
665+
TranslogOperationAsserter.withEngineConfig(engineConfig)
665666
);
666667
}
667668

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ public void trimUnreferencedTranslogFiles() {
166166
translogDeletionPolicy,
167167
engineConfig.getGlobalCheckpointSupplier(),
168168
engineConfig.getPrimaryTermSupplier(),
169-
seqNo -> {}
169+
seqNo -> {},
170+
TranslogOperationAsserter.DEFAULT
170171
)
171172
) {
172173
translog.trimUnreferencedReaders();

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ private static TranslogStats translogStats(final EngineConfig config, final Segm
267267
translogDeletionPolicy,
268268
config.getGlobalCheckpointSupplier(),
269269
config.getPrimaryTermSupplier(),
270-
seqNo -> {}
270+
seqNo -> {},
271+
TranslogOperationAsserter.DEFAULT
271272
)
272273
) {
273274
return translog.stats();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.index.engine;
11+
12+
import org.elasticsearch.index.mapper.DocumentParser;
13+
import org.elasticsearch.index.mapper.MappingLookup;
14+
import org.elasticsearch.index.shard.ShardId;
15+
import org.elasticsearch.index.translog.Translog;
16+
17+
import java.io.IOException;
18+
import java.util.Objects;
19+
20+
/**
21+
*
22+
* A utility class to assert that translog operations with the same sequence number
23+
* in the same generation are either identical or equivalent when synthetic sources are used.
24+
*/
25+
public abstract class TranslogOperationAsserter {
26+
public static final TranslogOperationAsserter DEFAULT = new TranslogOperationAsserter() {
27+
};
28+
29+
private TranslogOperationAsserter() {
30+
31+
}
32+
33+
public static TranslogOperationAsserter withEngineConfig(EngineConfig engineConfig) {
34+
return new TranslogOperationAsserter() {
35+
Translog.Index synthesizeSource(Translog.Index op) throws IOException {
36+
final ShardId shardId = engineConfig.getShardId();
37+
MappingLookup mappingLookup = engineConfig.getMapperService().mappingLookup();
38+
DocumentParser documentParser = engineConfig.getMapperService().documentParser();
39+
try (var reader = new TranslogDirectoryReader(shardId, op, mappingLookup, documentParser, engineConfig, () -> {})) {
40+
final Engine.Searcher searcher = new Engine.Searcher(
41+
"assert_translog",
42+
reader,
43+
engineConfig.getSimilarity(),
44+
engineConfig.getQueryCache(),
45+
engineConfig.getQueryCachingPolicy(),
46+
() -> {}
47+
);
48+
try (
49+
LuceneSyntheticSourceChangesSnapshot snapshot = new LuceneSyntheticSourceChangesSnapshot(
50+
mappingLookup,
51+
searcher,
52+
LuceneSyntheticSourceChangesSnapshot.DEFAULT_BATCH_SIZE,
53+
Integer.MAX_VALUE,
54+
op.seqNo(),
55+
op.seqNo(),
56+
true,
57+
false,
58+
engineConfig.getIndexSettings().getIndexVersionCreated()
59+
)
60+
) {
61+
final Translog.Operation normalized = snapshot.next();
62+
assert normalized != null : "expected one operation; got zero";
63+
return (Translog.Index) normalized;
64+
}
65+
}
66+
}
67+
68+
@Override
69+
boolean sameIndexOperation(Translog.Index o1, Translog.Index o2) throws IOException {
70+
return super.sameIndexOperation(o1, o2)
71+
|| super.sameIndexOperation(synthesizeSource(o1), o2)
72+
|| super.sameIndexOperation(o1, synthesizeSource(o2));
73+
}
74+
};
75+
}
76+
77+
boolean sameIndexOperation(Translog.Index o1, Translog.Index o2) throws IOException {
78+
// TODO: We haven't had timestamp for Index operations in Lucene yet, we need to loosen this check without timestamp.
79+
return Objects.equals(o1.id(), o2.id())
80+
&& Objects.equals(o1.source(), o2.source())
81+
&& Objects.equals(o1.routing(), o2.routing())
82+
&& o1.primaryTerm() == o2.primaryTerm()
83+
&& o1.seqNo() == o2.seqNo()
84+
&& o1.version() == o2.version();
85+
}
86+
87+
public boolean assertSameOperations(
88+
long seqNo,
89+
long generation,
90+
Translog.Operation newOp,
91+
Translog.Operation prvOp,
92+
Exception prevFailure
93+
) throws IOException {
94+
final boolean sameOp;
95+
if (newOp instanceof final Translog.Index o2 && prvOp instanceof final Translog.Index o1) {
96+
sameOp = sameIndexOperation(o1, o2);
97+
} else if (newOp instanceof final Translog.Delete o1 && prvOp instanceof final Translog.Delete o2) {
98+
sameOp = Objects.equals(o1.id(), o2.id())
99+
&& o1.primaryTerm() == o2.primaryTerm()
100+
&& o1.seqNo() == o2.seqNo()
101+
&& o1.version() == o2.version();
102+
} else {
103+
sameOp = false;
104+
}
105+
if (sameOp == false) {
106+
throw new AssertionError(
107+
"seqNo ["
108+
+ seqNo
109+
+ "] was processed twice in generation ["
110+
+ generation
111+
+ "], with different data. "
112+
+ "prvOp ["
113+
+ prvOp
114+
+ "], newOp ["
115+
+ newOp
116+
+ "]",
117+
prevFailure
118+
);
119+
}
120+
return true;
121+
}
122+
}

server/src/main/java/org/elasticsearch/index/translog/Translog.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.core.Releasable;
2828
import org.elasticsearch.index.IndexSettings;
2929
import org.elasticsearch.index.engine.Engine;
30+
import org.elasticsearch.index.engine.TranslogOperationAsserter;
3031
import org.elasticsearch.index.mapper.IdFieldMapper;
3132
import org.elasticsearch.index.mapper.MapperService;
3233
import org.elasticsearch.index.mapper.Uid;
@@ -123,6 +124,7 @@ public class Translog extends AbstractIndexShardComponent implements IndexShardC
123124
private final TranslogDeletionPolicy deletionPolicy;
124125
private final LongConsumer persistedSequenceNumberConsumer;
125126
private final OperationListener operationListener;
127+
private final TranslogOperationAsserter operationAsserter;
126128

127129
/**
128130
* Creates a new Translog instance. This method will create a new transaction log unless the given {@link TranslogGeneration} is
@@ -150,14 +152,16 @@ public Translog(
150152
TranslogDeletionPolicy deletionPolicy,
151153
final LongSupplier globalCheckpointSupplier,
152154
final LongSupplier primaryTermSupplier,
153-
final LongConsumer persistedSequenceNumberConsumer
155+
final LongConsumer persistedSequenceNumberConsumer,
156+
final TranslogOperationAsserter operationAsserter
154157
) throws IOException {
155158
super(config.getShardId(), config.getIndexSettings());
156159
this.config = config;
157160
this.globalCheckpointSupplier = globalCheckpointSupplier;
158161
this.primaryTermSupplier = primaryTermSupplier;
159162
this.persistedSequenceNumberConsumer = persistedSequenceNumberConsumer;
160163
this.operationListener = config.getOperationListener();
164+
this.operationAsserter = operationAsserter;
161165
this.deletionPolicy = deletionPolicy;
162166
this.translogUUID = translogUUID;
163167
this.bigArrays = config.getBigArrays();
@@ -586,6 +590,7 @@ TranslogWriter createWriter(
586590
bigArrays,
587591
diskIoBufferPool,
588592
operationListener,
593+
operationAsserter,
589594
config.fsync()
590595
);
591596
} catch (final IOException e) {
@@ -1962,6 +1967,7 @@ public static String createEmptyTranslog(
19621967
BigArrays.NON_RECYCLING_INSTANCE,
19631968
DiskIoBufferPool.INSTANCE,
19641969
TranslogConfig.NOOP_OPERATION_LISTENER,
1970+
TranslogOperationAsserter.DEFAULT,
19651971
true
19661972
);
19671973
writer.close();

server/src/main/java/org/elasticsearch/index/translog/TranslogWriter.java

+7-33
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.core.Releasables;
2727
import org.elasticsearch.core.SuppressForbidden;
2828
import org.elasticsearch.core.Tuple;
29+
import org.elasticsearch.index.engine.TranslogOperationAsserter;
2930
import org.elasticsearch.index.seqno.SequenceNumbers;
3031
import org.elasticsearch.index.shard.ShardId;
3132

@@ -39,7 +40,6 @@
3940
import java.util.HashMap;
4041
import java.util.List;
4142
import java.util.Map;
42-
import java.util.Objects;
4343
import java.util.concurrent.atomic.AtomicBoolean;
4444
import java.util.concurrent.locks.ReentrantLock;
4545
import java.util.function.LongConsumer;
@@ -69,6 +69,7 @@ public class TranslogWriter extends BaseTranslogReader implements Closeable {
6969
// callback that's called whenever an operation with a given sequence number is successfully persisted.
7070
private final LongConsumer persistedSequenceNumberConsumer;
7171
private final OperationListener operationListener;
72+
private final TranslogOperationAsserter operationAsserter;
7273
private final boolean fsync;
7374

7475
protected final AtomicBoolean closed = new AtomicBoolean(false);
@@ -108,6 +109,7 @@ private TranslogWriter(
108109
BigArrays bigArrays,
109110
DiskIoBufferPool diskIoBufferPool,
110111
OperationListener operationListener,
112+
TranslogOperationAsserter operationAsserter,
111113
boolean fsync
112114
) throws IOException {
113115
super(initialCheckpoint.generation, channel, path, header);
@@ -136,6 +138,7 @@ private TranslogWriter(
136138
this.seenSequenceNumbers = Assertions.ENABLED ? new HashMap<>() : null;
137139
this.tragedy = tragedy;
138140
this.operationListener = operationListener;
141+
this.operationAsserter = operationAsserter;
139142
this.fsync = fsync;
140143
this.lastModifiedTimeCache = new LastModifiedTimeCache(-1, -1, -1);
141144
}
@@ -157,6 +160,7 @@ public static TranslogWriter create(
157160
BigArrays bigArrays,
158161
DiskIoBufferPool diskIoBufferPool,
159162
OperationListener operationListener,
163+
TranslogOperationAsserter operationAsserter,
160164
boolean fsync
161165
) throws IOException {
162166
final Path checkpointFile = file.getParent().resolve(Translog.CHECKPOINT_FILE_NAME);
@@ -201,6 +205,7 @@ public static TranslogWriter create(
201205
bigArrays,
202206
diskIoBufferPool,
203207
operationListener,
208+
operationAsserter,
204209
fsync
205210
);
206211
} catch (Exception exception) {
@@ -276,38 +281,7 @@ private synchronized boolean assertNoSeqNumberConflict(long seqNo, BytesReferenc
276281
Translog.Operation prvOp = Translog.readOperation(
277282
new BufferedChecksumStreamInput(previous.v1().streamInput(), "assertion")
278283
);
279-
// TODO: We haven't had timestamp for Index operations in Lucene yet, we need to loosen this check without timestamp.
280-
final boolean sameOp;
281-
if (newOp instanceof final Translog.Index o2 && prvOp instanceof final Translog.Index o1) {
282-
sameOp = Objects.equals(o1.id(), o2.id())
283-
&& Objects.equals(o1.source(), o2.source())
284-
&& Objects.equals(o1.routing(), o2.routing())
285-
&& o1.primaryTerm() == o2.primaryTerm()
286-
&& o1.seqNo() == o2.seqNo()
287-
&& o1.version() == o2.version();
288-
} else if (newOp instanceof final Translog.Delete o1 && prvOp instanceof final Translog.Delete o2) {
289-
sameOp = Objects.equals(o1.id(), o2.id())
290-
&& o1.primaryTerm() == o2.primaryTerm()
291-
&& o1.seqNo() == o2.seqNo()
292-
&& o1.version() == o2.version();
293-
} else {
294-
sameOp = false;
295-
}
296-
if (sameOp == false) {
297-
throw new AssertionError(
298-
"seqNo ["
299-
+ seqNo
300-
+ "] was processed twice in generation ["
301-
+ generation
302-
+ "], with different data. "
303-
+ "prvOp ["
304-
+ prvOp
305-
+ "], newOp ["
306-
+ newOp
307-
+ "]",
308-
previous.v2()
309-
);
310-
}
284+
assert operationAsserter.assertSameOperations(seqNo, generation, newOp, prvOp, previous.v2());
311285
}
312286
} else {
313287
seenSequenceNumbers.put(

server/src/main/java/org/elasticsearch/index/translog/TruncateTranslogAction.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.core.Tuple;
2626
import org.elasticsearch.index.IndexNotFoundException;
2727
import org.elasticsearch.index.IndexSettings;
28+
import org.elasticsearch.index.engine.TranslogOperationAsserter;
2829
import org.elasticsearch.index.seqno.SequenceNumbers;
2930
import org.elasticsearch.index.shard.RemoveCorruptedShardDataCommand;
3031
import org.elasticsearch.index.shard.ShardPath;
@@ -171,7 +172,8 @@ private static boolean isTranslogClean(ShardPath shardPath, ClusterState cluster
171172
translogDeletionPolicy,
172173
() -> translogGlobalCheckpoint,
173174
() -> primaryTerm,
174-
seqNo -> {}
175+
seqNo -> {},
176+
TranslogOperationAsserter.DEFAULT
175177
);
176178
Translog.Snapshot snapshot = translog.newSnapshot(0, Long.MAX_VALUE)
177179
) {

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -3568,7 +3568,8 @@ public void testRecoverFromForeignTranslog() throws IOException {
35683568
new TranslogDeletionPolicy(),
35693569
() -> SequenceNumbers.NO_OPS_PERFORMED,
35703570
primaryTerm::get,
3571-
seqNo -> {}
3571+
seqNo -> {},
3572+
TranslogOperationAsserter.DEFAULT
35723573
);
35733574
translog.add(TranslogOperationsUtils.indexOp("SomeBogusId", 0, primaryTerm.get()));
35743575
assertEquals(generation.translogFileGeneration(), translog.currentFileGeneration());

server/src/test/java/org/elasticsearch/index/translog/TranslogDeletionPolicyTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.elasticsearch.core.IOUtils;
1818
import org.elasticsearch.core.Releasable;
1919
import org.elasticsearch.core.Tuple;
20+
import org.elasticsearch.index.engine.TranslogOperationAsserter;
2021
import org.elasticsearch.index.shard.ShardId;
2122
import org.elasticsearch.test.ESTestCase;
2223
import org.mockito.Mockito;
@@ -95,6 +96,7 @@ private Tuple<List<TranslogReader>, TranslogWriter> createReadersAndWriter() thr
9596
BigArrays.NON_RECYCLING_INSTANCE,
9697
TranslogTests.RANDOMIZING_IO_BUFFERS,
9798
TranslogConfig.NOOP_OPERATION_LISTENER,
99+
TranslogOperationAsserter.DEFAULT,
98100
true
99101
);
100102
writer = Mockito.spy(writer);

0 commit comments

Comments
 (0)