|
@@ -24,9 +24,9 @@ import org.elasticsearch.common.Rounding;
|
|
|
import org.elasticsearch.common.Strings;
|
|
|
import org.elasticsearch.common.unit.ByteSizeUnit;
|
|
|
import org.elasticsearch.common.unit.ByteSizeValue;
|
|
|
-import org.elasticsearch.common.util.ArrayUtils;
|
|
|
import org.elasticsearch.core.IOUtils;
|
|
|
import org.elasticsearch.core.TimeValue;
|
|
|
+import org.elasticsearch.core.Tuple;
|
|
|
import org.elasticsearch.index.IndexService;
|
|
|
import org.elasticsearch.index.engine.Engine;
|
|
|
import org.elasticsearch.index.fielddata.FormattedDocValues;
|
|
@@ -53,15 +53,15 @@ import org.elasticsearch.xpack.core.rollup.action.RollupShardTask;
|
|
|
|
|
|
import java.io.Closeable;
|
|
|
import java.io.IOException;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.Collections;
|
|
|
-import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
-import java.util.function.Function;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
+import static java.util.stream.Collectors.groupingBy;
|
|
|
import static org.elasticsearch.core.Strings.format;
|
|
|
|
|
|
/**
|
|
@@ -90,8 +90,7 @@ class RollupShardIndexer {
|
|
|
private final String[] dimensionFields;
|
|
|
private final String[] metricFields;
|
|
|
private final String[] labelFields;
|
|
|
- private final Map<String, FieldValueFetcher> fieldValueFetchers;
|
|
|
-
|
|
|
+ private final List<FieldValueFetcher> fieldValueFetchers;
|
|
|
private final RollupShardTask task;
|
|
|
private volatile boolean abort = false;
|
|
|
|
|
@@ -128,7 +127,11 @@ class RollupShardIndexer {
|
|
|
this.timestampField = searchExecutionContext.getFieldType(DataStreamTimestampFieldMapper.DEFAULT_PATH);
|
|
|
this.timestampFormat = timestampField.docValueFormat(null, null);
|
|
|
this.rounding = config.createRounding();
|
|
|
- this.fieldValueFetchers = FieldValueFetcher.create(searchExecutionContext, ArrayUtils.concat(metricFields, labelFields));
|
|
|
+
|
|
|
+ List<FieldValueFetcher> fetchers = new ArrayList<>(metricFields.length + labelFields.length);
|
|
|
+ fetchers.addAll(FieldValueFetcher.create(searchExecutionContext, metricFields));
|
|
|
+ fetchers.addAll(FieldValueFetcher.create(searchExecutionContext, labelFields));
|
|
|
+ this.fieldValueFetchers = Collections.unmodifiableList(fetchers);
|
|
|
toClose = null;
|
|
|
} finally {
|
|
|
IOUtils.closeWhileHandlingException(toClose);
|
|
@@ -239,14 +242,18 @@ class RollupShardIndexer {
|
|
|
|
|
|
private class TimeSeriesBucketCollector extends BucketCollector {
|
|
|
private final BulkProcessor bulkProcessor;
|
|
|
+ private final RollupBucketBuilder rollupBucketBuilder;
|
|
|
private long docsProcessed;
|
|
|
private long bucketsCreated;
|
|
|
- private final RollupBucketBuilder rollupBucketBuilder = new RollupBucketBuilder();
|
|
|
long lastTimestamp = Long.MAX_VALUE;
|
|
|
long lastHistoTimestamp = Long.MAX_VALUE;
|
|
|
|
|
|
TimeSeriesBucketCollector(BulkProcessor bulkProcessor) {
|
|
|
this.bulkProcessor = bulkProcessor;
|
|
|
+ List<AbstractRollupFieldProducer> rollupFieldProducers = fieldValueFetchers.stream()
|
|
|
+ .map(FieldValueFetcher::rollupFieldProducer)
|
|
|
+ .toList();
|
|
|
+ this.rollupBucketBuilder = new RollupBucketBuilder(rollupFieldProducers);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -254,7 +261,11 @@ class RollupShardIndexer {
|
|
|
final LeafReaderContext ctx = aggCtx.getLeafReaderContext();
|
|
|
final DocCountProvider docCountProvider = new DocCountProvider();
|
|
|
docCountProvider.setLeafReaderContext(ctx);
|
|
|
- final Map<String, FormattedDocValues> docValuesFetchers = FieldValueFetcher.docValuesFetchers(ctx, fieldValueFetchers);
|
|
|
+
|
|
|
+ // For each field, return a tuple with the rollup field producer and the field value leaf
|
|
|
+ final List<Tuple<AbstractRollupFieldProducer, FormattedDocValues>> fieldValueTuples = fieldValueFetchers.stream()
|
|
|
+ .map(fetcher -> Tuple.tuple(fetcher.rollupFieldProducer(), fetcher.getLeaf(ctx)))
|
|
|
+ .toList();
|
|
|
|
|
|
return new LeafBucketCollector() {
|
|
|
@Override
|
|
@@ -316,29 +327,16 @@ class RollupShardIndexer {
|
|
|
} else {
|
|
|
rollupBucketBuilder.resetTimestamp(lastHistoTimestamp);
|
|
|
}
|
|
|
-
|
|
|
bucketsCreated++;
|
|
|
}
|
|
|
|
|
|
final int docCount = docCountProvider.getDocCount(docId);
|
|
|
rollupBucketBuilder.collectDocCount(docCount);
|
|
|
- for (Map.Entry<String, FormattedDocValues> e : docValuesFetchers.entrySet()) {
|
|
|
- final String fieldName = e.getKey();
|
|
|
- final FormattedDocValues leafField = e.getValue();
|
|
|
-
|
|
|
- if (leafField.advanceExact(docId)) {
|
|
|
- rollupBucketBuilder.collect(fieldName, leafField.docValueCount(), docValueCount -> {
|
|
|
- final Object[] values = new Object[docValueCount];
|
|
|
- for (int i = 0; i < docValueCount; ++i) {
|
|
|
- try {
|
|
|
- values[i] = leafField.nextValue();
|
|
|
- } catch (IOException ex) {
|
|
|
- throw new ElasticsearchException("Failed to read values for field [" + fieldName + "]");
|
|
|
- }
|
|
|
- }
|
|
|
- return values;
|
|
|
- });
|
|
|
- }
|
|
|
+ // Iterate over all field values and collect the doc_values for this docId
|
|
|
+ for (Tuple<AbstractRollupFieldProducer, FormattedDocValues> tuple : fieldValueTuples) {
|
|
|
+ AbstractRollupFieldProducer rollupFieldProducer = tuple.v1();
|
|
|
+ FormattedDocValues docValues = tuple.v2();
|
|
|
+ rollupFieldProducer.collect(docValues, docId);
|
|
|
}
|
|
|
docsProcessed++;
|
|
|
}
|
|
@@ -386,12 +384,10 @@ class RollupShardIndexer {
|
|
|
private int tsidOrd = -1;
|
|
|
private long timestamp;
|
|
|
private int docCount;
|
|
|
- private final Map<String, MetricFieldProducer> metricFieldProducers;
|
|
|
- private final Map<String, LabelFieldProducer> labelFieldProducers;
|
|
|
+ private final List<AbstractRollupFieldProducer> rollupFieldProducers;
|
|
|
|
|
|
- RollupBucketBuilder() {
|
|
|
- this.metricFieldProducers = MetricFieldProducer.createMetricFieldProducers(searchExecutionContext, metricFields);
|
|
|
- this.labelFieldProducers = LabelFieldProducer.createLabelFieldProducers(searchExecutionContext, labelFields);
|
|
|
+ RollupBucketBuilder(List<AbstractRollupFieldProducer> rollupFieldProducers) {
|
|
|
+ this.rollupFieldProducers = rollupFieldProducers;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -409,8 +405,7 @@ class RollupShardIndexer {
|
|
|
public RollupBucketBuilder resetTimestamp(long timestamp) {
|
|
|
this.timestamp = timestamp;
|
|
|
this.docCount = 0;
|
|
|
- this.metricFieldProducers.values().forEach(MetricFieldProducer::reset);
|
|
|
- this.labelFieldProducers.values().forEach(LabelFieldProducer::reset);
|
|
|
+ this.rollupFieldProducers.forEach(AbstractRollupFieldProducer::reset);
|
|
|
if (logger.isTraceEnabled()) {
|
|
|
logger.trace(
|
|
|
"New bucket for _tsid: [{}], @timestamp: [{}]",
|
|
@@ -421,46 +416,6 @@ class RollupShardIndexer {
|
|
|
return this;
|
|
|
}
|
|
|
|
|
|
- public void collect(final String field, int docValueCount, final Function<Integer, Object[]> fieldValues) {
|
|
|
- final Object[] values = fieldValues.apply(docValueCount);
|
|
|
- if (metricFieldProducers.containsKey(field)) {
|
|
|
- // TODO: missing support for array metrics
|
|
|
- collectMetric(field, values);
|
|
|
- } else if (labelFieldProducers.containsKey(field)) {
|
|
|
- if (values.length == 1) {
|
|
|
- collectLabel(field, values[0]);
|
|
|
- } else {
|
|
|
- collectLabel(field, values);
|
|
|
- }
|
|
|
- } else {
|
|
|
- throw new IllegalArgumentException(
|
|
|
- "Field '"
|
|
|
- + field
|
|
|
- + "' is not a label nor a metric, existing labels: [ "
|
|
|
- + String.join(",", labelFieldProducers.keySet())
|
|
|
- + "], existing metrics: ["
|
|
|
- + String.join(", ", metricFieldProducers.keySet())
|
|
|
- + "]"
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void collectLabel(final String field, final Object value) {
|
|
|
- labelFieldProducers.get(field).collect(field, value);
|
|
|
- }
|
|
|
-
|
|
|
- private void collectMetric(final String field, final Object[] values) {
|
|
|
- for (Object value : values) {
|
|
|
- if (value instanceof Number number) {
|
|
|
- metricFieldProducers.get(field).collect(field, number);
|
|
|
- } else {
|
|
|
- throw new IllegalArgumentException(
|
|
|
- "Expected numeric value for field '" + field + "' but got non numeric value: '" + value + "'"
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
public void collectDocCount(int docCount) {
|
|
|
this.docCount += docCount;
|
|
|
}
|
|
@@ -483,14 +438,28 @@ class RollupShardIndexer {
|
|
|
builder.field(e.getKey(), e.getValue());
|
|
|
}
|
|
|
|
|
|
- // Serialize all metric fields
|
|
|
- for (var producer : new HashSet<>(metricFieldProducers.values())) {
|
|
|
- producer.write(builder);
|
|
|
- }
|
|
|
+ /*
|
|
|
+ * The rollup field producers for aggregate_metric_double all share the same name (this is
|
|
|
+ * the name they will be serialized in the target index). We group all field producers by
|
|
|
+ * name. If grouping yields multiple rollup field producers, we delegate serialization to
|
|
|
+ * the AggregateMetricFieldSerializer class.
|
|
|
+ */
|
|
|
+ List<RollupFieldSerializer> groupedProducers = rollupFieldProducers.stream()
|
|
|
+ .collect(groupingBy(AbstractRollupFieldProducer::name))
|
|
|
+ .entrySet()
|
|
|
+ .stream()
|
|
|
+ .map(e -> {
|
|
|
+ if (e.getValue().size() == 1) {
|
|
|
+ return e.getValue().get(0);
|
|
|
+ } else {
|
|
|
+ return new AggregateMetricFieldSerializer(e.getKey(), e.getValue());
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .toList();
|
|
|
|
|
|
- // Serialize all label fields
|
|
|
- for (var producer : new HashSet<>(labelFieldProducers.values())) {
|
|
|
- producer.write(builder);
|
|
|
+ // Serialize fields
|
|
|
+ for (RollupFieldSerializer fieldProducer : groupedProducers) {
|
|
|
+ fieldProducer.write(builder);
|
|
|
}
|
|
|
|
|
|
builder.endObject();
|
|
@@ -516,5 +485,6 @@ class RollupShardIndexer {
|
|
|
public boolean isEmpty() {
|
|
|
return tsid() == null || timestamp() == 0 || docCount() == 0;
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
}
|