Skip to content

Commit 7a6a79d

Browse files
committed
Add seq no powered optimistic locking support to the index and delete transport actions (#36619)
This commit add support for using sequence numbers to power [optimistic concurrency control](http://en.wikipedia.org/wiki/Optimistic_concurrency_control) in the delete and index transport actions and requests. A follow up will come with adding sequence numbers to the update and get results. Relates #36148 Relates #10708
1 parent eaec994 commit 7a6a79d

File tree

20 files changed

+298
-66
lines changed

20 files changed

+298
-66
lines changed

plugins/mapper-annotated-text/src/test/java/org/elasticsearch/index/mapper/annotatedtext/AnnotatedTextFieldMapperTests.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.elasticsearch.index.mapper.ParsedDocument;
5252
import org.elasticsearch.index.mapper.SourceToParse;
5353
import org.elasticsearch.index.mapper.TextFieldMapper;
54+
import org.elasticsearch.index.seqno.SequenceNumbers;
5455
import org.elasticsearch.index.shard.IndexShard;
5556
import org.elasticsearch.index.termvectors.TermVectorsService;
5657
import org.elasticsearch.indices.IndicesService;
@@ -135,7 +136,7 @@ public void testAnnotationInjection() throws IOException {
135136

136137
IndexShard shard = indexService.getShard(0);
137138
shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL,
138-
sourceToParse, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
139+
sourceToParse, SequenceNumbers.UNASSIGNED_SEQ_NO, 0, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
139140
shard.refresh("test");
140141
try (Engine.Searcher searcher = shard.acquireSearcher("test")) {
141142
LeafReader leaf = searcher.getDirectoryReader().leaves().get(0).reader();
@@ -190,7 +191,7 @@ public void testToleranceForBadAnnotationMarkup() throws IOException {
190191

191192
IndexShard shard = indexService.getShard(0);
192193
shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL,
193-
sourceToParse, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
194+
sourceToParse, SequenceNumbers.UNASSIGNED_SEQ_NO, 0, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
194195
shard.refresh("test");
195196
try (Engine.Searcher searcher = shard.acquireSearcher("test")) {
196197
LeafReader leaf = searcher.getDirectoryReader().leaves().get(0).reader();
@@ -389,7 +390,7 @@ public void testDefaultPositionIncrementGap() throws IOException {
389390

390391
IndexShard shard = indexService.getShard(0);
391392
shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL,
392-
sourceToParse, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
393+
sourceToParse, SequenceNumbers.UNASSIGNED_SEQ_NO, 0, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
393394
shard.refresh("test");
394395
try (Engine.Searcher searcher = shard.acquireSearcher("test")) {
395396
LeafReader leaf = searcher.getDirectoryReader().leaves().get(0).reader();
@@ -431,7 +432,7 @@ public void testPositionIncrementGap() throws IOException {
431432

432433
IndexShard shard = indexService.getShard(0);
433434
shard.applyIndexOperationOnPrimary(Versions.MATCH_ANY, VersionType.INTERNAL,
434-
sourceToParse, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
435+
sourceToParse, SequenceNumbers.UNASSIGNED_SEQ_NO, 0, IndexRequest.UNSET_AUTO_GENERATED_TIMESTAMP, false);
435436
shard.refresh("test");
436437
try (Engine.Searcher searcher = shard.acquireSearcher("test")) {
437438
LeafReader leaf = searcher.getDirectoryReader().leaves().get(0).reader();

server/src/main/java/org/elasticsearch/action/bulk/BulkRequest.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.elasticsearch.common.xcontent.XContentParser;
4747
import org.elasticsearch.common.xcontent.XContentType;
4848
import org.elasticsearch.index.VersionType;
49+
import org.elasticsearch.index.seqno.SequenceNumbers;
4950
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
5051

5152
import java.io.IOException;
@@ -83,6 +84,8 @@ public class BulkRequest extends ActionRequest implements CompositeIndicesReques
8384
private static final ParseField PIPELINE = new ParseField("pipeline");
8485
private static final ParseField FIELDS = new ParseField("fields");
8586
private static final ParseField SOURCE = new ParseField("_source");
87+
private static final ParseField IF_SEQ_NO_MATCH = new ParseField("if_seq_no_match");
88+
private static final ParseField IF_PRIMARY_TERM_MATCH = new ParseField("if_primary_term_match");
8689

8790
/**
8891
* Requests that are part of this request. It is only possible to add things that are both {@link ActionRequest}s and
@@ -354,6 +357,8 @@ public BulkRequest add(BytesReference data, @Nullable String defaultIndex, @Null
354357
String opType = null;
355358
long version = Versions.MATCH_ANY;
356359
VersionType versionType = VersionType.INTERNAL;
360+
long ifSeqNoMatch = SequenceNumbers.UNASSIGNED_SEQ_NO;
361+
long ifPrimaryTermMatch = 0;
357362
int retryOnConflict = 0;
358363
String pipeline = valueOrDefault(defaultPipeline, globalPipeline);
359364

@@ -386,6 +391,10 @@ public BulkRequest add(BytesReference data, @Nullable String defaultIndex, @Null
386391
version = parser.longValue();
387392
} else if (VERSION_TYPE.match(currentFieldName, parser.getDeprecationHandler())) {
388393
versionType = VersionType.fromString(parser.text());
394+
} else if (IF_SEQ_NO_MATCH.match(currentFieldName, parser.getDeprecationHandler())) {
395+
ifSeqNoMatch = parser.longValue();
396+
} else if (IF_PRIMARY_TERM_MATCH.match(currentFieldName, parser.getDeprecationHandler())) {
397+
ifPrimaryTermMatch = parser.longValue();
389398
} else if (RETRY_ON_CONFLICT.match(currentFieldName, parser.getDeprecationHandler())) {
390399
retryOnConflict = parser.intValue();
391400
} else if (PIPELINE.match(currentFieldName, parser.getDeprecationHandler())) {
@@ -423,7 +432,7 @@ public BulkRequest add(BytesReference data, @Nullable String defaultIndex, @Null
423432

424433
if ("delete".equals(action)) {
425434
add(new DeleteRequest(index, type, id).routing(routing).parent(parent).version(version)
426-
.versionType(versionType), payload);
435+
.versionType(versionType).setIfMatch(ifSeqNoMatch, ifPrimaryTermMatch), payload);
427436
} else {
428437
nextMarker = findNextMarker(marker, from, data, length);
429438
if (nextMarker == -1) {
@@ -436,16 +445,17 @@ public BulkRequest add(BytesReference data, @Nullable String defaultIndex, @Null
436445
if ("index".equals(action)) {
437446
if (opType == null) {
438447
internalAdd(new IndexRequest(index, type, id).routing(routing).parent(parent).version(version)
439-
.versionType(versionType).setPipeline(pipeline)
448+
.versionType(versionType).setPipeline(pipeline).ifMatch(ifSeqNoMatch, ifPrimaryTermMatch)
440449
.source(sliceTrimmingCarriageReturn(data, from, nextMarker,xContentType), xContentType), payload);
441450
} else {
442451
internalAdd(new IndexRequest(index, type, id).routing(routing).parent(parent).version(version)
443452
.versionType(versionType).create("create".equals(opType)).setPipeline(pipeline)
453+
.ifMatch(ifSeqNoMatch, ifPrimaryTermMatch)
444454
.source(sliceTrimmingCarriageReturn(data, from, nextMarker, xContentType), xContentType), payload);
445455
}
446456
} else if ("create".equals(action)) {
447457
internalAdd(new IndexRequest(index, type, id).routing(routing).parent(parent).version(version)
448-
.versionType(versionType).create(true).setPipeline(pipeline)
458+
.versionType(versionType).create(true).setPipeline(pipeline).ifMatch(ifSeqNoMatch, ifPrimaryTermMatch)
449459
.source(sliceTrimmingCarriageReturn(data, from, nextMarker, xContentType), xContentType), payload);
450460
} else if ("update".equals(action)) {
451461
UpdateRequest updateRequest = new UpdateRequest(index, type, id).routing(routing).parent(parent)

server/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
package org.elasticsearch.action.bulk;
2121

22-
import org.apache.logging.log4j.Logger;
2322
import org.apache.logging.log4j.LogManager;
23+
import org.apache.logging.log4j.Logger;
2424
import org.apache.logging.log4j.message.ParameterizedMessage;
2525
import org.elasticsearch.ExceptionsHelper;
2626
import org.elasticsearch.action.DocWriteRequest;
@@ -459,7 +459,7 @@ private static void executeIndexRequestOnPrimary(BulkPrimaryExecutionContext con
459459
executeOnPrimaryWhileHandlingMappingUpdates(context,
460460
() ->
461461
primary.applyIndexOperationOnPrimary(request.version(), request.versionType(), sourceToParse,
462-
request.getAutoGeneratedTimestamp(), request.isRetry()),
462+
request.ifSeqNoMatch(), request.ifPrimaryTermMatch(), request.getAutoGeneratedTimestamp(), request.isRetry()),
463463
e -> primary.getFailedIndexResult(e, request.version()),
464464
context::markOperationAsExecuted,
465465
mapping -> mappingUpdater.updateMappings(mapping, primary.shardId(), request.type()));
@@ -470,7 +470,8 @@ private static void executeDeleteRequestOnPrimary(BulkPrimaryExecutionContext co
470470
final DeleteRequest request = context.getRequestToExecute();
471471
final IndexShard primary = context.getPrimary();
472472
executeOnPrimaryWhileHandlingMappingUpdates(context,
473-
() -> primary.applyDeleteOperationOnPrimary(request.version(), request.type(), request.id(), request.versionType()),
473+
() -> primary.applyDeleteOperationOnPrimary(request.version(), request.type(), request.id(), request.versionType(),
474+
request.ifSeqNoMatch(), request.ifPrimaryTermMatch()),
474475
e -> primary.getFailedDeleteResult(e, request.version()),
475476
context::markOperationAsExecuted,
476477
mapping -> mappingUpdater.updateMappings(mapping, primary.shardId(), request.type()));

server/src/main/java/org/elasticsearch/action/delete/DeleteRequest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.action.delete;
2121

22+
import org.elasticsearch.Version;
2223
import org.elasticsearch.action.ActionRequestValidationException;
2324
import org.elasticsearch.action.CompositeIndicesRequest;
2425
import org.elasticsearch.action.DocWriteRequest;
@@ -29,6 +30,7 @@
2930
import org.elasticsearch.common.io.stream.StreamOutput;
3031
import org.elasticsearch.common.lucene.uid.Versions;
3132
import org.elasticsearch.index.VersionType;
33+
import org.elasticsearch.index.seqno.SequenceNumbers;
3234
import org.elasticsearch.index.shard.ShardId;
3335

3436
import java.io.IOException;
@@ -57,6 +59,8 @@ public class DeleteRequest extends ReplicatedWriteRequest<DeleteRequest>
5759
private String parent;
5860
private long version = Versions.MATCH_ANY;
5961
private VersionType versionType = VersionType.INTERNAL;
62+
private long ifSeqNoMatch = SequenceNumbers.UNASSIGNED_SEQ_NO;
63+
private long ifPrimaryTermMatch = 0;
6064

6165
public DeleteRequest() {
6266
}
@@ -98,6 +102,12 @@ public ActionRequestValidationException validate() {
98102
if (versionType == VersionType.FORCE) {
99103
validationException = addValidationError("version type [force] may no longer be used", validationException);
100104
}
105+
106+
if (ifSeqNoMatch != SequenceNumbers.UNASSIGNED_SEQ_NO && (
107+
versionType != VersionType.INTERNAL || version != Versions.MATCH_ANY
108+
)) {
109+
validationException = addValidationError("compare and write operations can not use versioning", validationException);
110+
}
101111
return validationException;
102112
}
103113

@@ -190,6 +200,32 @@ public DeleteRequest versionType(VersionType versionType) {
190200
return this;
191201
}
192202

203+
public long ifSeqNoMatch() {
204+
return ifSeqNoMatch;
205+
}
206+
207+
public long ifPrimaryTermMatch() {
208+
return ifPrimaryTermMatch;
209+
}
210+
211+
public DeleteRequest setIfMatch(long seqNo, long term) {
212+
if (term == 0 && seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
213+
throw new IllegalArgumentException("seqNo is set, but primary term is [0]");
214+
}
215+
if (term != 0 && seqNo == SequenceNumbers.UNASSIGNED_SEQ_NO) {
216+
throw new IllegalArgumentException("seqNo is unassigned, but primary term is [" + term + "]");
217+
}
218+
if (seqNo < 0 && seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
219+
throw new IllegalArgumentException("sequence numbers must be non negative. got [" + seqNo + "].");
220+
}
221+
if (term < 0) {
222+
throw new IllegalArgumentException("primary term must be non negative. got [" + term + "]");
223+
}
224+
ifSeqNoMatch = seqNo;
225+
ifPrimaryTermMatch = term;
226+
return this;
227+
}
228+
193229
@Override
194230
public VersionType versionType() {
195231
return this.versionType;
@@ -209,6 +245,13 @@ public void readFrom(StreamInput in) throws IOException {
209245
parent = in.readOptionalString();
210246
version = in.readLong();
211247
versionType = VersionType.fromValue(in.readByte());
248+
if (in.getVersion().onOrAfter(Version.V_6_0_0)) {
249+
ifSeqNoMatch = in.readZLong();
250+
ifPrimaryTermMatch = in.readVLong();
251+
} else {
252+
ifSeqNoMatch = SequenceNumbers.UNASSIGNED_SEQ_NO;
253+
ifPrimaryTermMatch = 0;
254+
}
212255
}
213256

214257
@Override
@@ -220,6 +263,15 @@ public void writeTo(StreamOutput out) throws IOException {
220263
out.writeOptionalString(parent());
221264
out.writeLong(version);
222265
out.writeByte(versionType.getValue());
266+
if (out.getVersion().onOrAfter(Version.V_6_0_0)) {
267+
out.writeZLong(ifSeqNoMatch);
268+
out.writeVLong(ifPrimaryTermMatch);
269+
} else if (ifSeqNoMatch != SequenceNumbers.UNASSIGNED_SEQ_NO || ifPrimaryTermMatch != 0) {
270+
assert false : "setIfMatch [" + ifSeqNoMatch + "], currentDocTem [" + ifPrimaryTermMatch + "]";
271+
throw new IllegalStateException(
272+
"sequence number based compare and write is not supported until all nodes are on version 7.0 or higher. " +
273+
"Stream version [" + out.getVersion() + "]");
274+
}
223275
}
224276

225277
@Override

server/src/main/java/org/elasticsearch/action/delete/DeleteRequestBuilder.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,14 @@ public DeleteRequestBuilder setVersionType(VersionType versionType) {
8989
request.versionType(versionType);
9090
return this;
9191
}
92+
93+
/**
94+
* only performs this delete request if the document was last modification was assigned the given
95+
* sequence number and primary term
96+
*/
97+
public DeleteRequestBuilder setIfMatch(long seqNo, long term) {
98+
request.setIfMatch(seqNo, term);
99+
return this;
100+
}
101+
92102
}

server/src/main/java/org/elasticsearch/action/index/IndexRequest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.elasticsearch.common.xcontent.XContentHelper;
4444
import org.elasticsearch.common.xcontent.XContentType;
4545
import org.elasticsearch.index.VersionType;
46+
import org.elasticsearch.index.seqno.SequenceNumbers;
4647
import org.elasticsearch.index.shard.ShardId;
4748

4849
import java.io.IOException;
@@ -106,6 +107,8 @@ public class IndexRequest extends ReplicatedWriteRequest<IndexRequest> implement
106107
private long autoGeneratedTimestamp = UNSET_AUTO_GENERATED_TIMESTAMP;
107108

108109
private boolean isRetry = false;
110+
private long ifSeqNoMatch = SequenceNumbers.UNASSIGNED_SEQ_NO;
111+
private long ifPrimaryTermMatch = 0;
109112

110113

111114
public IndexRequest() {
@@ -166,6 +169,12 @@ public ActionRequestValidationException validate() {
166169
validationException);
167170
return validationException;
168171
}
172+
173+
if (ifSeqNoMatch != SequenceNumbers.UNASSIGNED_SEQ_NO || ifPrimaryTermMatch != 0) {
174+
validationException = addValidationError("create operations do not support compare and set. use index instead",
175+
validationException);
176+
return validationException;
177+
}
169178
}
170179

171180
if (opType() != OpType.INDEX && id == null) {
@@ -194,6 +203,12 @@ public ActionRequestValidationException validate() {
194203
validationException = addValidationError("pipeline cannot be an empty string", validationException);
195204
}
196205

206+
if (ifSeqNoMatch != SequenceNumbers.UNASSIGNED_SEQ_NO && (
207+
versionType != VersionType.INTERNAL || version != Versions.MATCH_ANY
208+
)) {
209+
validationException = addValidationError("compare and write operations can not use versioning", validationException);
210+
}
211+
197212
return validationException;
198213
}
199214

@@ -486,6 +501,33 @@ public IndexRequest versionType(VersionType versionType) {
486501
return this;
487502
}
488503

504+
public IndexRequest ifMatch(long seqNo, long term) {
505+
if (term == 0 && seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
506+
throw new IllegalArgumentException("seqNo is set, but primary term is [0]");
507+
}
508+
509+
if (term != 0 && seqNo == SequenceNumbers.UNASSIGNED_SEQ_NO) {
510+
throw new IllegalArgumentException("seqNo is unassigned, but primary term is [" + term + "]");
511+
}
512+
if (seqNo < 0 && seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO) {
513+
throw new IllegalArgumentException("sequence numbers must be non negative. got [" + seqNo + "].");
514+
}
515+
if (term < 0) {
516+
throw new IllegalArgumentException("primary term must be non negative. got [" + term + "]");
517+
}
518+
ifSeqNoMatch = seqNo;
519+
ifPrimaryTermMatch = term;
520+
return this;
521+
}
522+
523+
public long ifSeqNoMatch() {
524+
return ifSeqNoMatch;
525+
}
526+
527+
public long ifPrimaryTermMatch() {
528+
return ifPrimaryTermMatch;
529+
}
530+
489531
@Override
490532
public VersionType versionType() {
491533
return this.versionType;
@@ -515,6 +557,8 @@ public void process(Version indexCreatedVersion, @Nullable MappingMetaData mappi
515557
// generate id if not already provided
516558
if (id == null) {
517559
assert autoGeneratedTimestamp == -1 : "timestamp has already been generated!";
560+
assert ifSeqNoMatch == SequenceNumbers.UNASSIGNED_SEQ_NO;
561+
assert ifPrimaryTermMatch == 0;
518562
autoGeneratedTimestamp = Math.max(0, System.currentTimeMillis()); // extra paranoia
519563
String uid;
520564
if (indexCreatedVersion.onOrAfter(Version.V_6_0_0_beta1)) {
@@ -554,6 +598,13 @@ public void readFrom(StreamInput in) throws IOException {
554598
} else {
555599
contentType = null;
556600
}
601+
if (in.getVersion().onOrAfter(Version.V_6_6_0)) {
602+
ifSeqNoMatch = in.readZLong();
603+
ifPrimaryTermMatch = in.readVLong();
604+
} else {
605+
ifSeqNoMatch = SequenceNumbers.UNASSIGNED_SEQ_NO;
606+
ifPrimaryTermMatch = 0;
607+
}
557608
}
558609

559610
@Override
@@ -583,6 +634,15 @@ public void writeTo(StreamOutput out) throws IOException {
583634
} else {
584635
out.writeBoolean(false);
585636
}
637+
if (out.getVersion().onOrAfter(Version.V_6_6_0)) {
638+
out.writeZLong(ifSeqNoMatch);
639+
out.writeVLong(ifPrimaryTermMatch);
640+
} else if (ifSeqNoMatch != SequenceNumbers.UNASSIGNED_SEQ_NO || ifPrimaryTermMatch != 0) {
641+
assert false : "setIfMatch [" + ifSeqNoMatch + "], currentDocTem [" + ifPrimaryTermMatch + "]";
642+
throw new IllegalStateException(
643+
"sequence number based compare and write is not supported until all nodes are on version 7.0 or higher. " +
644+
"Stream version [" + out.getVersion() + "]");
645+
}
586646
}
587647

588648
@Override

server/src/main/java/org/elasticsearch/action/index/IndexRequestBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ public IndexRequestBuilder setVersionType(VersionType versionType) {
208208
return this;
209209
}
210210

211+
/**
212+
* only performs this indexing request if the document was last modification was assigned the given
213+
* sequence number and primary term
214+
*/
215+
public IndexRequestBuilder setIfMatch(long seqNo, long term) {
216+
request.ifMatch(seqNo, term);
217+
return this;
218+
}
219+
211220
/**
212221
* Sets the ingest pipeline to be executed before indexing the document
213222
*/

0 commit comments

Comments
 (0)