-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Speed up date_histogram by precomputing ranges #61467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7687e30
e68463d
c13740b
4162ef1
02f0fe3
4fbcfbf
7a7ef49
4f23de0
178a158
f201a25
0e93729
35ef1c4
f1e286e
d2f1d23
2281d35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
*/ | ||
package org.elasticsearch.common; | ||
|
||
import org.apache.lucene.util.ArrayUtil; | ||
import org.elasticsearch.ElasticsearchException; | ||
import org.elasticsearch.Version; | ||
import org.elasticsearch.common.LocalTimeOffset.Gap; | ||
|
@@ -44,6 +45,7 @@ | |
import java.time.temporal.TemporalQueries; | ||
import java.time.zone.ZoneOffsetTransition; | ||
import java.time.zone.ZoneRules; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Objects; | ||
|
@@ -405,6 +407,34 @@ public Rounding build() { | |
} | ||
} | ||
|
||
private abstract class PreparedRounding implements Prepared { | ||
/** | ||
* Attempt to build a {@link Prepared} implementation that relies on pre-calcuated | ||
* "round down" points. If there would be more than {@code max} points then return | ||
* the original implementation, otherwise return the new, faster implementation. | ||
*/ | ||
protected Prepared maybeUseArray(long minUtcMillis, long maxUtcMillis, int max) { | ||
long[] values = new long[1]; | ||
long rounded = round(minUtcMillis); | ||
int i = 0; | ||
values[i++] = rounded; | ||
while ((rounded = nextRoundingValue(rounded)) <= maxUtcMillis) { | ||
if (i >= max) { | ||
return this; | ||
} | ||
/* | ||
* We expect a time in the last transition (rounded - 1) to round | ||
* to the last value we calculated. If it doesn't then we're | ||
* probably doing something wrong here.... | ||
*/ | ||
assert values[i - 1] == round(rounded - 1); | ||
values = ArrayUtil.grow(values, i + 1); | ||
values[i++]= rounded; | ||
} | ||
return new ArrayRounding(values, i, this); | ||
} | ||
} | ||
|
||
static class TimeUnitRounding extends Rounding { | ||
static final byte ID = 1; | ||
|
||
|
@@ -469,6 +499,15 @@ private LocalDateTime truncateLocalDateTime(LocalDateTime localDateTime) { | |
|
||
@Override | ||
public Prepared prepare(long minUtcMillis, long maxUtcMillis) { | ||
/* | ||
* 128 is a power of two that isn't huge. We might be able to do | ||
* better if the limit was based on the actual type of prepared | ||
* rounding but this'll do for now. | ||
*/ | ||
return prepareOffsetOrJavaTimeRounding(minUtcMillis, maxUtcMillis).maybeUseArray(minUtcMillis, maxUtcMillis, 128); | ||
} | ||
|
||
private TimeUnitPreparedRounding prepareOffsetOrJavaTimeRounding(long minUtcMillis, long maxUtcMillis) { | ||
long minLookup = minUtcMillis - unit.extraLocalOffsetLookup(); | ||
long maxLookup = maxUtcMillis; | ||
|
||
|
@@ -487,7 +526,6 @@ public Prepared prepare(long minUtcMillis, long maxUtcMillis) { | |
// Range too long, just use java.time | ||
return prepareJavaTime(); | ||
} | ||
|
||
LocalTimeOffset fixedOffset = lookup.fixedInRange(minLookup, maxLookup); | ||
if (fixedOffset != null) { | ||
// The time zone is effectively fixed | ||
|
@@ -516,7 +554,7 @@ public Prepared prepareForUnknown() { | |
} | ||
|
||
@Override | ||
Prepared prepareJavaTime() { | ||
TimeUnitPreparedRounding prepareJavaTime() { | ||
if (unitRoundsToMidnight) { | ||
return new JavaTimeToMidnightRounding(); | ||
} | ||
|
@@ -555,7 +593,7 @@ public String toString() { | |
return "Rounding[" + unit + " in " + timeZone + "]"; | ||
} | ||
|
||
private abstract class TimeUnitPreparedRounding implements Prepared { | ||
private abstract class TimeUnitPreparedRounding extends PreparedRounding { | ||
@Override | ||
public double roundingSize(long utcMillis, DateTimeUnit timeUnit) { | ||
if (timeUnit.isMillisBased == unit.isMillisBased) { | ||
|
@@ -649,6 +687,14 @@ public long inOverlap(long localMillis, Overlap overlap) { | |
public long beforeOverlap(long localMillis, Overlap overlap) { | ||
return overlap.previous().localToUtc(localMillis, this); | ||
} | ||
|
||
@Override | ||
protected Prepared maybeUseArray(long minUtcMillis, long maxUtcMillis, int max) { | ||
if (lookup.anyMoveBackToPreviousDay()) { | ||
return this; | ||
} | ||
return super.maybeUseArray(minUtcMillis, maxUtcMillis, max); | ||
} | ||
} | ||
|
||
private class NotToMidnightRounding extends AbstractNotToMidnightRounding implements LocalTimeOffset.Strategy { | ||
|
@@ -708,6 +754,12 @@ public long nextRoundingValue(long utcMillis) { | |
return firstTimeOnDay(localMidnight); | ||
} | ||
|
||
@Override | ||
protected Prepared maybeUseArray(long minUtcMillis, long maxUtcMillis, int max) { | ||
// We don't have the right information needed to know if this is safe for this time zone so we always use java rounding | ||
return this; | ||
} | ||
|
||
private long firstTimeOnDay(LocalDateTime localMidnight) { | ||
assert localMidnight.toLocalTime().equals(LocalTime.of(0, 0, 0)) : "firstTimeOnDay should only be called at midnight"; | ||
|
||
|
@@ -1111,7 +1163,7 @@ public byte id() { | |
|
||
@Override | ||
public Prepared prepare(long minUtcMillis, long maxUtcMillis) { | ||
return wrapPreparedRounding(delegate.prepare(minUtcMillis, maxUtcMillis)); | ||
return wrapPreparedRounding(delegate.prepare(minUtcMillis - offset, maxUtcMillis - offset)); | ||
} | ||
|
||
@Override | ||
|
@@ -1186,4 +1238,41 @@ public static Rounding read(StreamInput in) throws IOException { | |
throw new ElasticsearchException("unknown rounding id [" + id + "]"); | ||
} | ||
} | ||
|
||
/** | ||
* Implementation of {@link Prepared} using pre-calculated "round down" points. | ||
*/ | ||
private static class ArrayRounding implements Prepared { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will need to implement new methods added in #61369. |
||
private final long[] values; | ||
private final int max; | ||
private final Prepared delegate; | ||
|
||
private ArrayRounding(long[] values, int max, Prepared delegate) { | ||
this.values = values; | ||
this.max = max; | ||
this.delegate = delegate; | ||
} | ||
|
||
@Override | ||
public long round(long utcMillis) { | ||
assert values[0] <= utcMillis : "utcMillis must be after " + values[0]; | ||
int idx = Arrays.binarySearch(values, 0, max, utcMillis); | ||
assert idx != -1 : "The insertion point is before the array! This should have tripped the assertion above."; | ||
assert -1 - idx <= values.length : "This insertion point is after the end of the array."; | ||
if (idx < 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe assert that idx is neither -1 nor -1 - max? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! |
||
idx = -2 - idx; | ||
} | ||
return values[idx]; | ||
} | ||
|
||
@Override | ||
public long nextRoundingValue(long utcMillis) { | ||
return delegate.nextRoundingValue(utcMillis); | ||
} | ||
|
||
@Override | ||
public double roundingSize(long utcMillis, DateTimeUnit timeUnit) { | ||
return delegate.roundingSize(utcMillis, timeUnit); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this change fixing an existing bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I think the bug is worse with this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new assertions in the
ArrayRounding
fail without this.