Skip to content

Commit 5f6fa9f

Browse files
authored
Handle indices with zero/missing uptime correctly in write-load calculation (elastic#136929) (elastic#136933)
Fixes: ES-13286 (cherry picked from commit 2e340de)
1 parent 528a68b commit 5f6fa9f

File tree

3 files changed

+87
-1
lines changed

3 files changed

+87
-1
lines changed

docs/changelog/136929.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 136929
2+
summary: Handle indices with zero/missing uptime correctly in write-load calculation
3+
area: Allocation
4+
type: bug
5+
issues: []

x-pack/plugin/write-load-forecaster/src/main/java/org/elasticsearch/xpack/writeloadforecaster/LicensedWriteLoadForecaster.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ static OptionalDouble forecastIndexWriteLoad(List<IndexWriteLoad> indicesWriteLo
160160
maxShardUptimeInMillis = Math.max(maxShardUptimeInMillis, shardUptimeInMillis);
161161
}
162162
}
163+
// An index only contributes to the weighted average proportionally to its uptime
164+
if (totalShardUptimeInMillis == 0) {
165+
continue;
166+
}
163167
double weightedAverageShardWriteLoad = totalShardWriteLoad / totalShardUptimeInMillis;
164168
double totalIndexWriteLoad = weightedAverageShardWriteLoad * writeLoad.numberOfShards();
165169
// We need to weight the contribution from each index somehow, but we only know
@@ -171,6 +175,8 @@ static OptionalDouble forecastIndexWriteLoad(List<IndexWriteLoad> indicesWriteLo
171175
// that index. It should be safe to extrapolate our weighted average out to the
172176
// maximum uptime observed, based on the assumption that write-load is roughly
173177
// evenly distributed across shards of a datastream index.
178+
assert Double.isFinite(totalIndexWriteLoad) : "Invalid total index write load: " + totalIndexWriteLoad;
179+
assert maxShardUptimeInMillis > 0 : "Invalid max shard uptime in millis: " + maxShardUptimeInMillis;
174180
allIndicesWriteLoad += totalIndexWriteLoad * maxShardUptimeInMillis;
175181
allIndicesUptime += maxShardUptimeInMillis;
176182
}

x-pack/plugin/write-load-forecaster/src/test/java/org/elasticsearch/xpack/writeloadforecaster/LicensedWriteLoadForecasterTests.java

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,18 @@
3030
import org.junit.Before;
3131

3232
import java.util.ArrayList;
33+
import java.util.HashSet;
3334
import java.util.List;
3435
import java.util.Map;
3536
import java.util.Objects;
3637
import java.util.OptionalDouble;
38+
import java.util.Set;
3739
import java.util.concurrent.TimeUnit;
3840
import java.util.concurrent.atomic.AtomicBoolean;
3941
import java.util.concurrent.atomic.AtomicInteger;
42+
import java.util.function.LongSupplier;
4043
import java.util.stream.Collectors;
44+
import java.util.stream.IntStream;
4145

4246
import static org.elasticsearch.xpack.writeloadforecaster.LicensedWriteLoadForecaster.forecastIndexWriteLoad;
4347
import static org.hamcrest.Matchers.closeTo;
@@ -47,6 +51,8 @@
4751
import static org.hamcrest.Matchers.greaterThan;
4852
import static org.hamcrest.Matchers.is;
4953
import static org.hamcrest.Matchers.lessThan;
54+
import static org.hamcrest.Matchers.not;
55+
import static org.hamcrest.Matchers.notANumber;
5056

5157
public class LicensedWriteLoadForecasterTests extends ESTestCase {
5258
ThreadPool threadPool;
@@ -294,14 +300,18 @@ public void testWriteLoadForecast() {
294300
}
295301

296302
private IndexWriteLoad randomIndexWriteLoad(int numberOfShards) {
303+
return randomIndexWriteLoad(numberOfShards, () -> randomLongBetween(1, 10));
304+
}
305+
306+
private IndexWriteLoad randomIndexWriteLoad(int numberOfShards, LongSupplier uptimeSupplier) {
297307
IndexWriteLoad.Builder builder = IndexWriteLoad.builder(numberOfShards);
298308
for (int shardId = 0; shardId < numberOfShards; shardId++) {
299309
builder.withShardWriteLoad(
300310
shardId,
301311
randomDoubleBetween(0, 64, true),
302312
randomDoubleBetween(0, 64, true),
303313
randomDoubleBetween(0, 64, true),
304-
randomLongBetween(1, 10)
314+
uptimeSupplier.getAsLong()
305315
);
306316
}
307317
return builder.build();
@@ -445,6 +455,71 @@ private void testShardChangeDoesNotChangeTotalForecastLoad(ShardCountChange shar
445455
);
446456
}
447457

458+
public void testCanHandleIndicesWithMissingShardWriteLoadsOrZeroUptime() {
459+
final TimeValue maxIndexAge = TimeValue.timeValueDays(7);
460+
final WriteLoadForecaster writeLoadForecaster = new LicensedWriteLoadForecaster(() -> true, threadPool, maxIndexAge);
461+
writeLoadForecaster.refreshLicense();
462+
463+
final ProjectMetadata.Builder metadataBuilder = ProjectMetadata.builder(randomProjectIdOrDefault());
464+
final String dataStreamName = "logs-es";
465+
final int numberOfBackingIndices = 10;
466+
final int numberOfShards = randomIntBetween(1, 5);
467+
final int numberOfIndicesWithZeroUptime = randomBoolean()
468+
? numberOfBackingIndices
469+
: randomIntBetween(1, numberOfBackingIndices - 1);
470+
final Set<Integer> indicesWithZeroUptime = new HashSet<>(
471+
randomSubsetOf(numberOfIndicesWithZeroUptime, IntStream.range(0, numberOfBackingIndices).boxed().collect(Collectors.toSet()))
472+
);
473+
logger.info(
474+
"--> indices with zero uptime: {}/{} ({})",
475+
numberOfIndicesWithZeroUptime,
476+
numberOfBackingIndices,
477+
indicesWithZeroUptime
478+
);
479+
final List<Index> backingIndices = new ArrayList<>();
480+
for (int i = 0; i < numberOfBackingIndices; i++) {
481+
final IndexMetadata indexMetadata = createIndexMetadata(
482+
DataStream.getDefaultBackingIndexName(dataStreamName, i),
483+
numberOfShards,
484+
indicesWithZeroUptime.contains(i)
485+
? indexWriteLoadWithMissingOrZeroUptime(numberOfShards)
486+
: randomIndexWriteLoad(numberOfShards),
487+
System.currentTimeMillis() - (maxIndexAge.millis() / 2)
488+
);
489+
backingIndices.add(indexMetadata.getIndex());
490+
metadataBuilder.put(indexMetadata, false);
491+
}
492+
493+
final IndexMetadata writeIndexMetadata = createIndexMetadata(
494+
DataStream.getDefaultBackingIndexName(dataStreamName, numberOfBackingIndices),
495+
numberOfShards,
496+
null,
497+
System.currentTimeMillis()
498+
);
499+
backingIndices.add(writeIndexMetadata.getIndex());
500+
metadataBuilder.put(writeIndexMetadata, false);
501+
502+
final DataStream dataStream = createDataStream(dataStreamName, backingIndices);
503+
metadataBuilder.put(dataStream);
504+
505+
final ProjectMetadata.Builder updatedMetadataBuilder = writeLoadForecaster.withWriteLoadForecastForWriteIndex(
506+
dataStream.getName(),
507+
metadataBuilder
508+
);
509+
final IndexMetadata writeIndex = updatedMetadataBuilder.getSafe(dataStream.getWriteIndex());
510+
final OptionalDouble forecastedWriteLoad = writeLoadForecaster.getForecastedWriteLoad(writeIndex);
511+
512+
final boolean someIndicesHadUptime = numberOfIndicesWithZeroUptime != numberOfBackingIndices;
513+
assertThat(forecastedWriteLoad.isPresent(), is(someIndicesHadUptime));
514+
if (someIndicesHadUptime) {
515+
assertThat(forecastedWriteLoad.getAsDouble(), not(notANumber()));
516+
}
517+
}
518+
519+
private IndexWriteLoad indexWriteLoadWithMissingOrZeroUptime(int numShards) {
520+
return randomBoolean() ? IndexWriteLoad.builder(numShards).build() : randomIndexWriteLoad(numShards, () -> 0);
521+
}
522+
448523
public enum ShardCountChange implements IntToIntFunction {
449524
INCREASE(1, 15) {
450525
@Override

0 commit comments

Comments
 (0)