Skip to content
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

[NAE-2053] Optimize ElasticCaseService queries to eliminate maxClauseCount error #287

Merged
merged 2 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ services:
memory: "512M"

docker-elastic:
image: elasticsearch:7.17.4
image: elasticsearch:7.17.26
environment:
- cluster.name=elasticsearch
- discovery.type=single-node
Expand Down
5 changes: 2 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.netgrif</groupId>
<artifactId>application-engine</artifactId>
<version>6.2.9-SNAPSHOT</version>
<version>6.2.10</version>
<packaging>jar</packaging>

<name>NETGRIF Application Engine</name>
Expand Down Expand Up @@ -441,8 +441,7 @@
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</artifactId>
<version>2.0.0.M2</version>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>xmlunit</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ public Page<Case> search(List<CaseSearchRequest> requests, LoggedUser user, Page
List<Case> casePage;
long total;
if (query != null) {
SearchHits<ElasticCase> hits = template.search(query, ElasticCase.class, IndexCoordinates.of(caseIndex));
Page<ElasticCase> indexedCases = (Page)SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(hits, query.getPageable()));
SearchHits<ElasticCase> hits = template.search(query, ElasticCase.class, IndexCoordinates.of(caseIndex));
Page<ElasticCase> indexedCases = (Page) SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(hits, query.getPageable()));
casePage = workflowService.findAllById(indexedCases.get().map(ElasticCase::getStringId).collect(Collectors.toList()));
total = indexedCases.getTotalElements();
} else {
Expand All @@ -169,14 +169,16 @@ public long count(List<CaseSearchRequest> requests, LoggedUser user, Locale loca
}

private NativeSearchQuery buildQuery(List<CaseSearchRequest> requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) {
List<BoolQueryBuilder> singleQueries = requests.stream().map(request -> buildSingleQuery(request, user, locale)).collect(Collectors.toList());
List<BoolQueryBuilder> singleQueries = requests.stream()
.map(request -> buildSingleQuery(request, user, locale))
.collect(Collectors.toList());

if (isIntersection && !singleQueries.stream().allMatch(Objects::nonNull)) {
// one of the queries evaluates to empty set => the entire result is an empty set
return null;
} else if (!isIntersection) {
singleQueries = singleQueries.stream().filter(Objects::nonNull).collect(Collectors.toList());
if (singleQueries.size() == 0) {
if (singleQueries.isEmpty()) {
// all queries result in an empty set => the entire result is an empty set
return null;
}
Expand Down Expand Up @@ -209,28 +211,33 @@ private BoolQueryBuilder buildSingleQuery(CaseSearchRequest request, LoggedUser

// TODO: filtered query https://stackoverflow.com/questions/28116404/filtered-query-using-nativesearchquerybuilder-in-spring-data-elasticsearch

if (resultAlwaysEmpty)
return null;
else
return query;
return resultAlwaysEmpty ? null : query;
}

private void buildPetriNetQuery(CaseSearchRequest request, LoggedUser user, BoolQueryBuilder query) {
if (request.process == null || request.process.isEmpty()) {
return;
}

BoolQueryBuilder petriNetQuery = boolQuery();
Set<String> identifiers = new HashSet<>();
Set<String> processIds = new HashSet<>();

for (CaseSearchRequest.PetriNet process : request.process) {
if (process.identifier != null) {
petriNetQuery.should(termQuery("processIdentifier", process.identifier));
request.process.forEach(p -> {
if (p.identifier != null) {
identifiers.add(p.identifier);
}
if (process.processId != null) {
petriNetQuery.should(termQuery("processId", process.processId));
if (p.processId != null) {
processIds.add(p.processId);
}
}
});

BoolQueryBuilder petriNetQuery = boolQuery();
if (!identifiers.isEmpty()) {
petriNetQuery.should(termsQuery("processIdentifier", identifiers));
}
if (!processIds.isEmpty()) {
petriNetQuery.should(termsQuery("processId", processIds));
}
query.filter(petriNetQuery);
}

Expand Down Expand Up @@ -332,13 +339,7 @@ private void buildRoleQuery(CaseSearchRequest request, BoolQueryBuilder query) {
if (request.role == null || request.role.isEmpty()) {
return;
}

BoolQueryBuilder roleQuery = boolQuery();
for (String roleId : request.role) {
roleQuery.should(termQuery("enabledRoles", roleId));
}

query.filter(roleQuery);
query.filter(termsQuery("enabledRoles", request.role));
}

/**
Expand Down Expand Up @@ -416,20 +417,14 @@ private void buildCaseIdQuery(CaseSearchRequest request, BoolQueryBuilder query)
if (request.stringId == null || request.stringId.isEmpty()) {
return;
}

BoolQueryBuilder caseIdQuery = boolQuery();
request.stringId.forEach(caseId -> caseIdQuery.should(termQuery("stringId", caseId)));
query.filter(caseIdQuery);
query.filter(termsQuery("stringId", request.stringId));
}

private void buildUriNodeIdQuery(CaseSearchRequest request, BoolQueryBuilder query) {
if (request.uriNodeId == null || request.uriNodeId.isEmpty()) {
return;
}

BoolQueryBuilder caseIdQuery = boolQuery();
caseIdQuery.should(termQuery("uriNodeId", request.uriNodeId));
query.filter(caseIdQuery);
query.filter(termQuery("uriNodeId", request.uriNodeId));
}

/**
Expand Down Expand Up @@ -458,14 +453,13 @@ private boolean buildGroupQuery(CaseSearchRequest request, LoggedUser user, Loca
Map<String, Object> processQuery = new HashMap<>();
processQuery.put("group", request.group);
List<PetriNetReference> groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent();
if (groupProcesses.size() == 0)
if (groupProcesses.isEmpty()) {
return true;

BoolQueryBuilder groupQuery = boolQuery();
groupProcesses.stream().map(PetriNetReference::getIdentifier)
.map(netIdentifier -> termQuery("processIdentifier", netIdentifier))
.forEach(groupQuery::should);
query.filter(groupQuery);
}
List<String> identifiers = groupProcesses.stream()
.map(PetriNetReference::getIdentifier)
.collect(Collectors.toList());
query.filter(termsQuery("processIdentifier", identifiers));
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
public abstract class ElasticViewPermissionService {

protected void buildViewPermissionQuery(BoolQueryBuilder query, LoggedUser user) {
BoolQueryBuilder viewPermsExists = boolQuery();
BoolQueryBuilder viewPermNotExists = boolQuery();
viewPermsExists.should(existsQuery("viewRoles"));
viewPermsExists.should(existsQuery("viewUserRefs"));
viewPermNotExists.mustNot(viewPermsExists);
// Check if viewRoles or viewUserRefs exist
BoolQueryBuilder viewPermsExists = boolQuery()
.should(existsQuery("viewRoles"))
.should(existsQuery("viewUserRefs"));
// Condition where these attributes do NOT exist
BoolQueryBuilder viewPermNotExists = boolQuery()
.mustNot(viewPermsExists);

/* Build positive view role query */
BoolQueryBuilder positiveViewRole = buildPositiveViewRoleQuery(viewPermNotExists, user);
Expand All @@ -38,42 +40,45 @@ protected void buildViewPermissionQuery(BoolQueryBuilder query, LoggedUser user)
query.filter(permissionQuery);
}

/**
* Build a positive view role query using termsQuery for efficiency.
* This reduces the number of clauses by sending all roles at once.
*/
private BoolQueryBuilder buildPositiveViewRoleQuery(BoolQueryBuilder viewPermNotExists, LoggedUser user) {
BoolQueryBuilder positiveViewRole = boolQuery();
BoolQueryBuilder positiveViewRoleQuery = boolQuery();
for (String roleId : user.getProcessRoles()) {
positiveViewRoleQuery.should(termQuery("viewRoles", roleId));
if (!user.getProcessRoles().isEmpty()) {
positiveViewRole.should(termsQuery("viewRoles", user.getProcessRoles()));
}
positiveViewRole.should(viewPermNotExists);
positiveViewRole.should(positiveViewRoleQuery);
return positiveViewRole;
}

/**
* Build a negative view role query by excluding negative roles.
*/
private BoolQueryBuilder buildNegativeViewRoleQuery(LoggedUser user) {
BoolQueryBuilder negativeViewRole = boolQuery();
BoolQueryBuilder negativeViewRoleQuery = boolQuery();
for (String roleId : user.getProcessRoles()) {
negativeViewRoleQuery.should(termQuery("negativeViewRoles", roleId));
if (!user.getProcessRoles().isEmpty()) {
negativeViewRole.mustNot(termsQuery("negativeViewRoles", user.getProcessRoles()));
}
negativeViewRole.mustNot(negativeViewRoleQuery);
return negativeViewRole;
}

/**
* Build a positive view user query using filter (as score is not needed).
*/
private BoolQueryBuilder buildPositiveViewUser(BoolQueryBuilder viewPermNotExists, LoggedUser user) {
BoolQueryBuilder positiveViewUser = boolQuery();
BoolQueryBuilder positiveViewUserQuery = boolQuery();
positiveViewUserQuery.must(termQuery("viewUsers", user.getId()));
positiveViewUser.should(viewPermNotExists);
positiveViewUser.should(positiveViewUserQuery);
return positiveViewUser;
return boolQuery()
.should(viewPermNotExists)
.filter(termQuery("viewUsers", user.getId()));
}

/**
* Build a negative view user query to exclude the specified user.
*/
private BoolQueryBuilder buildNegativeViewUser(LoggedUser user) {
BoolQueryBuilder negativeViewUser = boolQuery();
BoolQueryBuilder negativeViewUserQuery = boolQuery();
negativeViewUserQuery.should(termQuery("negativeViewUsers", user.getId()));
negativeViewUser.mustNot(negativeViewUserQuery);
return negativeViewUser;
return boolQuery()
.mustNot(termQuery("negativeViewUsers", user.getId()));
}

private BoolQueryBuilder setMinus(BoolQueryBuilder positiveSet, BoolQueryBuilder negativeSet) {
Expand All @@ -83,10 +88,13 @@ private BoolQueryBuilder setMinus(BoolQueryBuilder positiveSet, BoolQueryBuilder
return positiveSetMinusNegativeSet;
}

/**
* Unions two queries using OR with a minimum_should_match of 1.
*/
private BoolQueryBuilder union(BoolQueryBuilder setA, BoolQueryBuilder setB) {
BoolQueryBuilder unionSet = boolQuery();
unionSet.should(setA);
unionSet.should(setB);
return unionSet;
return boolQuery()
.should(setA)
.should(setB)
.minimumShouldMatch(1);
}
}
Loading