|
@@ -41,13 +41,14 @@ import org.elasticsearch.search.aggregations.support.CoreValuesSourceType;
|
|
|
import java.io.IOException;
|
|
|
import java.net.InetAddress;
|
|
|
import java.time.ZoneId;
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.Base64;
|
|
|
import java.util.Collections;
|
|
|
-import java.util.Comparator;
|
|
|
import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
import java.util.Map;
|
|
|
-import java.util.SortedSet;
|
|
|
-import java.util.TreeSet;
|
|
|
+import java.util.SortedMap;
|
|
|
+import java.util.TreeMap;
|
|
|
|
|
|
/**
|
|
|
* Mapper for {@code _tsid} field included generated when the index is
|
|
@@ -176,16 +177,14 @@ public class TimeSeriesIdFieldMapper extends MetadataFieldMapper {
|
|
|
|
|
|
public static final int MAX_DIMENSIONS = 512;
|
|
|
|
|
|
- private record Dimension(BytesRef name, BytesReference value) {}
|
|
|
-
|
|
|
private final Murmur3Hasher tsidHasher = new Murmur3Hasher(0);
|
|
|
|
|
|
/**
|
|
|
- * A sorted set of the serialized values of dimension fields that will be used
|
|
|
+ * A map of the serialized values of dimension fields that will be used
|
|
|
* for generating the _tsid field. The map will be used by {@link TimeSeriesIdFieldMapper}
|
|
|
* to build the _tsid field for the document.
|
|
|
*/
|
|
|
- private final SortedSet<Dimension> dimensions = new TreeSet<>(Comparator.comparing(o -> o.name));
|
|
|
+ private final SortedMap<BytesRef, List<BytesReference>> dimensions = new TreeMap<>();
|
|
|
/**
|
|
|
* Builds the routing. Used for building {@code _id}. If null then skipped.
|
|
|
*/
|
|
@@ -203,9 +202,17 @@ public class TimeSeriesIdFieldMapper extends MetadataFieldMapper {
|
|
|
|
|
|
try (BytesStreamOutput out = new BytesStreamOutput()) {
|
|
|
out.writeVInt(dimensions.size());
|
|
|
- for (Dimension entry : dimensions) {
|
|
|
- out.writeBytesRef(entry.name);
|
|
|
- entry.value.writeTo(out);
|
|
|
+ for (Map.Entry<BytesRef, List<BytesReference>> entry : dimensions.entrySet()) {
|
|
|
+ out.writeBytesRef(entry.getKey());
|
|
|
+ List<BytesReference> value = entry.getValue();
|
|
|
+ if (value.size() > 1) {
|
|
|
+ // multi-value dimensions are only supported for newer indices that use buildTsidHash
|
|
|
+ throw new IllegalArgumentException(
|
|
|
+ "Dimension field [" + entry.getKey().utf8ToString() + "] cannot be a multi-valued field."
|
|
|
+ );
|
|
|
+ }
|
|
|
+ assert value.isEmpty() == false : "dimension value is empty";
|
|
|
+ value.get(0).writeTo(out);
|
|
|
}
|
|
|
return out.bytes();
|
|
|
}
|
|
@@ -237,18 +244,19 @@ public class TimeSeriesIdFieldMapper extends MetadataFieldMapper {
|
|
|
int tsidHashIndex = StreamOutput.putVInt(tsidHash, len, 0);
|
|
|
|
|
|
tsidHasher.reset();
|
|
|
- for (final Dimension dimension : dimensions) {
|
|
|
- tsidHasher.update(dimension.name.bytes);
|
|
|
+ for (final BytesRef name : dimensions.keySet()) {
|
|
|
+ tsidHasher.update(name.bytes);
|
|
|
}
|
|
|
tsidHashIndex = writeHash128(tsidHasher.digestHash(), tsidHash, tsidHashIndex);
|
|
|
|
|
|
// NOTE: concatenate all dimension value hashes up to a certain number of dimensions
|
|
|
int tsidHashStartIndex = tsidHashIndex;
|
|
|
- for (final Dimension dimension : dimensions) {
|
|
|
+ for (final List<BytesReference> values : dimensions.values()) {
|
|
|
if ((tsidHashIndex - tsidHashStartIndex) >= 4 * numberOfDimensions) {
|
|
|
break;
|
|
|
}
|
|
|
- final BytesRef dimensionValueBytesRef = dimension.value.toBytesRef();
|
|
|
+ assert values.isEmpty() == false : "dimension values are empty";
|
|
|
+ final BytesRef dimensionValueBytesRef = values.get(0).toBytesRef();
|
|
|
ByteUtils.writeIntLE(
|
|
|
StringHelper.murmurhash3_x86_32(
|
|
|
dimensionValueBytesRef.bytes,
|
|
@@ -264,8 +272,10 @@ public class TimeSeriesIdFieldMapper extends MetadataFieldMapper {
|
|
|
|
|
|
// NOTE: hash all dimension field allValues
|
|
|
tsidHasher.reset();
|
|
|
- for (final Dimension dimension : dimensions) {
|
|
|
- tsidHasher.update(dimension.value.toBytesRef().bytes);
|
|
|
+ for (final List<BytesReference> values : dimensions.values()) {
|
|
|
+ for (BytesReference v : values) {
|
|
|
+ tsidHasher.update(v.toBytesRef().bytes);
|
|
|
+ }
|
|
|
}
|
|
|
tsidHashIndex = writeHash128(tsidHasher.digestHash(), tsidHash, tsidHashIndex);
|
|
|
|
|
@@ -368,8 +378,20 @@ public class TimeSeriesIdFieldMapper extends MetadataFieldMapper {
|
|
|
}
|
|
|
|
|
|
private void add(String fieldName, BytesReference encoded) throws IOException {
|
|
|
- if (dimensions.add(new Dimension(new BytesRef(fieldName), encoded)) == false) {
|
|
|
- throw new IllegalArgumentException("Dimension field [" + fieldName + "] cannot be a multi-valued field.");
|
|
|
+ BytesRef name = new BytesRef(fieldName);
|
|
|
+ List<BytesReference> values = dimensions.get(name);
|
|
|
+ if (values == null) {
|
|
|
+ // optimize for the common case where dimensions are not multi-valued
|
|
|
+ dimensions.put(name, List.of(encoded));
|
|
|
+ } else {
|
|
|
+ if (values.size() == 1) {
|
|
|
+ // converts the immutable list that's optimized for the common case of having only one value to a mutable list
|
|
|
+ BytesReference previousValue = values.get(0);
|
|
|
+ values = new ArrayList<>(4);
|
|
|
+ values.add(previousValue);
|
|
|
+ dimensions.put(name, values);
|
|
|
+ }
|
|
|
+ values.add(encoded);
|
|
|
}
|
|
|
}
|
|
|
}
|