Skip to content

Commit 033ba72

Browse files
authored
Remove support for internal versioning for concurrency control (#38254)
Elasticsearch has long [supported](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning) compare and set (a.k.a optimistic concurrency control) operations using internal document versioning. Sadly that approach is flawed and can sometime do the wrong thing. Here's the relevant excerpt from the resiliency status page: > When a primary has been partitioned away from the cluster there is a short period of time until it detects this. During that time it will continue indexing writes locally, thereby updating document versions. When it tries to replicate the operation, however, it will discover that it is partitioned away. It won’t acknowledge the write and will wait until the partition is resolved to negotiate with the master on how to proceed. The master will decide to either fail any replicas which failed to index the operations on the primary or tell the primary that it has to step down because a new primary has been chosen in the meantime. Since the old primary has already written documents, clients may already have read from the old primary before it shuts itself down. The version numbers of these reads may not be unique if the new primary has already accepted writes for the same document We recently [introduced](https://www.elastic.co/guide/en/elasticsearch/reference/6.x/optimistic-concurrency-control.html) a new sequence number based approach that doesn't suffer from this dirty reads problem. This commit removes support for internal versioning as a concurrency control mechanism in favor of the sequence number approach. Relates to #1078
1 parent b03d138 commit 033ba72

File tree

46 files changed

+225
-805
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+225
-805
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/WatcherRequestConverters.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ static Request putWatch(PutWatchRequest putWatchRequest) {
7070

7171
Request request = new Request(HttpPut.METHOD_NAME, endpoint);
7272
RequestConverters.Params params = new RequestConverters.Params(request)
73-
.withVersion(putWatchRequest.getVersion())
7473
.withIfSeqNo(putWatchRequest.ifSeqNo())
7574
.withIfPrimaryTerm(putWatchRequest.ifPrimaryTerm());
7675
if (putWatchRequest.isActive() == false) {

client/rest-high-level/src/main/java/org/elasticsearch/client/watcher/PutWatchRequest.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import org.elasticsearch.client.Validatable;
2222
import org.elasticsearch.common.Strings;
2323
import org.elasticsearch.common.bytes.BytesReference;
24-
import org.elasticsearch.common.lucene.uid.Versions;
2524
import org.elasticsearch.common.xcontent.XContentType;
2625
import org.elasticsearch.index.seqno.SequenceNumbers;
2726

@@ -43,11 +42,9 @@ public final class PutWatchRequest implements Validatable {
4342
private final BytesReference source;
4443
private final XContentType xContentType;
4544
private boolean active = true;
46-
private long version = Versions.MATCH_ANY;
4745
private long ifSeqNo = SequenceNumbers.UNASSIGNED_SEQ_NO;
4846
private long ifPrimaryTerm = UNASSIGNED_PRIMARY_TERM;
4947

50-
5148
public PutWatchRequest(String id, BytesReference source, XContentType xContentType) {
5249
Objects.requireNonNull(id, "watch id is missing");
5350
if (isValidId(id) == false) {
@@ -95,14 +92,6 @@ public XContentType xContentType() {
9592
return xContentType;
9693
}
9794

98-
public long getVersion() {
99-
return version;
100-
}
101-
102-
public void setVersion(long version) {
103-
this.version = version;
104-
}
105-
10695
/**
10796
* only performs this put request if the watch's last modification was assigned the given
10897
* sequence number. Must be used in combination with {@link #setIfPrimaryTerm(long)}

client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -768,8 +768,6 @@ public void testUpdate() throws IOException {
768768
}
769769
}
770770
setRandomWaitForActiveShards(updateRequest::waitForActiveShards, expectedParams);
771-
setRandomVersion(updateRequest, expectedParams);
772-
setRandomVersionType(updateRequest::versionType, expectedParams);
773771
setRandomIfSeqNoAndTerm(updateRequest, new HashMap<>()); // if* params are passed in the body
774772
if (randomBoolean()) {
775773
int retryOnConflict = randomIntBetween(0, 5);
@@ -911,14 +909,7 @@ public void testBulk() throws IOException {
911909
if (randomBoolean()) {
912910
docWriteRequest.routing(randomAlphaOfLength(10));
913911
}
914-
if (randomBoolean()) {
915-
if (randomBoolean()) {
916-
docWriteRequest.version(randomNonNegativeLong());
917-
}
918-
if (randomBoolean()) {
919-
docWriteRequest.versionType(randomFrom(VersionType.values()));
920-
}
921-
} else if (randomBoolean()) {
912+
if (opType != DocWriteRequest.OpType.UPDATE && randomBoolean()) {
922913
docWriteRequest.setIfSeqNo(randomNonNegativeLong());
923914
docWriteRequest.setIfPrimaryTerm(randomLongBetween(1, 200));
924915
}

client/rest-high-level/src/test/java/org/elasticsearch/client/WatcherRequestConvertersTests.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
import org.elasticsearch.client.watcher.DeactivateWatchRequest;
3030
import org.elasticsearch.client.watcher.DeleteWatchRequest;
3131
import org.elasticsearch.client.watcher.ExecuteWatchRequest;
32-
import org.elasticsearch.client.watcher.PutWatchRequest;
3332
import org.elasticsearch.client.watcher.GetWatchRequest;
33+
import org.elasticsearch.client.watcher.PutWatchRequest;
3434
import org.elasticsearch.client.watcher.StartWatchServiceRequest;
3535
import org.elasticsearch.client.watcher.StopWatchServiceRequest;
3636
import org.elasticsearch.client.watcher.WatcherStatsRequest;
@@ -88,9 +88,12 @@ public void testPutWatch() throws Exception {
8888
}
8989

9090
if (randomBoolean()) {
91-
long version = randomLongBetween(10, 100);
92-
putWatchRequest.setVersion(version);
93-
expectedParams.put("version", String.valueOf(version));
91+
long seqNo = randomNonNegativeLong();
92+
long ifPrimaryTerm = randomLongBetween(1, 200);
93+
putWatchRequest.setIfSeqNo(seqNo);
94+
putWatchRequest.setIfPrimaryTerm(ifPrimaryTerm);
95+
expectedParams.put("if_seq_no", String.valueOf(seqNo));
96+
expectedParams.put("if_primary_term", String.valueOf(ifPrimaryTerm));
9497
}
9598

9699
Request request = WatcherRequestConverters.putWatch(putWatchRequest);

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/CRUDDocumentationIT.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ public void testIndex() throws Exception {
170170
// tag::index-response
171171
String index = indexResponse.getIndex();
172172
String id = indexResponse.getId();
173-
long version = indexResponse.getVersion();
174173
if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {
175174
// <1>
176175
} else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) {
@@ -220,7 +219,8 @@ public void testIndex() throws Exception {
220219
IndexRequest request = new IndexRequest("posts")
221220
.id("1")
222221
.source("field", "value")
223-
.version(1);
222+
.setIfSeqNo(10L)
223+
.setIfPrimaryTerm(20);
224224
try {
225225
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
226226
} catch(ElasticsearchException e) {
@@ -432,7 +432,8 @@ public void testUpdate() throws Exception {
432432
// tag::update-conflict
433433
UpdateRequest request = new UpdateRequest("posts", "1")
434434
.doc("field", "value")
435-
.version(1);
435+
.setIfSeqNo(101L)
436+
.setIfPrimaryTerm(200L);
436437
try {
437438
UpdateResponse updateResponse = client.update(
438439
request, RequestOptions.DEFAULT);
@@ -499,9 +500,10 @@ public void testUpdate() throws Exception {
499500
request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL); // <1>
500501
request.setRefreshPolicy("wait_for"); // <2>
501502
// end::update-request-refresh
502-
// tag::update-request-version
503-
request.version(2); // <1>
504-
// end::update-request-version
503+
// tag::update-request-cas
504+
request.setIfSeqNo(2L); // <1>
505+
request.setIfPrimaryTerm(1L); // <2>
506+
// end::update-request-request-cas
505507
// tag::update-request-detect-noop
506508
request.detectNoop(false); // <1>
507509
// end::update-request-detect-noop
@@ -630,7 +632,7 @@ public void testDelete() throws Exception {
630632
// tag::delete-conflict
631633
try {
632634
DeleteResponse deleteResponse = client.delete(
633-
new DeleteRequest("posts", "1").version(2),
635+
new DeleteRequest("posts", "1").setIfSeqNo(100).setIfPrimaryTerm(2),
634636
RequestOptions.DEFAULT);
635637
} catch (ElasticsearchException exception) {
636638
if (exception.status() == RestStatus.CONFLICT) {

docs/java-rest/high-level/document/update.asciidoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,10 @@ include-tagged::{doc-tests-file}[{api}-request-source-exclude]
140140

141141
["source","java",subs="attributes,callouts,macros"]
142142
--------------------------------------------------
143-
include-tagged::{doc-tests-file}[{api}-request-version]
143+
include-tagged::{doc-tests-file}[{api}-request-cas]
144144
--------------------------------------------------
145-
<1> Version
145+
<1> ifSeqNo
146+
<2> ifPrimaryTerm
146147

147148
["source","java",subs="attributes,callouts,macros"]
148149
--------------------------------------------------

docs/reference/migration/migrate_7_0/api.asciidoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22
[[breaking_70_api_changes]]
33
=== API changes
44

5+
[float]
6+
==== Internal Versioning is no longer supported for optimistic concurrency control
7+
8+
Elasticsearch maintains a numeric version field for each document it stores. That field
9+
is incremented by one with every change to the document. Until 7.0.0 the API allowed using
10+
that field for optimistic concurrency control, i.e., making a write operation conditional
11+
on the current document version. Sadly, that approach is flawed because the value of the
12+
version doesn't always uniquely represent a change to the document. If a primary fails
13+
while handling a write operation, it may expose a version that will then be reused by the
14+
new primary.
15+
16+
Due to that issue, internal versioning can no longer be used and is replaced by a new
17+
method based on sequence numbers. See <<optimistic-concurrency-control>> for more details.
18+
19+
Note that the `external` versioning type is still fully supported.
20+
521
[float]
622
==== Camel case and underscore parameters deprecated in 6.x have been removed
723
A number of duplicate parameters deprecated in 6.x have been removed from

modules/reindex/src/main/java/org/elasticsearch/index/reindex/AsyncDeleteByQueryAction.java

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@
2020
package org.elasticsearch.index.reindex;
2121

2222
import org.apache.logging.log4j.Logger;
23-
import org.elasticsearch.Version;
2423
import org.elasticsearch.action.ActionListener;
2524
import org.elasticsearch.action.delete.DeleteRequest;
2625
import org.elasticsearch.client.ParentTaskAssigningClient;
27-
import org.elasticsearch.cluster.ClusterState;
2826
import org.elasticsearch.script.ScriptService;
2927
import org.elasticsearch.threadpool.ThreadPool;
3028

@@ -33,18 +31,10 @@
3331
*/
3432
public class AsyncDeleteByQueryAction extends AbstractAsyncBulkByScrollAction<DeleteByQueryRequest, TransportDeleteByQueryAction> {
3533

36-
private final boolean useSeqNoForCAS;
37-
3834
public AsyncDeleteByQueryAction(BulkByScrollTask task, Logger logger, ParentTaskAssigningClient client,
3935
ThreadPool threadPool, TransportDeleteByQueryAction action, DeleteByQueryRequest request,
40-
ScriptService scriptService, ClusterState clusterState, ActionListener<BulkByScrollResponse> listener) {
41-
super(task,
42-
// not all nodes support sequence number powered optimistic concurrency control, we fall back to version
43-
clusterState.nodes().getMinNodeVersion().onOrAfter(Version.V_6_7_0) == false,
44-
// all nodes support sequence number powered optimistic concurrency control and we can use it
45-
clusterState.nodes().getMinNodeVersion().onOrAfter(Version.V_6_7_0),
46-
logger, client, threadPool, action, request, listener);
47-
useSeqNoForCAS = clusterState.nodes().getMinNodeVersion().onOrAfter(Version.V_6_7_0);
36+
ScriptService scriptService, ActionListener<BulkByScrollResponse> listener) {
37+
super(task, false, true, logger, client, threadPool, action, request, listener);
4838
}
4939

5040
@Override
@@ -60,12 +50,8 @@ protected RequestWrapper<DeleteRequest> buildRequest(ScrollableHitSource.Hit doc
6050
delete.index(doc.getIndex());
6151
delete.type(doc.getType());
6252
delete.id(doc.getId());
63-
if (useSeqNoForCAS) {
64-
delete.setIfSeqNo(doc.getSeqNo());
65-
delete.setIfPrimaryTerm(doc.getPrimaryTerm());
66-
} else {
67-
delete.version(doc.getVersion());
68-
}
53+
delete.setIfSeqNo(doc.getSeqNo());
54+
delete.setIfPrimaryTerm(doc.getPrimaryTerm());
6955
return wrap(delete);
7056
}
7157

modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportDeleteByQueryAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void doExecute(Task task, DeleteByQueryRequest request, ActionListener<Bu
6161
ClusterState state = clusterService.state();
6262
ParentTaskAssigningClient assigningClient = new ParentTaskAssigningClient(client, clusterService.localNode(),
6363
bulkByScrollTask);
64-
new AsyncDeleteByQueryAction(bulkByScrollTask, logger, assigningClient, threadPool, this, request, scriptService, state,
64+
new AsyncDeleteByQueryAction(bulkByScrollTask, logger, assigningClient, threadPool, this, request, scriptService,
6565
listener).start();
6666
}
6767
);

modules/reindex/src/main/java/org/elasticsearch/index/reindex/TransportUpdateByQueryAction.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import org.elasticsearch.cluster.service.ClusterService;
3232
import org.elasticsearch.common.inject.Inject;
3333
import org.elasticsearch.common.io.stream.Writeable;
34-
import org.elasticsearch.index.VersionType;
3534
import org.elasticsearch.index.mapper.IdFieldMapper;
3635
import org.elasticsearch.index.mapper.IndexFieldMapper;
3736
import org.elasticsearch.index.mapper.RoutingFieldMapper;
@@ -113,13 +112,8 @@ protected RequestWrapper<IndexRequest> buildRequest(ScrollableHitSource.Hit doc)
113112
index.type(doc.getType());
114113
index.id(doc.getId());
115114
index.source(doc.getSource(), doc.getXContentType());
116-
if (useSeqNoForCAS) {
117-
index.setIfSeqNo(doc.getSeqNo());
118-
index.setIfPrimaryTerm(doc.getPrimaryTerm());
119-
} else {
120-
index.versionType(VersionType.INTERNAL);
121-
index.version(doc.getVersion());
122-
}
115+
index.setIfSeqNo(doc.getSeqNo());
116+
index.setIfPrimaryTerm(doc.getPrimaryTerm());
123117
index.setPipeline(mainRequest.getPipeline());
124118
return wrap(index);
125119
}

0 commit comments

Comments
 (0)