Skip to content

Commit 2e02742

Browse files
authored
Allow follower indices to override leader settings (#58103)
Today when creating a follower index via the put follow API, or via an auto-follow pattern, it is not possible to specify settings overrides for the follower index. Instead, we copy all of the leader index settings to the follower. Yet, there are cases where a user would want some different settings on the follower index such as the number of replicas, or allocation settings. This commit addresses this by allowing the user to specify settings overrides when creating follower index via manual put follower calls, or via auto-follow patterns. Note that not all settings can be overrode (e.g., index.number_of_shards) so we also have detection that prevents attempting to override settings that must be equal between the leader and follow index. Note that we do not even allow specifying such settings in the overrides, even if they are specified to be equal between the leader and the follower index. Instead, the must be implicitly copied from the leader index, not explicitly set by the user.
1 parent b49ae99 commit 2e02742

File tree

32 files changed

+1138
-132
lines changed

32 files changed

+1138
-132
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.client.ccr;
2121

2222
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.settings.Settings;
2324
import org.elasticsearch.common.unit.ByteSizeValue;
2425
import org.elasticsearch.common.unit.TimeValue;
2526
import org.elasticsearch.common.xcontent.ObjectParser;
@@ -32,6 +33,7 @@
3233

3334
public class FollowConfig {
3435

36+
static final ParseField SETTINGS = new ParseField("settings");
3537
static final ParseField MAX_READ_REQUEST_OPERATION_COUNT = new ParseField("max_read_request_operation_count");
3638
static final ParseField MAX_READ_REQUEST_SIZE = new ParseField("max_read_request_size");
3739
static final ParseField MAX_OUTSTANDING_READ_REQUESTS = new ParseField("max_outstanding_read_requests");
@@ -49,6 +51,7 @@ public class FollowConfig {
4951
FollowConfig::new);
5052

5153
static {
54+
PARSER.declareObject(FollowConfig::setSettings, (p, c) -> Settings.fromXContent(p), SETTINGS);
5255
PARSER.declareInt(FollowConfig::setMaxReadRequestOperationCount, MAX_READ_REQUEST_OPERATION_COUNT);
5356
PARSER.declareInt(FollowConfig::setMaxOutstandingReadRequests, MAX_OUTSTANDING_READ_REQUESTS);
5457
PARSER.declareField(
@@ -81,6 +84,7 @@ static FollowConfig fromXContent(XContentParser parser) {
8184
return PARSER.apply(parser, null);
8285
}
8386

87+
private Settings settings = Settings.EMPTY;
8488
private Integer maxReadRequestOperationCount;
8589
private Integer maxOutstandingReadRequests;
8690
private ByteSizeValue maxReadRequestSize;
@@ -95,6 +99,14 @@ static FollowConfig fromXContent(XContentParser parser) {
9599
FollowConfig() {
96100
}
97101

102+
public Settings getSettings() {
103+
return settings;
104+
}
105+
106+
public void setSettings(final Settings settings) {
107+
this.settings = Objects.requireNonNull(settings);
108+
}
109+
98110
public Integer getMaxReadRequestOperationCount() {
99111
return maxReadRequestOperationCount;
100112
}
@@ -176,6 +188,13 @@ public void setReadPollTimeout(TimeValue readPollTimeout) {
176188
}
177189

178190
void toXContentFragment(XContentBuilder builder, ToXContent.Params params) throws IOException {
191+
if (settings.isEmpty() == false) {
192+
builder.startObject(SETTINGS.getPreferredName());
193+
{
194+
settings.toXContent(builder, params);
195+
}
196+
builder.endObject();
197+
}
179198
if (maxReadRequestOperationCount != null) {
180199
builder.field(MAX_READ_REQUEST_OPERATION_COUNT.getPreferredName(), maxReadRequestOperationCount);
181200
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.client.ccr;
2121

2222
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.settings.Settings;
2324
import org.elasticsearch.common.unit.ByteSizeValue;
2425
import org.elasticsearch.common.unit.TimeValue;
2526
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
@@ -98,6 +99,7 @@ public static class Pattern extends FollowConfig {
9899
PARSER.declareString(ConstructingObjectParser.constructorArg(), PutFollowRequest.REMOTE_CLUSTER_FIELD);
99100
PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), PutAutoFollowPatternRequest.LEADER_PATTERNS_FIELD);
100101
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), PutAutoFollowPatternRequest.FOLLOW_PATTERN_FIELD);
102+
PARSER.declareObject(Pattern::setSettings, (p, c) -> Settings.fromXContent(p), PutAutoFollowPatternRequest.SETTINGS);
101103
PARSER.declareInt(Pattern::setMaxReadRequestOperationCount, FollowConfig.MAX_READ_REQUEST_OPERATION_COUNT);
102104
PARSER.declareField(
103105
Pattern::setMaxReadRequestSize,

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
package org.elasticsearch.client;
2121

2222
import org.apache.logging.log4j.message.ParameterizedMessage;
23+
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
24+
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
2325
import org.elasticsearch.action.index.IndexRequest;
2426
import org.elasticsearch.action.search.SearchRequest;
2527
import org.elasticsearch.action.search.SearchResponse;
@@ -48,6 +50,8 @@
4850
import org.elasticsearch.client.indices.CloseIndexRequest;
4951
import org.elasticsearch.client.indices.CreateIndexRequest;
5052
import org.elasticsearch.client.indices.CreateIndexResponse;
53+
import org.elasticsearch.cluster.metadata.IndexMetadata;
54+
import org.elasticsearch.common.settings.Settings;
5155
import org.elasticsearch.common.xcontent.XContentType;
5256
import org.elasticsearch.index.seqno.ReplicationTracker;
5357
import org.elasticsearch.test.rest.yaml.ObjectPath;
@@ -61,6 +65,7 @@
6165

6266
import static org.hamcrest.Matchers.empty;
6367
import static org.hamcrest.Matchers.equalTo;
68+
import static org.hamcrest.Matchers.hasEntry;
6469
import static org.hamcrest.Matchers.hasSize;
6570
import static org.hamcrest.Matchers.is;
6671
import static org.hamcrest.Matchers.notNullValue;
@@ -80,6 +85,7 @@ public void testIndexFollowing() throws Exception {
8085
assertThat(response.isAcknowledged(), is(true));
8186

8287
PutFollowRequest putFollowRequest = new PutFollowRequest("local_cluster", "leader", "follower", ActiveShardCount.ONE);
88+
putFollowRequest.setSettings(Settings.builder().put("index.number_of_replicas", 0L).build());
8389
PutFollowResponse putFollowResponse = execute(putFollowRequest, ccrClient::putFollow, ccrClient::putFollowAsync);
8490
assertThat(putFollowResponse.isFollowIndexCreated(), is(true));
8591
assertThat(putFollowResponse.isFollowIndexShardsAcked(), is(true));
@@ -118,6 +124,13 @@ public void testIndexFollowing() throws Exception {
118124
SearchRequest followerSearchRequest = new SearchRequest("follower");
119125
SearchResponse followerSearchResponse = highLevelClient().search(followerSearchRequest, RequestOptions.DEFAULT);
120126
assertThat(followerSearchResponse.getHits().getTotalHits().value, equalTo(1L));
127+
128+
GetSettingsRequest followerSettingsRequest = new GetSettingsRequest().indices("follower");
129+
GetSettingsResponse followerSettingsResponse =
130+
highLevelClient().indices().getSettings(followerSettingsRequest, RequestOptions.DEFAULT);
131+
assertThat(
132+
IndexMetadata.INDEX_NUMBER_OF_REPLICAS_SETTING.get(followerSettingsResponse.getIndexToSettings().get("follower")),
133+
equalTo(0));
121134
});
122135
} catch (Exception e) {
123136
IndicesFollowStats followStats = ccrClient.getCcrStats(new CcrStatsRequest(), RequestOptions.DEFAULT).getIndicesFollowStats();
@@ -245,6 +258,10 @@ public void testAutoFollowing() throws Exception {
245258
PutAutoFollowPatternRequest putAutoFollowPatternRequest =
246259
new PutAutoFollowPatternRequest("pattern1", "local_cluster", Collections.singletonList("logs-*"));
247260
putAutoFollowPatternRequest.setFollowIndexNamePattern("copy-{{leader_index}}");
261+
final int followerNumberOfReplicas = randomIntBetween(0, 4);
262+
final Settings autoFollowerPatternSettings =
263+
Settings.builder().put("index.number_of_replicas", followerNumberOfReplicas).build();
264+
putAutoFollowPatternRequest.setSettings(autoFollowerPatternSettings);
248265
AcknowledgedResponse putAutoFollowPatternResponse =
249266
execute(putAutoFollowPatternRequest, ccrClient::putAutoFollowPattern, ccrClient::putAutoFollowPatternAsync);
250267
assertThat(putAutoFollowPatternResponse.isAcknowledged(), is(true));
@@ -260,6 +277,9 @@ public void testAutoFollowing() throws Exception {
260277
assertThat(ccrStatsResponse.getIndicesFollowStats().getShardFollowStats("copy-logs-20200101"), notNullValue());
261278
});
262279
assertThat(indexExists("copy-logs-20200101"), is(true));
280+
assertThat(
281+
getIndexSettingsAsMap("copy-logs-20200101"),
282+
hasEntry("index.number_of_replicas", Integer.toString(followerNumberOfReplicas)));
263283

264284
GetAutoFollowPatternRequest getAutoFollowPatternRequest =
265285
randomBoolean() ? new GetAutoFollowPatternRequest("pattern1") : new GetAutoFollowPatternRequest();
@@ -271,6 +291,7 @@ public void testAutoFollowing() throws Exception {
271291
assertThat(pattern.getRemoteCluster(), equalTo(putAutoFollowPatternRequest.getRemoteCluster()));
272292
assertThat(pattern.getLeaderIndexPatterns(), equalTo(putAutoFollowPatternRequest.getLeaderIndexPatterns()));
273293
assertThat(pattern.getFollowIndexNamePattern(), equalTo(putAutoFollowPatternRequest.getFollowIndexNamePattern()));
294+
assertThat(pattern.getSettings(), equalTo(autoFollowerPatternSettings));
274295

275296
// Cleanup:
276297
final DeleteAutoFollowPatternRequest deleteAutoFollowPatternRequest = new DeleteAutoFollowPatternRequest("pattern1");

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
package org.elasticsearch.client.ccr;
2121

2222
import org.elasticsearch.client.AbstractResponseTestCase;
23+
import org.elasticsearch.cluster.metadata.IndexMetadata;
24+
import org.elasticsearch.common.settings.Settings;
2325
import org.elasticsearch.common.unit.ByteSizeValue;
2426
import org.elasticsearch.common.unit.TimeValue;
2527
import org.elasticsearch.common.xcontent.XContentParser;
@@ -47,8 +49,10 @@ protected GetAutoFollowPatternAction.Response createServerTestInstance(XContentT
4749
NavigableMap<String, AutoFollowMetadata.AutoFollowPattern> patterns = new TreeMap<>();
4850
for (int i = 0; i < numPatterns; i++) {
4951
String remoteCluster = randomAlphaOfLength(4);
50-
List<String> leaderIndexPatters = Collections.singletonList(randomAlphaOfLength(4));
52+
List<String> leaderIndexPatterns = Collections.singletonList(randomAlphaOfLength(4));
5153
String followIndexNamePattern = randomAlphaOfLength(4);
54+
final Settings settings =
55+
Settings.builder().put(IndexMetadata.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), randomIntBetween(0, 4)).build();
5256
boolean active = randomBoolean();
5357

5458
Integer maxOutstandingReadRequests = null;
@@ -91,10 +95,26 @@ protected GetAutoFollowPatternAction.Response createServerTestInstance(XContentT
9195
if (randomBoolean()) {
9296
readPollTimeout = new TimeValue(randomNonNegativeLong());
9397
}
94-
patterns.put(randomAlphaOfLength(4), new AutoFollowMetadata.AutoFollowPattern(remoteCluster, leaderIndexPatters,
95-
followIndexNamePattern, active, maxReadRequestOperationCount, maxWriteRequestOperationCount, maxOutstandingReadRequests,
96-
maxOutstandingWriteRequests, maxReadRequestSize, maxWriteRequestSize, maxWriteBufferCount, maxWriteBufferSize,
97-
maxRetryDelay, readPollTimeout));
98+
patterns.put(
99+
randomAlphaOfLength(4),
100+
new AutoFollowMetadata.AutoFollowPattern(
101+
remoteCluster,
102+
leaderIndexPatterns,
103+
followIndexNamePattern,
104+
settings,
105+
active,
106+
maxReadRequestOperationCount,
107+
maxWriteRequestOperationCount,
108+
maxOutstandingReadRequests,
109+
maxOutstandingWriteRequests,
110+
maxReadRequestSize,
111+
maxWriteRequestSize,
112+
maxWriteBufferCount,
113+
maxWriteBufferSize,
114+
maxRetryDelay,
115+
readPollTimeout
116+
)
117+
);
98118
}
99119
return new GetAutoFollowPatternAction.Response(patterns);
100120
}
@@ -115,6 +135,7 @@ protected void assertInstances(GetAutoFollowPatternAction.Response serverTestIns
115135
assertThat(serverPattern.getRemoteCluster(), equalTo(clientPattern.getRemoteCluster()));
116136
assertThat(serverPattern.getLeaderIndexPatterns(), equalTo(clientPattern.getLeaderIndexPatterns()));
117137
assertThat(serverPattern.getFollowIndexPattern(), equalTo(clientPattern.getFollowIndexNamePattern()));
138+
assertThat(serverPattern.getSettings(), equalTo(clientPattern.getSettings()));
118139
assertThat(serverPattern.getMaxOutstandingReadRequests(), equalTo(clientPattern.getMaxOutstandingReadRequests()));
119140
assertThat(serverPattern.getMaxOutstandingWriteRequests(), equalTo(clientPattern.getMaxOutstandingWriteRequests()));
120141
assertThat(serverPattern.getMaxReadRequestOperationCount(), equalTo(clientPattern.getMaxReadRequestOperationCount()));

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.elasticsearch.client.indices.CloseIndexRequest;
5555
import org.elasticsearch.client.indices.CreateIndexRequest;
5656
import org.elasticsearch.client.indices.CreateIndexResponse;
57+
import org.elasticsearch.common.settings.Settings;
5758
import org.elasticsearch.test.rest.yaml.ObjectPath;
5859
import org.junit.Before;
5960

@@ -91,6 +92,9 @@ public void testPutFollow() throws Exception {
9192
"follower", // <3>
9293
ActiveShardCount.ONE // <4>
9394
);
95+
Settings settings =
96+
Settings.builder().put("index.number_of_replicas", 0L).build();
97+
putFollowRequest.setSettings(settings); // <5>
9498
// end::ccr-put-follow-request
9599

96100
// tag::ccr-put-follow-execute
@@ -484,6 +488,9 @@ public void testPutAutoFollowPattern() throws Exception {
484488
Arrays.asList("logs-*", "metrics-*") // <3>
485489
);
486490
request.setFollowIndexNamePattern("copy-{{leader_index}}"); // <4>
491+
Settings settings =
492+
Settings.builder().put("index.number_of_replicas", 0L).build();
493+
request.setSettings(settings); // <5>
487494
// end::ccr-put-auto-follow-pattern-request
488495

489496
// tag::ccr-put-auto-follow-pattern-execute

docs/java-rest/high-level/ccr/put_auto_follow_pattern.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include-tagged::{doc-tests-file}[{api}-request]
2222
<2> The name of the remote cluster.
2323
<3> The leader index patterns.
2424
<4> The pattern used to create the follower index
25+
<5> The settings overrides for the follower index
2526

2627
[id="{upid}-{api}-response"]
2728
==== Response

docs/java-rest/high-level/ccr/put_follow.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include-tagged::{doc-tests-file}[{api}-request]
2222
<3> The name of the follower index that gets created as part of the put follow API call.
2323
<4> The number of active shard copies to wait for before the put follow API returns a
2424
response, as an `ActiveShardCount`
25+
<5> The settings overrides for the follower index.
2526

2627
[id="{upid}-{api}-response"]
2728
==== Response

docs/reference/ccr/apis/auto-follow/put-auto-follow-pattern.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ PUT /_ccr/auto_follow/my_auto_follow_pattern
9494
"leader_index*"
9595
],
9696
"follow_index_pattern" : "{{leader_index}}-follower",
97+
"settings": {
98+
"index.number_of_replicas": 0
99+
},
97100
"max_read_request_operation_count" : 1024,
98101
"max_outstanding_read_requests" : 16,
99102
"max_read_request_size" : "1024k",

docs/reference/ccr/apis/follow-request-body.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
[testenv="platinum"]
2+
`settings`::
3+
(object) Settings to override from the leader index. Note that certain
4+
settings can not be overrode (e.g., `index.number_of_shards`).
5+
26
`max_read_request_operation_count`::
37
(integer) The maximum number of operations to pull per read from the remote
48
cluster.

docs/reference/ccr/apis/follow/put-follow.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ PUT /follower_index/_ccr/follow?wait_for_active_shards=1
9090
{
9191
"remote_cluster" : "remote_cluster",
9292
"leader_index" : "leader_index",
93+
"settings": {
94+
"index.number_of_replicas": 0
95+
},
9396
"max_read_request_operation_count" : 1024,
9497
"max_outstanding_read_requests" : 16,
9598
"max_read_request_size" : "1024k",

0 commit comments

Comments
 (0)