-
Notifications
You must be signed in to change notification settings - Fork 8
perf(entities): optimise AND filter query #193
Conversation
Test Results366 tests ±0 366 ✅ ±0 9s ⏱️ -1s Results for commit 271eab2. ± Comparison against base commit fe6c625. This pull request removes 17 and adds 2 tests. Note that renamed tests count towards both.
♻️ This comment has been updated with latest results. |
@@ -18,10 +18,12 @@ | |||
import java.util.stream.Stream; |
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.
logic class
@@ -1,12 +1,19 @@ | |||
package org.hypertrace.gateway.service.entity.query; |
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.
logic class
@@ -19,13 +19,26 @@ public class DataFetcherNode implements QueryNode { | |||
private Integer limit; |
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.
logic class
@@ -30,6 +30,7 @@ | |||
import org.hypertrace.gateway.service.entity.query.NoOpNode; |
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.
logic class
@@ -305,6 +316,115 @@ QueryNode buildFilterTree(EntitiesRequest entitiesRequest, Filter filter) { | |||
} | |||
} | |||
|
|||
QueryNode buildAndFilterTree(EntitiesRequest entitiesRequest) { |
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 most important logic
@@ -118,6 +119,19 @@ protected static EntityResponse union(List<EntityResponse> entityResponses) { | |||
|
|||
@Override | |||
public EntityResponse visit(DataFetcherNode dataFetcherNode) { | |||
QueryNode childNode = dataFetcherNode.getChildNode(); |
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 most important logic
QueryNode optimizedFilterTree = filterTree.acceptVisitor(new FilterOptimizingVisitor()); | ||
if (LOG.isDebugEnabled()) { | ||
LOG.debug("Optimized Filter Tree:{}", optimizedFilterTree.acceptVisitor(new PrintVisitor())); | ||
boolean isAndFilter = executionContext.getExpressionContext().isAndFilter(); |
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.
Didnt understand this. The comment says optimization visitor is not needed for AND filter but the condition is checking for isAndFilter. I'm confused.
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.
Thanks for catching it. I have fixed it. It should have been !isAndFilter
public DataFetcherNode(String source, Filter filter, QueryNode childNode) { | ||
this.source = source; | ||
this.filter = filter; | ||
this.childNode = childNode; |
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 there any restriction or limitation on the type of data a childNode fetch node can have?
@@ -147,13 +155,19 @@ public QueryNode build() { | |||
* {@link FilterOptimizingVisitor} is needed to merge filters corresponding to the same source | |||
* into one {@link DataFetcherNode}, instead of having multiple {@link DataFetcherNode}s for | |||
* each filter | |||
* | |||
* <p>It is not needed for AND filter, since the filter tree is already optimised with a single |
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 same true for OR
filter too?
@@ -305,6 +316,104 @@ QueryNode buildFilterTree(EntitiesRequest entitiesRequest, Filter filter) { | |||
} | |||
} | |||
|
|||
QueryNode buildAndFilterTree(EntitiesRequest entitiesRequest) { | |||
// If the filter by and order by are from QS, pagination can be pushed down to QS | |||
// Since the filter and order by are from QS, there won't be any filter on other |
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.
Isn't the single source fetch is already handled at the beginning of the function?
AttributeSource source = entry.getKey(); | ||
Filter andFilter = entry.getValue(); | ||
|
||
dataFetcherNodes.add(new DataFetcherNode(source.name(), andFilter, qsNode)); |
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.
This make sure that first we get the data from QS as its child node, correct?
childFilter = Filter.getDefaultInstance(); | ||
} else { | ||
// Construct the filter from the child nodes result | ||
childEntityFetcherResponse = childNodeResponse.getEntityFetcherResponse(); |
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.
Are we restricting the max number of entities in the sub-sequent filter?
@skjindal93 other change look fine to me. But, the build has been failing. Please check and fix. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #193 +/- ##
============================================
- Coverage 82.56% 81.35% -1.21%
- Complexity 1367 1371 +4
============================================
Files 125 126 +1
Lines 6068 6200 +132
Branches 501 524 +23
============================================
+ Hits 5010 5044 +34
- Misses 807 897 +90
- Partials 251 259 +8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
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.
Overall looks fine.
import org.hypertrace.gateway.service.entity.config.LogConfig; | ||
|
||
public class GatewayServiceConfig { | ||
private static final String ENTITY_AND_FILTER_ENABLED_CONFIG_KEY = "filter.entity.and.enabled"; |
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.
entity.andFilter.enabled
looks more readable.
this.canFetchTotal = false; | ||
} | ||
|
||
public DataFetcherNode(String source, Filter filter, QueryNode childNode) { |
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.
As mentioned prior, Should we be restrictive that DataFetcherNode can have only another DataFetcherNode as child or NoOp node?
Map<AttributeSource, Filter> sourceToAndFilterMap = | ||
new EnumMap<>(buildSourceToAndFilterMap(entitiesRequest.getFilter())); | ||
|
||
// qs node as the pivot node to fetch time range data |
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.
So, there won't be any query w/o time range filter, right?
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.
right
@@ -128,6 +140,15 @@ public EntityResponse visit(DataFetcherNode dataFetcherNode) { | |||
entitiesRequest.getEntityType(), | |||
executionContext.getTimestampAttributeId()); | |||
|
|||
Filter entitiesRequestFilter = | |||
childFilter != null |
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.
When result is empty - it seems that the method constructFilterFromChildNodesResult
returns Filter.getDefaultInstance()
Is the defaultInstance equals null
?
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 defaultInstance equals null?
No. It's just an empty filter instance
EntityFetcherResponse entityFetcherResponse = entityFetcher.getEntities(context, request); | ||
response = | ||
childEntityFetcherResponse != null | ||
? intersectEntities(List.of(childEntityFetcherResponse, entityFetcherResponse)) |
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.
Curious Q: Do we still need to intersect when childEntityFetcherResponse
is not null
? In that case, wouldn't the child filter have already applied in subsequent query?
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.
intersectEntities
also takes care of merging the selection attributes fetched from various sources
If we fetch attribute A from QS, and attribute B from EDS for the same entity, intersectEntities
would merge the responses, and have both A and B added as an attribute on the entity response
@@ -69,6 +69,10 @@ entity.idcolumn.config = [ | |||
}, | |||
] | |||
|
|||
filter.entity = { |
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.
entity.andFilter.enabled?
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 usually follow a top-down hierarchial approach while building configs, so that the configs are re-usable at a parent level
In this case, filter
being the parent can be extended to other configs, and so on
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.
While reading the code, all variable names are isEntityAndFilterEnabled, but the corresponding configuration name differs in the application.conf file. This makes it a bit difficult to easily co-relate.
No description provided.