-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Avoid expensive findEntry call in segment metadata query #10892
Avoid expensive findEntry call in segment metadata query #10892
Conversation
core/src/main/java/org/apache/druid/timeline/TimelineLookup.java
Outdated
Show resolved
Hide resolved
The change looks like a good idea to me. How about:
|
Another thing: I see that |
@abhishekagarwal87 Another idea I had is in the |
Yes. I plan to remove all |
Good catch. As far as I can tell, cutting down that call should not make a difference. |
yeah, I thought about that too. I didn't go ahead with that approach since that would require creating a new temporary state variable. For queries with many intervals, it could be that we end up creating many map entries with deep copies of |
It would seem this change alone is not enough. Adding a chunk to timeline is also O(N*N) since it calls |
); | ||
|
||
for (int ii = 0; ii < segmentCount; ii++) { | ||
segmentDescriptors.add(new SegmentDescriptor(interval, "1", ii)); |
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.
For the purpose of perf testing, does it matter if the segments have:
- different interval
- different versions
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 bottlenecks are in PartitionHolder
when it has too many objects. If segments have different intervals and versions, the size of PartitionHolder
will be much smaller and there is unlikely to be a perf issue.
{ | ||
|
||
@Test(timeout = 10_000) | ||
public void testGetQueryRunnerForSegments_singleIntervalLargeSegments() |
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.
Do we have perf different before and after 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.
It was under a second after the change. Before the change, it was more than a minute though I didn't let it run completely so it may have been very high.
unfilteredIterator, | ||
Objects::nonNull | ||
); | ||
// We add all the entries via batch add to avoid overhead of single add call. The call to add an entry to interval |
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.
I think the comment can be a little more clear in mentioning that by avoiding to call add n times and calling addAll once reduces O(n)*n to O(n)
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.
I removed unnecessary details and just put that addAll is much more efficient than add.
{ | ||
lock.readLock().lock(); | ||
try { | ||
for (Entry<Interval, TreeMap<VersionType, TimelineEntry>> entry : allTimelineEntries.entrySet()) { | ||
if (entry.getKey().equals(interval) || entry.getKey().contains(interval)) { | ||
TimelineEntry foundEntry = entry.getValue().get(version); | ||
if (foundEntry != null) { | ||
return foundEntry.getPartitionHolder().asImmutable(); |
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 the asImmutable method still needed?
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.
Looks like its not. will delete this method as well as the class ImmutablePartitionHolder.
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.
LGTM. Few comments on perf improvement validation / testing
import static org.mockito.ArgumentMatchers.any; | ||
|
||
/** | ||
* Performance tests for {@link CachingClusteredClient} can be added here. There is one test for a scenario |
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.
What performance does this class test? Is it a scalability test? It would be nice to make it clear.
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.
any kind of perf test that doesn't require a real cluster.
|
||
public static class PartitionChunkEntry<VersionType, ObjectType> | ||
{ | ||
private final Interval interval; |
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.
Please add javadoc explaining what this interval is. There are two types of interval in timeline. See LogicalSegment
.
PartitionChunk<OvershadowableInteger> actual | ||
) | ||
{ | ||
SingleElementPartitionChunk<OvershadowableInteger> expectedSingle = (SingleElementPartitionChunk<OvershadowableInteger>) expected; |
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.
SingleElementPartitionChunk
is used only by deprecated things such as NoneShardSpec
and Tranquility
. We should deprecate SingleElementPartitionChunk
as well and stop using it. You can use NumberedPartitionChunk
instead.
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.
Since it's unrelated to my change, I will leave this one for now as it is. Though I did mark SingleElementPartitionChunk
deprecated.
LGTM |
* Avoid expensive findEntry call in segment metadata query * other places * Remove findEntry * Fix add cost * Refactor a bit * Add performance test * Add comment * Review comments * intellij (cherry picked from commit 489f5b1)
* Avoid expensive findEntry call in segment metadata query * other places * Remove findEntry * Fix add cost * Refactor a bit * Add performance test * Add comment * Review comments * intellij (cherry picked from commit 489f5b1)
* Avoid expensive findEntry call in segment metadata query * other places * Remove findEntry * Fix add cost * Refactor a bit * Add performance test * Add comment * Review comments * intellij (cherry picked from commit 489f5b1)
* Avoid expensive findEntry call in segment metadata query * other places * Remove findEntry * Fix add cost * Refactor a bit * Add performance test * Add comment * Review comments * intellij (cherry picked from commit 489f5b1)
…che#10892)" This reverts commit 51a973a.
Revert "Avoid expensive findEntry call in segment metadata query (apache#10892)"
…ery (apache#10892)"" This reverts commit 97c45bc.
Revert "Revert "Avoid expensive findEntry call in segment metadata query (apache#10892)""
…che#10892)" This reverts commit 51a973a.
…ery (apache#10892)"" This reverts commit 97c45bc.
This PR fixes two performance issues in the timeline conversion, observed when an interval has a large number of segments. For such an interval,
PartitionHolder.asImmutable
is an expensive operation since it involves a deep copy of the chunk. And on top of that, a deep copy is done for every segment in the interval which significantly amplifies the performance overhead O(N*N)isComplete
call which too is O(N) for a single interval. Thus, writing to a new timeline is O(N*N) as well.