-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Seq# based optimistic concurrency control to UpdateRequest #37872
Changes from 14 commits
d297d87
41632be
cb585db
e7eb0a7
11d3986
1b4fbf1
8d2a1a5
2bb0b0e
4cb6b60
dc89962
96dd81e
ff6c34e
2b8f9f8
46fd7da
5917046
a7c75e5
536efac
1e8fb04
42ad9d4
d6fbfae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
--- | ||
"Update with if_seq_no": | ||
|
||
- skip: | ||
version: " - 6.99.99" | ||
reason: if_seq_no was added in 7.0 | ||
|
||
- do: | ||
catch: missing | ||
update: | ||
index: test_1 | ||
id: 1 | ||
if_seq_no: 1 | ||
if_primary_term: 1 | ||
body: | ||
doc: { foo: baz } | ||
|
||
- do: | ||
index: | ||
index: test_1 | ||
id: 1 | ||
body: | ||
foo: baz | ||
|
||
- do: | ||
catch: conflict | ||
update: | ||
index: test_1 | ||
id: 1 | ||
if_seq_no: 234 | ||
if_primary_term: 1 | ||
body: | ||
doc: { foo: baz } | ||
|
||
- do: | ||
update: | ||
index: test_1 | ||
id: 1 | ||
if_seq_no: 0 | ||
if_primary_term: 1 | ||
body: | ||
doc: { foo: bar } | ||
|
||
- do: | ||
get: | ||
index: test_1 | ||
id: 1 | ||
|
||
- match: { _source: { foo: bar } } | ||
|
||
- do: | ||
bulk: | ||
body: | ||
- update: | ||
_index: test_1 | ||
_id: 1 | ||
if_seq_no: 100 | ||
if_primary_term: 200 | ||
- doc: | ||
foo: baz | ||
|
||
- match: { errors: true } | ||
- match: { items.0.update.status: 409 } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,6 +54,8 @@ | |
import java.util.Map; | ||
|
||
import static org.elasticsearch.action.ValidateActions.addValidationError; | ||
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_PRIMARY_TERM; | ||
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO; | ||
|
||
public class UpdateRequest extends InstanceShardOperationRequest<UpdateRequest> | ||
implements DocWriteRequest<UpdateRequest>, WriteRequest<UpdateRequest>, ToXContentObject { | ||
|
@@ -66,6 +68,8 @@ public class UpdateRequest extends InstanceShardOperationRequest<UpdateRequest> | |
private static final ParseField DOC_AS_UPSERT_FIELD = new ParseField("doc_as_upsert"); | ||
private static final ParseField DETECT_NOOP_FIELD = new ParseField("detect_noop"); | ||
private static final ParseField SOURCE_FIELD = new ParseField("_source"); | ||
private static final ParseField IF_SEQ_NO = new ParseField("if_seq_no"); | ||
private static final ParseField IF_PRIMARY_TERM = new ParseField("if_primary_term"); | ||
|
||
static { | ||
PARSER = new ObjectParser<>(UpdateRequest.class.getSimpleName()); | ||
|
@@ -89,6 +93,8 @@ public class UpdateRequest extends InstanceShardOperationRequest<UpdateRequest> | |
PARSER.declareField(UpdateRequest::fetchSource, | ||
(parser, context) -> FetchSourceContext.fromXContent(parser), SOURCE_FIELD, | ||
ObjectParser.ValueType.OBJECT_ARRAY_BOOLEAN_OR_STRING); | ||
PARSER.declareLong(UpdateRequest::setIfSeqNo, IF_SEQ_NO); | ||
PARSER.declareLong(UpdateRequest::setIfPrimaryTerm, IF_PRIMARY_TERM); | ||
} | ||
|
||
// Set to null initially so we can know to override in bulk requests that have a default type. | ||
|
@@ -105,6 +111,9 @@ public class UpdateRequest extends InstanceShardOperationRequest<UpdateRequest> | |
private long version = Versions.MATCH_ANY; | ||
private VersionType versionType = VersionType.INTERNAL; | ||
private int retryOnConflict = 0; | ||
private long ifSeqNo = UNASSIGNED_SEQ_NO; | ||
private long ifPrimaryTerm = UNASSIGNED_PRIMARY_TERM; | ||
|
||
|
||
private RefreshPolicy refreshPolicy = RefreshPolicy.NONE; | ||
|
||
|
@@ -170,6 +179,27 @@ public ActionRequestValidationException validate() { | |
} | ||
} | ||
|
||
if (ifSeqNo != UNASSIGNED_SEQ_NO && ( | ||
versionType != VersionType.INTERNAL || version != Versions.MATCH_ANY | ||
)) { | ||
validationException = addValidationError("compare and write operations can not use versioning", validationException); | ||
} | ||
if (ifPrimaryTerm == UNASSIGNED_PRIMARY_TERM && ifSeqNo != UNASSIGNED_SEQ_NO) { | ||
validationException = addValidationError("ifSeqNo is set, but primary term is [0]", validationException); | ||
} | ||
if (ifPrimaryTerm != UNASSIGNED_PRIMARY_TERM && ifSeqNo == UNASSIGNED_SEQ_NO) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these 3 conditions are the same as for index request. Can we share this code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah. I now changed to take another approach which I also took in a future PR for something else. Let me know if you prefer it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks good |
||
validationException = | ||
addValidationError("ifSeqNo is unassigned, but primary term is [" + ifPrimaryTerm + "]", validationException); | ||
} | ||
|
||
if (ifSeqNo != UNASSIGNED_SEQ_NO && retryOnConflict > 0) { | ||
validationException = addValidationError("compare and write operations can not be retried", validationException); | ||
} | ||
|
||
if (ifSeqNo != UNASSIGNED_SEQ_NO && docAsUpsert) { | ||
validationException = addValidationError("compare and write operations can not be used with upsert", validationException); | ||
} | ||
|
||
if (script == null && doc == null) { | ||
validationException = addValidationError("script or doc is missing", validationException); | ||
} | ||
|
@@ -531,6 +561,55 @@ public VersionType versionType() { | |
return this.versionType; | ||
} | ||
|
||
/** | ||
* only perform this update request if the document was last modification was assigned the given | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. something wrong with this sentence (same for some of these other docs here and in the requestbuilder class) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed. thanks |
||
* sequence number. Must be used in combination with {@link #setIfPrimaryTerm(long)} | ||
* | ||
* If the document last modification was assigned a different sequence number a | ||
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown. | ||
*/ | ||
public UpdateRequest setIfSeqNo(long seqNo) { | ||
if (seqNo < 0 && seqNo != UNASSIGNED_SEQ_NO) { | ||
throw new IllegalArgumentException("sequence numbers must be non negative. got [" + seqNo + "]."); | ||
} | ||
ifSeqNo = seqNo; | ||
return this; | ||
} | ||
|
||
/** | ||
* only performs this update request if the document was last modification was assigned the given | ||
* primary term. Must be used in combination with {@link #setIfSeqNo(long)} | ||
* | ||
* If the document last modification was assigned a different term a | ||
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown. | ||
*/ | ||
public UpdateRequest setIfPrimaryTerm(long term) { | ||
if (term < 0) { | ||
throw new IllegalArgumentException("primary term must be non negative. got [" + term + "]"); | ||
} | ||
ifPrimaryTerm = term; | ||
return this; | ||
} | ||
|
||
/** | ||
* If set, only perform this update request if the document was last modification was assigned this sequence number. | ||
* If the document last modification was assigned a different sequence number a | ||
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown. | ||
*/ | ||
public long ifSeqNo() { | ||
return ifSeqNo; | ||
} | ||
|
||
/** | ||
* If set, only perform this update request if the document was last modification was assigned this primary term. | ||
* | ||
* If the document last modification was assigned a different term a | ||
* {@link org.elasticsearch.index.engine.VersionConflictEngineException} will be thrown. | ||
*/ | ||
public long ifPrimaryTerm() { | ||
return ifPrimaryTerm; | ||
} | ||
|
||
@Override | ||
public OpType opType() { | ||
return OpType.UPDATE; | ||
|
@@ -811,6 +890,10 @@ public void readFrom(StreamInput in) throws IOException { | |
docAsUpsert = in.readBoolean(); | ||
version = in.readLong(); | ||
versionType = VersionType.fromValue(in.readByte()); | ||
if (in.getVersion().onOrAfter(Version.V_7_0_0)) { | ||
ifSeqNo = in.readZLong(); | ||
ifPrimaryTerm = in.readVLong(); | ||
} | ||
detectNoop = in.readBoolean(); | ||
scriptedUpsert = in.readBoolean(); | ||
} | ||
|
@@ -862,6 +945,10 @@ public void writeTo(StreamOutput out) throws IOException { | |
out.writeBoolean(docAsUpsert); | ||
out.writeLong(version); | ||
out.writeByte(versionType.getValue()); | ||
if (out.getVersion().onOrAfter(Version.V_7_0_0)) { | ||
out.writeZLong(ifSeqNo); | ||
out.writeVLong(ifPrimaryTerm); | ||
} | ||
out.writeBoolean(detectNoop); | ||
out.writeBoolean(scriptedUpsert); | ||
} | ||
|
@@ -880,6 +967,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws | |
builder.copyCurrentStructure(parser); | ||
} | ||
} | ||
|
||
if (ifSeqNo != UNASSIGNED_SEQ_NO) { | ||
builder.field(IF_SEQ_NO.getPreferredName(), ifSeqNo); | ||
builder.field(IF_PRIMARY_TERM.getPreferredName(), ifPrimaryTerm); | ||
} | ||
|
||
if (script != null) { | ||
builder.field("script", script); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean "conditional" and not "optional"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good question. I took this over from the text in IndexRequest. I can change all if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
++ to change it all