Skip to content
This repository has been archived by the owner on Jun 26, 2024. It is now read-only.

perf(entities): optimise AND filter query #193

Merged
merged 4 commits into from
Feb 28, 2024

Conversation

skjindal93
Copy link
Contributor

No description provided.

Copy link

github-actions bot commented Feb 21, 2024

Test Results

366 tests  ±0   366 ✅ ±0   9s ⏱️ -1s
 67 suites ±0     0 💤 ±0 
 67 files   ±0     0 ❌ ±0 

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.

      long: 60
      string: "PT1M"
      valueType: LONG
      valueType: STRING
    alias: "numCalls"
    columnName: "SERVICE.numCalls"
    value {
    }
  columnIdentifier {
…
org.hypertrace.gateway.service.baseline.BaselineServiceImplTest ‑ [1] function: AVGRATE
arguments {
  columnIdentifier {
    columnName: "SERVICE.numCalls"
    alias: "numCalls"
  }
}
arguments {
  literal {
    value {
      valueType: STRING
      string: "PT1M"
    }
  }
}
alias: "numCalls"

org.hypertrace.gateway.service.baseline.BaselineServiceImplTest ‑ [2] function: AVGRATE
arguments {
  columnIdentifier {
    columnName: "SERVICE.numCalls"
    alias: "numCalls"
  }
}
arguments {
  literal {
    value {
      valueType: LONG
      long: 60
    }
  }
}
alias: "numCalls"

♻️ This comment has been updated with latest results.

@@ -18,10 +18,12 @@
import java.util.stream.Stream;
Copy link
Contributor Author

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;
Copy link
Contributor Author

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;
Copy link
Contributor Author

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;
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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();
Copy link
Contributor Author

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();
Copy link
Contributor

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.

Copy link
Contributor Author

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

@skjindal93 skjindal93 marked this pull request as ready for review February 26, 2024 07:04
@skjindal93 skjindal93 requested a review from a team as a code owner February 26, 2024 07:04
public DataFetcherNode(String source, Filter filter, QueryNode childNode) {
this.source = source;
this.filter = filter;
this.childNode = childNode;
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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));
Copy link
Contributor

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();
Copy link
Contributor

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?

@avinashkolluru
Copy link
Contributor

@skjindal93 other change look fine to me. But, the build has been failing. Please check and fix.

Copy link

codecov bot commented Feb 28, 2024

Codecov Report

Attention: Patch coverage is 43.18182% with 100 lines in your changes are missing coverage. Please review.

Project coverage is 81.35%. Comparing base (fe6c625) to head (271eab2).

Files Patch % Lines
...way/service/entity/query/ExecutionTreeBuilder.java 15.49% 57 Missing and 3 partials ⚠️
.../gateway/service/entity/query/DataFetcherNode.java 35.00% 12 Missing and 1 partial ⚠️
...service/entity/query/visitor/ExecutionVisitor.java 48.00% 9 Missing and 4 partials ⚠️
...race/gateway/service/common/ExpressionContext.java 20.00% 7 Missing and 1 partial ⚠️
...ay/service/common/config/GatewayServiceConfig.java 69.23% 3 Missing and 1 partial ⚠️
...pertrace/gateway/service/entity/EntityService.java 77.77% 1 Missing and 1 partial ⚠️
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     
Flag Coverage Δ
unit 81.35% <43.18%> (-1.21%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@kotharironak kotharironak left a 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";
Copy link
Contributor

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) {
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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))
Copy link
Contributor

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?

Copy link
Contributor Author

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 = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entity.andFilter.enabled?

Copy link
Contributor Author

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

Copy link
Contributor

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.

@skjindal93 skjindal93 merged commit d05ac93 into main Feb 28, 2024
7 of 9 checks passed
@skjindal93 skjindal93 deleted the optimise+entities+query+and+filter branch February 28, 2024 13:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants