Skip to content

Commit e900579

Browse files
committed
Simplify watcher indexing listener. (elastic#52627)
Add watcher to trigger server after index operation has succeeded, instead of adding a watch to trigger service before the actual index operation has performed on the shard level. This logic is simpler to reason about in the case that a failure does occur during the execution of an index operation on the shard level. Relates to elastic#52453, but I think doesn't fix it, but makes it easier to debug.
1 parent a154f9c commit e900579

File tree

2 files changed

+24
-49
lines changed

2 files changed

+24
-49
lines changed

x-pack/plugin/watcher/src/main/java/org/elasticsearch/xpack/watcher/WatcherIndexingListener.java

+12-28
Original file line numberDiff line numberDiff line change
@@ -97,20 +97,25 @@ void setConfiguration(Configuration configuration) {
9797
*
9898
* @param shardId The shard id object of the document being processed
9999
* @param operation The index operation
100-
* @return The index operation
100+
* @param result The result of the operation
101101
*/
102102
@Override
103-
public Engine.Index preIndex(ShardId shardId, Engine.Index operation) {
103+
public void postIndex(ShardId shardId, Engine.Index operation, Engine.IndexResult result) {
104104
if (isWatchDocument(shardId.getIndexName())) {
105+
if (result.getResultType() == Engine.Result.Type.FAILURE) {
106+
postIndex(shardId, operation, result.getFailure());
107+
return;
108+
}
109+
105110
ZonedDateTime now = Instant.ofEpochMilli(clock.millis()).atZone(ZoneOffset.UTC);
106111
try {
107112
Watch watch = parser.parseWithSecrets(operation.id(), true, operation.source(), now, XContentType.JSON,
108113
operation.getIfSeqNo(), operation.getIfPrimaryTerm());
109114
ShardAllocationConfiguration shardAllocationConfiguration = configuration.localShards.get(shardId);
110115
if (shardAllocationConfiguration == null) {
111116
logger.debug("no distributed watch execution info found for watch [{}] on shard [{}], got configuration for {}",
112-
watch.id(), shardId, configuration.localShards.keySet());
113-
return operation;
117+
watch.id(), shardId, configuration.localShards.keySet());
118+
return;
114119
}
115120

116121
boolean shouldBeTriggered = shardAllocationConfiguration.shouldBeTriggered(watch.id());
@@ -128,32 +133,12 @@ public Engine.Index preIndex(ShardId shardId, Engine.Index operation) {
128133
} catch (IOException e) {
129134
throw new ElasticsearchParseException("Could not parse watch with id [{}]", e, operation.id());
130135
}
131-
132136
}
133-
134-
return operation;
135137
}
136138

137139
/**
138-
* In case of a document related failure (for example version conflict), then clean up resources for a watch
139-
* in the trigger service.
140-
*/
141-
@Override
142-
public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult result) {
143-
if (result.getResultType() == Engine.Result.Type.FAILURE) {
144-
assert result.getFailure() != null;
145-
postIndex(shardId, index, result.getFailure());
146-
}
147-
}
148-
149-
/**
150-
* In case of an engine related error, we have to ensure that the triggerservice does not leave anything behind
151-
*
152-
* TODO: If the configuration changes between preindex and postindex methods and we add a
153-
* watch, that could not be indexed
154-
* TODO: this watch might not be deleted from the triggerservice. Are we willing to accept this?
155-
* TODO: This could be circumvented by using a threadlocal in preIndex(), that contains the
156-
* watch and is cleared afterwards
140+
* In case of an engine related error, we just log that we failed the add the watch to the trigger service.
141+
* No need to interact with the trigger service.
157142
*
158143
* @param shardId The shard id object of the document being processed
159144
* @param index The index operation
@@ -162,8 +147,7 @@ public void postIndex(ShardId shardId, Engine.Index index, Engine.IndexResult re
162147
@Override
163148
public void postIndex(ShardId shardId, Engine.Index index, Exception ex) {
164149
if (isWatchDocument(shardId.getIndexName())) {
165-
logger.debug(() -> new ParameterizedMessage("removing watch [{}] from trigger", index.id()), ex);
166-
triggerService.remove(index.id());
150+
logger.debug(() -> new ParameterizedMessage("failed to add watch [{}] to trigger service", index.id()), ex);
167151
}
168152
}
169153

x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherIndexingListenerTests.java

+12-21
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,20 @@ public void testPreIndexCheckActive() throws Exception {
114114
verifyZeroInteractions(parser);
115115
}
116116

117-
public void testPreIndex() throws Exception {
117+
public void testPostIndex() throws Exception {
118118
when(operation.id()).thenReturn(randomAlphaOfLength(10));
119119
when(operation.source()).thenReturn(BytesArray.EMPTY);
120120
when(shardId.getIndexName()).thenReturn(Watch.INDEX);
121+
List<Engine.Result.Type> types = new ArrayList<>(Arrays.asList(Engine.Result.Type.values()));
122+
types.remove(Engine.Result.Type.FAILURE);
123+
when(result.getResultType()).thenReturn(randomFrom(types));
121124

122125
boolean watchActive = randomBoolean();
123126
boolean isNewWatch = randomBoolean();
124127
Watch watch = mockWatch("_id", watchActive, isNewWatch);
125128
when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject(), anyLong(), anyLong())).thenReturn(watch);
126129

127-
Engine.Index returnedOperation = listener.preIndex(shardId, operation);
128-
assertThat(returnedOperation, is(operation));
130+
listener.postIndex(shardId, operation, result);
129131
ZonedDateTime now = DateUtils.nowWithMillisResolution(clock);
130132
verify(parser).parseWithSecrets(eq(operation.id()), eq(true), eq(BytesArray.EMPTY), eq(now), anyObject(), anyLong(), anyLong());
131133

@@ -140,12 +142,13 @@ public void testPreIndex() throws Exception {
140142

141143
// this test emulates an index with 10 shards, and ensures that triggering only happens on a
142144
// single shard
143-
public void testPreIndexWatchGetsOnlyTriggeredOnceAcrossAllShards() throws Exception {
145+
public void testPostIndexWatchGetsOnlyTriggeredOnceAcrossAllShards() throws Exception {
144146
String id = randomAlphaOfLength(10);
145147
int totalShardCount = randomIntBetween(1, 10);
146148
boolean watchActive = randomBoolean();
147149
boolean isNewWatch = randomBoolean();
148150
Watch watch = mockWatch(id, watchActive, isNewWatch);
151+
when(result.getResultType()).thenReturn(Engine.Result.Type.SUCCESS);
149152

150153
when(shardId.getIndexName()).thenReturn(Watch.INDEX);
151154
when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject(), anyLong(), anyLong())).thenReturn(watch);
@@ -155,7 +158,7 @@ public void testPreIndexWatchGetsOnlyTriggeredOnceAcrossAllShards() throws Excep
155158
localShards.put(shardId, new ShardAllocationConfiguration(idx, totalShardCount, Collections.emptyList()));
156159
Configuration configuration = new Configuration(Watch.INDEX, localShards);
157160
listener.setConfiguration(configuration);
158-
listener.preIndex(shardId, operation);
161+
listener.postIndex(shardId, operation, result);
159162
}
160163

161164
// no matter how many shards we had, this should have been only called once
@@ -187,16 +190,17 @@ private Watch mockWatch(String id, boolean active, boolean isNewWatch) {
187190
return watch;
188191
}
189192

190-
public void testPreIndexCheckParsingException() throws Exception {
193+
public void testPostIndexCheckParsingException() throws Exception {
191194
String id = randomAlphaOfLength(10);
192195
when(operation.id()).thenReturn(id);
193196
when(operation.source()).thenReturn(BytesArray.EMPTY);
194197
when(shardId.getIndexName()).thenReturn(Watch.INDEX);
195198
when(parser.parseWithSecrets(anyObject(), eq(true), anyObject(), anyObject(), anyObject(), anyLong(), anyLong()))
196199
.thenThrow(new IOException("self thrown"));
200+
when(result.getResultType()).thenReturn(Engine.Result.Type.SUCCESS);
197201

198202
ElasticsearchParseException exc = expectThrows(ElasticsearchParseException.class,
199-
() -> listener.preIndex(shardId, operation));
203+
() -> listener.postIndex(shardId, operation, result));
200204
assertThat(exc.getMessage(), containsString("Could not parse watch"));
201205
assertThat(exc.getMessage(), containsString(id));
202206
}
@@ -207,19 +211,6 @@ public void testPostIndexRemoveTriggerOnDocumentRelatedException() throws Except
207211
when(result.getFailure()).thenReturn(new RuntimeException());
208212
when(shardId.getIndexName()).thenReturn(Watch.INDEX);
209213

210-
listener.postIndex(shardId, operation, result);
211-
verify(triggerService).remove(eq("_id"));
212-
}
213-
214-
public void testPostIndexRemoveTriggerOnDocumentRelatedException_ignoreOtherEngineResultTypes() throws Exception {
215-
List<Engine.Result.Type> types = new ArrayList<>(Arrays.asList(Engine.Result.Type.values()));
216-
types.remove(Engine.Result.Type.FAILURE);
217-
218-
when(operation.id()).thenReturn("_id");
219-
when(result.getResultType()).thenReturn(randomFrom(types));
220-
when(result.getFailure()).thenReturn(new RuntimeException());
221-
when(shardId.getIndexName()).thenReturn(Watch.INDEX);
222-
223214
listener.postIndex(shardId, operation, result);
224215
verifyZeroInteractions(triggerService);
225216
}
@@ -239,7 +230,7 @@ public void testPostIndexRemoveTriggerOnEngineLevelException() throws Exception
239230
when(shardId.getIndexName()).thenReturn(Watch.INDEX);
240231

241232
listener.postIndex(shardId, operation, new ElasticsearchParseException("whatever"));
242-
verify(triggerService).remove(eq("_id"));
233+
verifyZeroInteractions(triggerService);
243234
}
244235

245236
public void testPostIndexRemoveTriggerOnEngineLevelException_ignoreNonWatcherDocument() throws Exception {

0 commit comments

Comments
 (0)