TSK-1718: Performance optimization for TaskService#getTask and TaskQuery#list

The execution time has been reduced from O(n²) to O(n)
This commit is contained in:
Mustapha Zorgati 2021-08-25 09:23:05 +02:00
parent 591963cebc
commit 54428045dd
2 changed files with 114 additions and 140 deletions

View File

@ -1,6 +1,7 @@
package pro.taskana.task.internal; package pro.taskana.task.internal;
import java.time.Instant; import java.time.Instant;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Delete;
@ -81,7 +82,7 @@ public interface AttachmentMapper {
@Result(property = "channel", column = "CHANNEL") @Result(property = "channel", column = "CHANNEL")
@Result(property = "received", column = "RECEIVED") @Result(property = "received", column = "RECEIVED")
List<AttachmentSummaryImpl> findAttachmentSummariesByTaskIds( List<AttachmentSummaryImpl> findAttachmentSummariesByTaskIds(
@Param("taskIds") List<String> taskIds); @Param("taskIds") Collection<String> taskIds);
@Delete("DELETE FROM ATTACHMENT WHERE ID=#{attachmentId}") @Delete("DELETE FROM ATTACHMENT WHERE ID=#{attachmentId}")
void delete(@Param("attachmentId") String attachmentId); void delete(@Param("attachmentId") String attachmentId);

View File

@ -14,6 +14,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -312,18 +313,13 @@ public class TaskServiceImpl implements TaskService {
attachmentImpls = new ArrayList<>(); attachmentImpls = new ArrayList<>();
} }
List<ClassificationSummary> classifications; Map<String, ClassificationSummary> classificationSummariesById =
classifications = findClassificationForTaskImplAndAttachments(resultTask, attachmentImpls); findClassificationForTaskImplAndAttachments(resultTask, attachmentImpls);
List<Attachment> attachments = addClassificationSummariesToAttachments(attachmentImpls, classificationSummariesById);
addClassificationSummariesToAttachments(attachmentImpls, classifications); resultTask.setAttachments(new ArrayList<>(attachmentImpls));
resultTask.setAttachments(attachments);
String classificationId = resultTask.getClassificationSummary().getId(); String classificationId = resultTask.getClassificationSummary().getId();
ClassificationSummary classification = ClassificationSummary classification = classificationSummariesById.get(classificationId);
classifications.stream()
.filter(c -> c.getId().equals(classificationId))
.findFirst()
.orElse(null);
if (classification == null) { if (classification == null) {
throw new SystemException( throw new SystemException(
"Could not find a Classification for task " + resultTask.getId()); "Could not find a Classification for task " + resultTask.getId());
@ -969,8 +965,8 @@ public class TaskServiceImpl implements TaskService {
private List<TaskSummaryImpl> augmentTaskSummariesByContainedSummariesWithoutPartitioning( private List<TaskSummaryImpl> augmentTaskSummariesByContainedSummariesWithoutPartitioning(
List<TaskSummaryImpl> taskSummaries) { List<TaskSummaryImpl> taskSummaries) {
List<String> taskIds = Set<String> taskIds =
taskSummaries.stream().map(TaskSummaryImpl::getId).distinct().collect(Collectors.toList()); taskSummaries.stream().map(TaskSummaryImpl::getId).collect(Collectors.toSet());
if (taskIds.isEmpty()) { if (taskIds.isEmpty()) {
taskIds = null; taskIds = null;
@ -982,15 +978,17 @@ public class TaskServiceImpl implements TaskService {
+ "about to query for attachmentSummaries ", + "about to query for attachmentSummaries ",
taskSummaries); taskSummaries);
} }
List<AttachmentSummaryImpl> attachmentSummaries = List<AttachmentSummaryImpl> attachmentSummaries =
attachmentMapper.findAttachmentSummariesByTaskIds(taskIds); attachmentMapper.findAttachmentSummariesByTaskIds(taskIds);
Map<String, ClassificationSummary> classificationSummariesById =
List<ClassificationSummary> classifications =
findClassificationsForTasksAndAttachments(taskSummaries, attachmentSummaries); findClassificationsForTasksAndAttachments(taskSummaries, attachmentSummaries);
Map<String, WorkbasketSummary> workbasketSummariesById = findWorkbasketsForTasks(taskSummaries);
addClassificationSummariesToTaskSummaries(taskSummaries, classifications); addClassificationSummariesToAttachments(attachmentSummaries, classificationSummariesById);
addWorkbasketSummariesToTaskSummaries(taskSummaries); addClassificationSummariesToTaskSummaries(taskSummaries, classificationSummariesById);
addAttachmentSummariesToTaskSummaries(taskSummaries, attachmentSummaries, classifications); addWorkbasketSummariesToTaskSummaries(taskSummaries, workbasketSummariesById);
addAttachmentSummariesToTaskSummaries(taskSummaries, attachmentSummaries);
return taskSummaries; return taskSummaries;
} }
@ -1541,184 +1539,159 @@ public class TaskServiceImpl implements TaskService {
} }
} }
private void addClassificationSummariesToTaskSummaries( private Map<String, WorkbasketSummary> findWorkbasketsForTasks(
List<TaskSummaryImpl> tasks, List<ClassificationSummary> classifications) { List<? extends TaskSummary> taskSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
if (tasks == null || tasks.isEmpty()) { return Collections.emptyMap();
return;
}
// assign query results to appropriate tasks.
for (TaskSummaryImpl task : tasks) {
String classificationId = task.getClassificationSummary().getId();
ClassificationSummary classificationSummary =
classifications.stream()
.filter(c -> c.getId().equals(classificationId))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
throw new SystemException(
"Did not find a Classification for task (Id="
+ task.getId()
+ ",classification="
+ task.getClassificationSummary().getId()
+ ")");
}
// set the classification on the task object
task.setClassificationSummary(classificationSummary);
}
} }
private List<ClassificationSummary> findClassificationsForTasksAndAttachments( Set<String> workbasketIds =
List<? extends TaskSummaryImpl> taskSummaries, taskSummaries.stream()
.map(TaskSummary::getWorkbasketSummary)
.map(WorkbasketSummary::getId)
.collect(Collectors.toSet());
return queryWorkbasketsForTasks(workbasketIds).stream()
.collect(Collectors.toMap(WorkbasketSummary::getId, Function.identity()));
}
private Map<String, ClassificationSummary> findClassificationsForTasksAndAttachments(
List<? extends TaskSummary> taskSummaries,
List<? extends AttachmentSummaryImpl> attachmentSummaries) { List<? extends AttachmentSummaryImpl> attachmentSummaries) {
if (taskSummaries == null || taskSummaries.isEmpty()) { if (taskSummaries == null || taskSummaries.isEmpty()) {
return new ArrayList<>(); return Collections.emptyMap();
} }
Set<String> classificationIdSet = Set<String> classificationIds =
Stream.concat( Stream.concat(
taskSummaries.stream().map(TaskSummary::getClassificationSummary), taskSummaries.stream().map(TaskSummary::getClassificationSummary),
attachmentSummaries.stream().map(AttachmentSummary::getClassificationSummary)) attachmentSummaries.stream().map(AttachmentSummary::getClassificationSummary))
.map(ClassificationSummary::getId) .map(ClassificationSummary::getId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
return queryClassificationsForTasksAndAttachments(classificationIdSet); return queryClassificationsForTasksAndAttachments(classificationIds).stream()
.collect(Collectors.toMap(ClassificationSummary::getId, Function.identity()));
} }
private List<ClassificationSummary> findClassificationForTaskImplAndAttachments( private Map<String, ClassificationSummary> findClassificationForTaskImplAndAttachments(
TaskImpl task, List<AttachmentImpl> attachmentImpls) { TaskImpl task, List<AttachmentImpl> attachmentImpls) {
return findClassificationsForTasksAndAttachments( return findClassificationsForTasksAndAttachments(
Collections.singletonList(task), attachmentImpls); Collections.singletonList(task), attachmentImpls);
} }
private List<ClassificationSummary> queryClassificationsForTasksAndAttachments( private List<ClassificationSummary> queryClassificationsForTasksAndAttachments(
Set<String> classificationIdSet) { Set<String> classificationIds) {
String[] classificationIdArray = classificationIdSet.toArray(new String[0]);
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug( LOGGER.debug(
"getClassificationsForTasksAndAttachments() about to query classifications and exit"); "queryClassificationsForTasksAndAttachments() about to query classifications and exit");
} }
// perform classification query
return this.classificationService return this.classificationService
.createClassificationQuery() .createClassificationQuery()
.idIn(classificationIdArray) .idIn(classificationIds.toArray(new String[0]))
.list(); .list();
} }
private void addWorkbasketSummariesToTaskSummaries(List<TaskSummaryImpl> taskSummaries) { private List<WorkbasketSummary> queryWorkbasketsForTasks(Set<String> workbasketIds) {
if (taskSummaries == null || taskSummaries.isEmpty()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("queryWorkbasketsForTasks() about to query workbaskets and exit");
}
// perform classification query
return this.workbasketService
.createWorkbasketQuery()
.idIn(workbasketIds.toArray(new String[0]))
.list();
}
private void addClassificationSummariesToTaskSummaries(
List<TaskSummaryImpl> tasks, Map<String, ClassificationSummary> classificationSummaryById) {
if (tasks == null || tasks.isEmpty()) {
return; return;
} }
// calculate parameters for workbasket query: workbasket keys
String[] workbasketIdArray = for (TaskSummaryImpl task : tasks) {
taskSummaries.stream() String classificationId = task.getClassificationSummary().getId();
.map(t -> t.getWorkbasketSummary().getId()) ClassificationSummary classificationSummary = classificationSummaryById.get(classificationId);
.distinct() if (classificationSummary == null) {
.toArray(String[]::new); throw new SystemException(
if (LOGGER.isDebugEnabled()) { "Did not find a Classification for task (Id="
LOGGER.debug("addWorkbasketSummariesToTaskSummaries() about to query workbaskets"); + task.getId()
+ ",Classification="
+ task.getClassificationSummary().getId()
+ ")");
}
task.setClassificationSummary(classificationSummary);
}
} }
WorkbasketQueryImpl query = (WorkbasketQueryImpl) workbasketService.createWorkbasketQuery();
query.setUsedToAugmentTasks(true);
List<WorkbasketSummary> workbaskets = query.idIn(workbasketIdArray).list(); private void addWorkbasketSummariesToTaskSummaries(
Iterator<TaskSummaryImpl> taskIterator = taskSummaries.iterator(); List<TaskSummaryImpl> tasks, Map<String, WorkbasketSummary> workbasketSummaryById) {
while (taskIterator.hasNext()) { if (tasks == null || tasks.isEmpty()) {
TaskSummaryImpl task = taskIterator.next(); return;
String workbasketId = task.getWorkbasketSummaryImpl().getId(); }
WorkbasketSummary workbasketSummary = for (TaskSummaryImpl task : tasks) {
workbaskets.stream() String workbasketId = task.getWorkbasketSummary().getId();
.filter(x -> workbasketId != null && workbasketId.equals(x.getId())) WorkbasketSummary workbasketSummary = workbasketSummaryById.get(workbasketId);
.findFirst()
.orElse(null);
if (workbasketSummary == null) { if (workbasketSummary == null) {
LOGGER.warn("Could not find a Workbasket for task {}.", task.getId()); throw new SystemException(
taskIterator.remove(); "Did not find a Workbasket for task (Id="
continue; + task.getId()
+ ",Workbasket="
+ task.getWorkbasketSummary().getId()
+ ")");
} }
task.setWorkbasketSummary(workbasketSummary); task.setWorkbasketSummary(workbasketSummary);
} }
} }
private void addAttachmentSummariesToTaskSummaries( private void addAttachmentSummariesToTaskSummaries(
List<TaskSummaryImpl> taskSummaries, List<TaskSummaryImpl> taskSummaries, List<AttachmentSummaryImpl> attachmentSummaries) {
List<AttachmentSummaryImpl> attachmentSummaries,
List<ClassificationSummary> classifications) {
if (taskSummaries == null || taskSummaries.isEmpty()) { if (taskSummaries == null || taskSummaries.isEmpty()) {
return; return;
} }
// augment attachment summaries by classification summaries Map<String, TaskSummaryImpl> taskSummariesById =
// Note: taskSummaries.stream()
// the mapper sets for each Attachment summary the property classificationSummary.key from the .collect(
// CLASSIFICATION_KEY property in the DB Collectors.toMap(
addClassificationSummariesToAttachmentSummaries( TaskSummary::getId,
attachmentSummaries, taskSummaries, classifications); Function.identity(),
// assign attachment summaries to task summaries // Currently, we still have a bug (TSK-1204), where the TaskQuery#list function
for (TaskSummaryImpl task : taskSummaries) { // returns the same task multiple times when that task has more than one
for (AttachmentSummaryImpl attachment : attachmentSummaries) { // attachment...Therefore, this MergeFunction is necessary.
if (attachment.getTaskId() != null && attachment.getTaskId().equals(task.getId())) { (a, b) -> b));
task.addAttachmentSummary(attachment);
} for (AttachmentSummaryImpl attachmentSummary : attachmentSummaries) {
String taskId = attachmentSummary.getTaskId();
TaskSummaryImpl taskSummary = taskSummariesById.get(taskId);
if (taskSummary != null) {
taskSummary.addAttachmentSummary(attachmentSummary);
} }
} }
} }
private void addClassificationSummariesToAttachmentSummaries( private void addClassificationSummariesToAttachments(
List<AttachmentSummaryImpl> attachmentSummaries, List<? extends AttachmentSummaryImpl> attachments,
List<TaskSummaryImpl> taskSummaries, Map<String, ClassificationSummary> classificationSummariesById) {
List<ClassificationSummary> classifications) {
// prereq: in each attachmentSummary, the classificationSummary.key property is set. if (attachments == null || attachments.isEmpty()) {
if (attachmentSummaries == null
|| attachmentSummaries.isEmpty()
|| taskSummaries == null
|| taskSummaries.isEmpty()) {
return; return;
} }
// iterate over all attachment summaries an add the appropriate classification summary to each
for (AttachmentSummaryImpl att : attachmentSummaries) { for (AttachmentSummaryImpl attachment : attachments) {
String classificationId = att.getClassificationSummary().getId(); String classificationId = attachment.getClassificationSummary().getId();
ClassificationSummary classificationSummary = ClassificationSummary classificationSummary =
classifications.stream() classificationSummariesById.get(classificationId);
.filter(x -> classificationId != null && classificationId.equals(x.getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) {
throw new SystemException("Could not find a Classification for attachment " + att);
}
att.setClassificationSummary(classificationSummary);
}
}
private List<Attachment> addClassificationSummariesToAttachments(
List<AttachmentImpl> attachmentImpls, List<ClassificationSummary> classifications) {
if (attachmentImpls == null || attachmentImpls.isEmpty()) {
return new ArrayList<>();
}
List<Attachment> result = new ArrayList<>();
for (AttachmentImpl att : attachmentImpls) {
// find the associated task to use the correct domain
ClassificationSummary classificationSummary =
classifications.stream()
.filter(c -> c != null && c.getId().equals(att.getClassificationSummary().getId()))
.findFirst()
.orElse(null);
if (classificationSummary == null) { if (classificationSummary == null) {
throw new SystemException("Could not find a Classification for attachment " + att); throw new SystemException("Could not find a Classification for attachment " + attachment);
} }
att.setClassificationSummary(classificationSummary); attachment.setClassificationSummary(classificationSummary);
result.add(att);
} }
return result;
} }
private TaskImpl initUpdatedTask( private TaskImpl initUpdatedTask(