Skip to content

Commit d768bf0

Browse files
committed
update rollover to leverage write-alias semantics (#32216)
Rollover should not swap aliases when `is_write_index` is set to `true`. Instead, both the new and old indices should have the rollover alias, with the newly created index as the new write index Updates Rollover to leverage the ability to preserve aliases and swap which is the write index. Historically, Rollover would swap which index had the designated alias for writing documents against. This required users to keep a separate read-alias that enabled reading against both rolled over and newly created indices, whiles the write-alias was being re-assigned at every rollover. With the ability for aliases to designate a write index, Rollover can be a bit more flexible with its use of aliases. Updates include: - Rollover validates that the target alias has a write index (the index that is being rolled over). This means that the restriction that aliases only point to one index is no longer necessary. - Rollover explicitly (and atomically) swaps which index is the write-index by explicitly assigning the existing index to have `is_write_index: false` and have the newly created index have its rollover alias as `is_write_index: true`. This is only done when `is_write_index: true` on the write index. Default behavior of removing the alias from the rolled over index stays when `is_write_index` is not explicitly set Relevant things that are staying the same: - Rollover is rejected if there exist any templates that match the newly-created index and configure the rollover-alias - I think this existed to prevent the situation where an alias pointed to two indices for a short while. Although this can technically be relaxed, the specific cases that are safe are really particular and difficult to reason, so leaving the broad restriction sounds good
1 parent f628495 commit d768bf0

File tree

5 files changed

+274
-37
lines changed

5 files changed

+274
-37
lines changed

docs/reference/indices/aliases.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ and there are multiple indices referenced by an alias, then writes will not be a
257257
It is possible to specify an index associated with an alias as a write index using both the aliases API
258258
and index creation API.
259259

260+
Setting an index to be the write index with an alias also affects how the alias is manipulated during
261+
Rollover (see <<indices-rollover-index, Rollover With Write Index>>).
262+
260263
[source,js]
261264
--------------------------------------------------
262265
POST /_aliases

docs/reference/indices/rollover-index.asciidoc

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@
44
The rollover index API rolls an alias over to a new index when the existing
55
index is considered to be too large or too old.
66

7-
The API accepts a single alias name and a list of `conditions`. The alias
8-
must point to a single index only. If the index satisfies the specified
9-
conditions then a new index is created and the alias is switched to point to
10-
the new index.
7+
The API accepts a single alias name and a list of `conditions`. The alias must point to a write index for
8+
a Rollover request to be valid. There are two ways this can be achieved, and depending on the configuration, the
9+
alias metadata will be updated differently. The two scenarios are as follows:
10+
11+
- The alias only points to a single index with `is_write_index` not configured (defaults to `null`).
12+
13+
In this scenario, the original index will have their rollover alias will be added to the newly created index, and removed
14+
from the original (rolled-over) index.
15+
16+
- The alias points to one or more indices with `is_write_index` set to `true` on the index to be rolled over (the write index).
17+
18+
In this scenario, the write index will have its rollover alias' `is_write_index` set to `false`, while the newly created index
19+
will now have the rollover alias pointing to it as the write index with `is_write_index` as `true`.
1120

1221

1322
[source,js]
@@ -231,3 +240,98 @@ POST /logs_write/_rollover?dry_run
231240
Because the rollover operation creates a new index to rollover to, the
232241
<<create-index-wait-for-active-shards,`wait_for_active_shards`>> setting on
233242
index creation applies to the rollover action as well.
243+
244+
[[indices-rollover-is-write-index]]
245+
[float]
246+
=== Write Index Alias Behavior
247+
248+
The rollover alias when rolling over a write index that has `is_write_index` explicitly set to `true` is not
249+
swapped during rollover actions. Since having an alias point to multiple indices is ambiguous in distinguishing
250+
which is the correct write index to roll over, it is not valid to rollover an alias that points to multiple indices.
251+
For this reason, the default behavior is to swap which index is being pointed to by the write-oriented alias. This
252+
was `logs_write` in some of the above examples. Since setting `is_write_index` enables an alias to point to multiple indices
253+
while also being explicit as to which is the write index that rollover should target, removing the alias from the rolled over
254+
index is not necessary. This simplifies things by allowing for one alias to behave both as the write and read aliases for
255+
indices that are being managed with Rollover.
256+
257+
Look at the behavior of the aliases in the following example where `is_write_index` is set on the rolled over index.
258+
259+
[source,js]
260+
--------------------------------------------------
261+
PUT my_logs_index-000001
262+
{
263+
"aliases": {
264+
"logs": { "is_write_index": true } <1>
265+
}
266+
}
267+
268+
PUT logs/_doc/1
269+
{
270+
"message": "a dummy log"
271+
}
272+
273+
POST logs/_refresh
274+
275+
POST /logs/_rollover
276+
{
277+
"conditions": {
278+
"max_docs": "1"
279+
}
280+
}
281+
282+
PUT logs/_doc/2 <2>
283+
{
284+
"message": "a newer log"
285+
}
286+
--------------------------------------------------
287+
// CONSOLE
288+
<1> configures `my_logs_index` as the write index for the `logs` alias
289+
<2> newly indexed documents against the `logs` alias will write to the new index
290+
291+
[source,js]
292+
--------------------------------------------------
293+
{
294+
"_index" : "my_logs_index-000002",
295+
"_type" : "_doc",
296+
"_id" : "2",
297+
"_version" : 1,
298+
"result" : "created",
299+
"_shards" : {
300+
"total" : 2,
301+
"successful" : 1,
302+
"failed" : 0
303+
},
304+
"_seq_no" : 0,
305+
"_primary_term" : 1
306+
}
307+
--------------------------------------------------
308+
// TESTRESPONSE
309+
310+
//////////////////////////
311+
[source,js]
312+
--------------------------------------------------
313+
GET _alias
314+
--------------------------------------------------
315+
// CONSOLE
316+
// TEST[continued]
317+
//////////////////////////
318+
319+
After the rollover, the alias metadata for the two indices will have the `is_write_index` setting
320+
reflect each index's role, with the newly created index as the write index.
321+
322+
[source,js]
323+
--------------------------------------------------
324+
{
325+
"my_logs_index-000002": {
326+
"aliases": {
327+
"logs": { "is_write_index": true }
328+
}
329+
},
330+
"my_logs_index-000001": {
331+
"aliases": {
332+
"logs": { "is_write_index" : false }
333+
}
334+
}
335+
}
336+
--------------------------------------------------
337+
// TESTRESPONSE

server/src/main/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverAction.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ protected void masterOperation(final RolloverRequest rolloverRequest, final Clus
109109
final ActionListener<RolloverResponse> listener) {
110110
final MetaData metaData = state.metaData();
111111
validate(metaData, rolloverRequest);
112-
final AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(rolloverRequest.getAlias());
113-
final IndexMetaData indexMetaData = aliasOrIndex.getIndices().get(0);
112+
final AliasOrIndex.Alias alias = (AliasOrIndex.Alias) metaData.getAliasAndIndexLookup().get(rolloverRequest.getAlias());
113+
final IndexMetaData indexMetaData = alias.getWriteIndex();
114+
final boolean explicitWriteIndex = Boolean.TRUE.equals(indexMetaData.getAliases().get(alias.getAliasName()).writeIndex());
114115
final String sourceProvidedName = indexMetaData.getSettings().get(IndexMetaData.SETTING_INDEX_PROVIDED_NAME,
115116
indexMetaData.getIndex().getName());
116117
final String sourceIndexName = indexMetaData.getIndex().getName();
@@ -138,10 +139,15 @@ public void onResponse(IndicesStatsResponse statsResponse) {
138139
CreateIndexClusterStateUpdateRequest updateRequest = prepareCreateIndexRequest(unresolvedName, rolloverIndexName,
139140
rolloverRequest);
140141
createIndexService.createIndex(updateRequest, ActionListener.wrap(createIndexClusterStateUpdateResponse -> {
141-
// switch the alias to point to the newly created index
142-
indexAliasesService.indicesAliases(
143-
prepareRolloverAliasesUpdateRequest(sourceIndexName, rolloverIndexName,
144-
rolloverRequest),
142+
final IndicesAliasesClusterStateUpdateRequest aliasesUpdateRequest;
143+
if (explicitWriteIndex) {
144+
aliasesUpdateRequest = prepareRolloverAliasesWriteIndexUpdateRequest(sourceIndexName,
145+
rolloverIndexName, rolloverRequest);
146+
} else {
147+
aliasesUpdateRequest = prepareRolloverAliasesUpdateRequest(sourceIndexName,
148+
rolloverIndexName, rolloverRequest);
149+
}
150+
indexAliasesService.indicesAliases(aliasesUpdateRequest,
145151
ActionListener.wrap(aliasClusterStateUpdateResponse -> {
146152
if (aliasClusterStateUpdateResponse.isAcknowledged()) {
147153
clusterService.submitStateUpdateTask("update_rollover_info", new ClusterStateUpdateTask() {
@@ -196,8 +202,19 @@ public void onFailure(Exception e) {
196202
static IndicesAliasesClusterStateUpdateRequest prepareRolloverAliasesUpdateRequest(String oldIndex, String newIndex,
197203
RolloverRequest request) {
198204
List<AliasAction> actions = unmodifiableList(Arrays.asList(
199-
new AliasAction.Add(newIndex, request.getAlias(), null, null, null, null),
200-
new AliasAction.Remove(oldIndex, request.getAlias())));
205+
new AliasAction.Add(newIndex, request.getAlias(), null, null, null, null),
206+
new AliasAction.Remove(oldIndex, request.getAlias())));
207+
final IndicesAliasesClusterStateUpdateRequest updateRequest = new IndicesAliasesClusterStateUpdateRequest(actions)
208+
.ackTimeout(request.ackTimeout())
209+
.masterNodeTimeout(request.masterNodeTimeout());
210+
return updateRequest;
211+
}
212+
213+
static IndicesAliasesClusterStateUpdateRequest prepareRolloverAliasesWriteIndexUpdateRequest(String oldIndex, String newIndex,
214+
RolloverRequest request) {
215+
List<AliasAction> actions = unmodifiableList(Arrays.asList(
216+
new AliasAction.Add(newIndex, request.getAlias(), null, null, null, true),
217+
new AliasAction.Add(oldIndex, request.getAlias(), null, null, null, false)));
201218
final IndicesAliasesClusterStateUpdateRequest updateRequest = new IndicesAliasesClusterStateUpdateRequest(actions)
202219
.ackTimeout(request.ackTimeout())
203220
.masterNodeTimeout(request.masterNodeTimeout());
@@ -244,8 +261,9 @@ static void validate(MetaData metaData, RolloverRequest request) {
244261
if (aliasOrIndex.isAlias() == false) {
245262
throw new IllegalArgumentException("source alias is a concrete index");
246263
}
247-
if (aliasOrIndex.getIndices().size() != 1) {
248-
throw new IllegalArgumentException("source alias maps to multiple indices");
264+
final AliasOrIndex.Alias alias = (AliasOrIndex.Alias) aliasOrIndex;
265+
if (alias.getWriteIndex() == null) {
266+
throw new IllegalArgumentException("source alias [" + alias.getAliasName() + "] does not point to a write index");
249267
}
250268
}
251269

server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverIT.java

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ protected Collection<Class<? extends Plugin>> nodePlugins() {
6060

6161

6262
public void testRolloverOnEmptyIndex() throws Exception {
63-
assertAcked(prepareCreate("test_index-1").addAlias(new Alias("test_alias")).get());
63+
Alias testAlias = new Alias("test_alias");
64+
boolean explicitWriteIndex = randomBoolean();
65+
if (explicitWriteIndex) {
66+
testAlias.writeIndex(true);
67+
}
68+
assertAcked(prepareCreate("test_index-1").addAlias(testAlias).get());
6469
final RolloverResponse response = client().admin().indices().prepareRolloverIndex("test_alias").get();
6570
assertThat(response.getOldIndex(), equalTo("test_index-1"));
6671
assertThat(response.getNewIndex(), equalTo("test_index-000002"));
@@ -69,7 +74,12 @@ public void testRolloverOnEmptyIndex() throws Exception {
6974
assertThat(response.getConditionStatus().size(), equalTo(0));
7075
final ClusterState state = client().admin().cluster().prepareState().get().getState();
7176
final IndexMetaData oldIndex = state.metaData().index("test_index-1");
72-
assertFalse(oldIndex.getAliases().containsKey("test_alias"));
77+
if (explicitWriteIndex) {
78+
assertTrue(oldIndex.getAliases().containsKey("test_alias"));
79+
assertFalse(oldIndex.getAliases().get("test_alias").writeIndex());
80+
} else {
81+
assertFalse(oldIndex.getAliases().containsKey("test_alias"));
82+
}
7383
final IndexMetaData newIndex = state.metaData().index("test_index-000002");
7484
assertTrue(newIndex.getAliases().containsKey("test_alias"));
7585
}
@@ -97,8 +107,49 @@ public void testRollover() throws Exception {
97107
is(both(greaterThanOrEqualTo(beforeTime)).and(lessThanOrEqualTo(client().threadPool().absoluteTimeInMillis() + 1000L))));
98108
}
99109

110+
public void testRolloverWithExplicitWriteIndex() throws Exception {
111+
long beforeTime = client().threadPool().absoluteTimeInMillis() - 1000L;
112+
assertAcked(prepareCreate("test_index-2").addAlias(new Alias("test_alias").writeIndex(true)).get());
113+
index("test_index-2", "type1", "1", "field", "value");
114+
flush("test_index-2");
115+
final RolloverResponse response = client().admin().indices().prepareRolloverIndex("test_alias").get();
116+
assertThat(response.getOldIndex(), equalTo("test_index-2"));
117+
assertThat(response.getNewIndex(), equalTo("test_index-000003"));
118+
assertThat(response.isDryRun(), equalTo(false));
119+
assertThat(response.isRolledOver(), equalTo(true));
120+
assertThat(response.getConditionStatus().size(), equalTo(0));
121+
final ClusterState state = client().admin().cluster().prepareState().get().getState();
122+
final IndexMetaData oldIndex = state.metaData().index("test_index-2");
123+
assertTrue(oldIndex.getAliases().containsKey("test_alias"));
124+
assertFalse(oldIndex.getAliases().get("test_alias").writeIndex());
125+
final IndexMetaData newIndex = state.metaData().index("test_index-000003");
126+
assertTrue(newIndex.getAliases().containsKey("test_alias"));
127+
assertTrue(newIndex.getAliases().get("test_alias").writeIndex());
128+
assertThat(oldIndex.getRolloverInfos().size(), equalTo(1));
129+
assertThat(oldIndex.getRolloverInfos().get("test_alias").getAlias(), equalTo("test_alias"));
130+
assertThat(oldIndex.getRolloverInfos().get("test_alias").getMetConditions(), is(empty()));
131+
assertThat(oldIndex.getRolloverInfos().get("test_alias").getTime(),
132+
is(both(greaterThanOrEqualTo(beforeTime)).and(lessThanOrEqualTo(client().threadPool().absoluteTimeInMillis() + 1000L))));
133+
}
134+
135+
public void testRolloverWithNoWriteIndex() {
136+
Boolean firstIsWriteIndex = randomFrom(false, null);
137+
assertAcked(prepareCreate("index1").addAlias(new Alias("alias").writeIndex(firstIsWriteIndex)).get());
138+
if (firstIsWriteIndex == null) {
139+
assertAcked(prepareCreate("index2").addAlias(new Alias("alias").writeIndex(randomFrom(false, null))).get());
140+
}
141+
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class,
142+
() -> client().admin().indices().prepareRolloverIndex("alias").dryRun(randomBoolean()).get());
143+
assertThat(exception.getMessage(), equalTo("source alias [alias] does not point to a write index"));
144+
}
145+
100146
public void testRolloverWithIndexSettings() throws Exception {
101-
assertAcked(prepareCreate("test_index-2").addAlias(new Alias("test_alias")).get());
147+
Alias testAlias = new Alias("test_alias");
148+
boolean explicitWriteIndex = randomBoolean();
149+
if (explicitWriteIndex) {
150+
testAlias.writeIndex(true);
151+
}
152+
assertAcked(prepareCreate("test_index-2").addAlias(testAlias).get());
102153
index("test_index-2", "type1", "1", "field", "value");
103154
flush("test_index-2");
104155
final Settings settings = Settings.builder()
@@ -114,12 +165,17 @@ public void testRolloverWithIndexSettings() throws Exception {
114165
assertThat(response.getConditionStatus().size(), equalTo(0));
115166
final ClusterState state = client().admin().cluster().prepareState().get().getState();
116167
final IndexMetaData oldIndex = state.metaData().index("test_index-2");
117-
assertFalse(oldIndex.getAliases().containsKey("test_alias"));
118168
final IndexMetaData newIndex = state.metaData().index("test_index-000003");
119169
assertThat(newIndex.getNumberOfShards(), equalTo(1));
120170
assertThat(newIndex.getNumberOfReplicas(), equalTo(0));
121171
assertTrue(newIndex.getAliases().containsKey("test_alias"));
122172
assertTrue(newIndex.getAliases().containsKey("extra_alias"));
173+
if (explicitWriteIndex) {
174+
assertFalse(oldIndex.getAliases().get("test_alias").writeIndex());
175+
assertTrue(newIndex.getAliases().get("test_alias").writeIndex());
176+
} else {
177+
assertFalse(oldIndex.getAliases().containsKey("test_alias"));
178+
}
123179
}
124180

125181
public void testRolloverDryRun() throws Exception {
@@ -140,7 +196,12 @@ public void testRolloverDryRun() throws Exception {
140196
}
141197

142198
public void testRolloverConditionsNotMet() throws Exception {
143-
assertAcked(prepareCreate("test_index-0").addAlias(new Alias("test_alias")).get());
199+
boolean explicitWriteIndex = randomBoolean();
200+
Alias testAlias = new Alias("test_alias");
201+
if (explicitWriteIndex) {
202+
testAlias.writeIndex(true);
203+
}
204+
assertAcked(prepareCreate("test_index-0").addAlias(testAlias).get());
144205
index("test_index-0", "type1", "1", "field", "value");
145206
flush("test_index-0");
146207
final RolloverResponse response = client().admin().indices().prepareRolloverIndex("test_alias")
@@ -160,12 +221,22 @@ public void testRolloverConditionsNotMet() throws Exception {
160221
final ClusterState state = client().admin().cluster().prepareState().get().getState();
161222
final IndexMetaData oldIndex = state.metaData().index("test_index-0");
162223
assertTrue(oldIndex.getAliases().containsKey("test_alias"));
224+
if (explicitWriteIndex) {
225+
assertTrue(oldIndex.getAliases().get("test_alias").writeIndex());
226+
} else {
227+
assertNull(oldIndex.getAliases().get("test_alias").writeIndex());
228+
}
163229
final IndexMetaData newIndex = state.metaData().index("test_index-000001");
164230
assertNull(newIndex);
165231
}
166232

167233
public void testRolloverWithNewIndexName() throws Exception {
168-
assertAcked(prepareCreate("test_index").addAlias(new Alias("test_alias")).get());
234+
Alias testAlias = new Alias("test_alias");
235+
boolean explicitWriteIndex = randomBoolean();
236+
if (explicitWriteIndex) {
237+
testAlias.writeIndex(true);
238+
}
239+
assertAcked(prepareCreate("test_index").addAlias(testAlias).get());
169240
index("test_index", "type1", "1", "field", "value");
170241
flush("test_index");
171242
final RolloverResponse response = client().admin().indices().prepareRolloverIndex("test_alias")
@@ -177,9 +248,14 @@ public void testRolloverWithNewIndexName() throws Exception {
177248
assertThat(response.getConditionStatus().size(), equalTo(0));
178249
final ClusterState state = client().admin().cluster().prepareState().get().getState();
179250
final IndexMetaData oldIndex = state.metaData().index("test_index");
180-
assertFalse(oldIndex.getAliases().containsKey("test_alias"));
181251
final IndexMetaData newIndex = state.metaData().index("test_new_index");
182252
assertTrue(newIndex.getAliases().containsKey("test_alias"));
253+
if (explicitWriteIndex) {
254+
assertFalse(oldIndex.getAliases().get("test_alias").writeIndex());
255+
assertTrue(newIndex.getAliases().get("test_alias").writeIndex());
256+
} else {
257+
assertFalse(oldIndex.getAliases().containsKey("test_alias"));
258+
}
183259
}
184260

185261
public void testRolloverOnExistingIndex() throws Exception {

0 commit comments

Comments
 (0)