8
8
9
9
package org .elasticsearch .search .aggregations .timeseries ;
10
10
11
+ import org .apache .lucene .util .BytesRef ;
11
12
import org .elasticsearch .common .io .stream .StreamInput ;
12
13
import org .elasticsearch .common .io .stream .StreamOutput ;
14
+ import org .elasticsearch .common .util .BigArrays ;
15
+ import org .elasticsearch .common .util .BytesRefHash ;
16
+ import org .elasticsearch .common .util .LongObjectPagedHashMap ;
17
+ import org .elasticsearch .index .mapper .TimeSeriesIdFieldMapper ;
13
18
import org .elasticsearch .search .aggregations .AggregationReduceContext ;
14
19
import org .elasticsearch .search .aggregations .InternalAggregation ;
15
20
import org .elasticsearch .search .aggregations .InternalAggregations ;
@@ -46,11 +51,12 @@ public class InternalTimeSeries extends InternalMultiBucketAggregation<InternalT
46
51
public static class InternalBucket extends InternalMultiBucketAggregation .InternalBucket implements TimeSeries .Bucket {
47
52
protected long bucketOrd ;
48
53
protected final boolean keyed ;
49
- protected final Map <String , Object > key ;
54
+ protected final BytesRef key ;
55
+ // TODO: make computing docCount optional
50
56
protected long docCount ;
51
57
protected InternalAggregations aggregations ;
52
58
53
- public InternalBucket (Map < String , Object > key , long docCount , InternalAggregations aggregations , boolean keyed ) {
59
+ public InternalBucket (BytesRef key , long docCount , InternalAggregations aggregations , boolean keyed ) {
54
60
this .key = key ;
55
61
this .docCount = docCount ;
56
62
this .aggregations = aggregations ;
@@ -62,26 +68,26 @@ public InternalBucket(Map<String, Object> key, long docCount, InternalAggregatio
62
68
*/
63
69
public InternalBucket (StreamInput in , boolean keyed ) throws IOException {
64
70
this .keyed = keyed ;
65
- key = in .readOrderedMap ( StreamInput :: readString , StreamInput :: readGenericValue );
71
+ key = in .readBytesRef ( );
66
72
docCount = in .readVLong ();
67
73
aggregations = InternalAggregations .readFrom (in );
68
74
}
69
75
70
76
@ Override
71
77
public void writeTo (StreamOutput out ) throws IOException {
72
- out .writeMap (key , StreamOutput :: writeString , StreamOutput :: writeGenericValue );
78
+ out .writeBytesRef (key );
73
79
out .writeVLong (docCount );
74
80
aggregations .writeTo (out );
75
81
}
76
82
77
83
@ Override
78
84
public Map <String , Object > getKey () {
79
- return key ;
85
+ return TimeSeriesIdFieldMapper . decodeTsid ( key ) ;
80
86
}
81
87
82
88
@ Override
83
89
public String getKeyAsString () {
84
- return key .toString ();
90
+ return getKey () .toString ();
85
91
}
86
92
87
93
@ Override
@@ -96,8 +102,10 @@ public InternalAggregations getAggregations() {
96
102
97
103
@ Override
98
104
public XContentBuilder toXContent (XContentBuilder builder , Params params ) throws IOException {
105
+ // Use map key in the xcontent response:
106
+ var key = getKey ();
99
107
if (keyed ) {
100
- builder .startObject (getKeyAsString ());
108
+ builder .startObject (key . toString ());
101
109
} else {
102
110
builder .startObject ();
103
111
}
@@ -186,38 +194,46 @@ protected void doWriteTo(StreamOutput out) throws IOException {
186
194
187
195
@ Override
188
196
public InternalAggregation reduce (List <InternalAggregation > aggregations , AggregationReduceContext reduceContext ) {
189
- // We still need to reduce in case we got the same time series in 2 different indices, but we should be able to optimize
190
- // that in the future
191
- Map <Map <String , Object >, List <InternalBucket >> bucketsList = null ;
192
- for (InternalAggregation aggregation : aggregations ) {
193
- InternalTimeSeries timeSeries = (InternalTimeSeries ) aggregation ;
194
- if (bucketsList != null ) {
195
- for (InternalBucket bucket : timeSeries .buckets ) {
196
- bucketsList .compute (bucket .key , (map , list ) -> {
197
- if (list == null ) {
198
- list = new ArrayList <>();
197
+ // TODO: optimize single result case either by having a if check here and return aggregations.get(0) or
198
+ // by overwriting the mustReduceOnSingleInternalAgg() method
199
+
200
+ final BigArrays bigArrays = reduceContext .bigArrays ();
201
+ final int initialCapacity = aggregations .stream ()
202
+ .map (value -> (InternalTimeSeries ) value )
203
+ .mapToInt (value -> value .getBuckets ().size ())
204
+ .max ()
205
+ .getAsInt ();
206
+ try (LongObjectPagedHashMap <List <InternalBucket >> tsKeyToBuckets = new LongObjectPagedHashMap <>(initialCapacity , bigArrays )) {
207
+ final int numTsids ;
208
+ // We still need to reduce in case we got the same time series in 2 different indices, but we should be able to optimize
209
+ // that in the future
210
+ try (BytesRefHash tsids = new BytesRefHash (initialCapacity , bigArrays )) {
211
+ for (int i = 0 ; i < aggregations .size (); i ++) {
212
+ InternalTimeSeries timeSeries = (InternalTimeSeries ) aggregations .get (i );
213
+ for (int j = 0 ; j < timeSeries .getBuckets ().size (); j ++) {
214
+ InternalBucket bucket = timeSeries .getBuckets ().get (j );
215
+ long key = tsids .add (bucket .key );
216
+ List <InternalBucket > buckets ;
217
+ if (key < 0 ) {
218
+ key = -1 - key ;
219
+ buckets = tsKeyToBuckets .get (key );
220
+ } else {
221
+ buckets = new ArrayList <>();
199
222
}
200
- list .add (bucket );
201
- return list ;
202
- });
203
- }
204
- } else {
205
- bucketsList = new HashMap <>(timeSeries .buckets .size ());
206
- for (InternalTimeSeries .InternalBucket bucket : timeSeries .buckets ) {
207
- List <InternalBucket > bucketList = new ArrayList <>();
208
- bucketList .add (bucket );
209
- bucketsList .put (bucket .key , bucketList );
223
+ buckets .add (bucket );
224
+ tsKeyToBuckets .put (key , buckets );
225
+ }
210
226
}
227
+ numTsids = (int ) tsids .size ();
211
228
}
212
- }
213
229
214
- reduceContext .consumeBucketsAndMaybeBreak (bucketsList .size ());
215
- InternalTimeSeries reduced = new InternalTimeSeries (name , new ArrayList <>(bucketsList .size ()), keyed , getMetadata ());
216
- for (Map .Entry <Map <String , Object >, List <InternalBucket >> bucketEntry : bucketsList .entrySet ()) {
217
- reduced .buckets .add (reduceBucket (bucketEntry .getValue (), reduceContext ));
230
+ reduceContext .consumeBucketsAndMaybeBreak (numTsids );
231
+ InternalTimeSeries reduced = new InternalTimeSeries (name , new ArrayList <>(numTsids ), keyed , getMetadata ());
232
+ for (LongObjectPagedHashMap .Cursor <List <InternalBucket >> tsKeyToBucket : tsKeyToBuckets ) {
233
+ reduced .buckets .add (reduceBucket (tsKeyToBucket .value , reduceContext ));
234
+ }
235
+ return reduced ;
218
236
}
219
- return reduced ;
220
-
221
237
}
222
238
223
239
@ Override
0 commit comments