How to make rules more efficient
1. Modified fields contain fields that have no influence on rule execution result.
Example:
Rule reacts to Feature
creation and modification.
Modified fields filters contains fields: Release
, Effort
.
Rule syncs release of child User Stories
Rule action:
const api = context.getService("targetprocess/api/v2");
const featureId = args.ResourceId;
const releaseId = args.Current.Release ? args.Current.Release.Id : null;
const measurements = await api.queryAsync("Userstory", {
select: "id",
where: `feature.id = ${featureId}`
});
return measurements.map(id => {
return {
command: "targetprocess:UpdateResource",
payload: {
resourceType: "Userstory",
resourceId: id,
fields: {
Release: releaseId === null ? null : { id: releaseId }
}
}
}
});
Solution:
Rule execution does not depend on Effort
. So these fields can be excluded from modified fields filter to avoid not needed rule trigerrings.
2. Loops with http api requests that don't depend on loop iteration.
Example:
const id = 1;
...
for (let i = 0; i++; i < countOfIterations) {
...
// This api request does not depend on iteration of the loop
const api = require("targetprocess/api/v2");
const response = await api.queryAsync("feature", {
select: "name",
where: `feature.id = ${id}`
});
...
}
Solution:
Move independent request outside the loop:
const id = 1;
const api = require("targetprocess/api/v2");
const response = await api.queryAsync("userstory", {
select: "name",
where: `feature.id = ${id}`
});
...
for (let i = 0; i++; i < countOfIterations) {
...
}
3. Several http api requests, that can be merged into one request.
Example:
const api = require("targetprocess/api/v2");
const featureIds = [1, 2, 3, 4, 5];
...
for (let featureId of featureIds) {
...
const userStoriesForFeature = await api.queryAsync("userstory", {
select: "name",
where: `feature.id = ${featureId}`
});
...
}
It doesn't really makes sense to send separate requests for user stories here. They all can be merged into single one and filtered by feature.id later if necessary.
Solution:
const api = require("targetprocess/api/v2");
const featureIds = [1, 2, 3, 4, 5];
const allUsertories = await api.queryAsync("userstory", {
select: "name,feature:{feature.id}",
where: `feature.id in [${featureIds.join(',')}]`
});
...
for (let featureId of featureIds) {
...
userStoriesForFeature = allUsertories.filter(us => us.feature.id = featureId);
...
}
4. Sequential http independent api requests, that can executed in parallel.
Example 1:
const api = require("targetprocess/api/v2");
const projects = await api.queryAsync("project", {
select: "name",
where: `id = 1`
});
const features = await api.queryAsync("feature", {
select: "name",
where: `effort > 0`
});
const stories = await api.queryAsync("userstory", {
select: "name",
where: `tasks.count > 0`
});
Requests for projects, features and stories do not depend on each other here.
We don't need to wait for one request to finish to start next one.
Solution:
const api = require("targetprocess/api/v2");
const [projects, features, stories] = await Promise.all([
api.queryAsync("project", {
select: "name",
where: `id = 1`
}),
api.queryAsync("feature", {
select: "name",
where: `effort > 0`
}),
api.queryAsync("userstory", {
select: "name",
where: `tasks.count > 0`
})]);
Example 2. Sequential http independent api requests, that depend on loop iteration:
const api = require("targetprocess/api/v2");
const entityTypesToRead = ['project', 'feature', 'userstory'];
for (let entityType of entityTypesToRead) {
//Part one work in loop
...
const someComlexIterationDependentFilter = ...;
const response = await api.queryAsync(entityType, {
select: "name",
where: someComlexIterationDependentFilter
});
//Part two work in loop
...
}
Requests in loop do not depend on each others results. We can execute them in parallel manner.
Solution:
const api = require("targetprocess/api/v2");
const entityTypesToRead = ['project', 'feature', 'userstory'];
await Promise.all(entityTypesToRead.map(async entityType => {
//Part one work in loop
...
const someComlexIterationDependentFilter = ...;
const response = await api.queryAsync(entityType, {
select: "name",
where: someComlexIterationDependentFilter
});
//Part two work in loop
...
}));
5. Reading too much not needed data
Here we have JavaScript action of a rule reacts to update of UserStory. And we need to read some custom field of parent project.
Example
...
const entityId = args.ResourceId;
const entityType = args.Current.EntityType.Name;
const projectId = args.Current.Project.Id;
const api = context.getService("targetprocess/api/v2");
const projects = await api.queryAsync("UserStory", {
select: "project.ProjectCF",
where: `project.id=${projectId}`
});
const projectCf = projects[0];
...
There is no need to read all user stories of the parent project here. We can read project directly.
Solution:
...
const entityId = args.ResourceId;
const entityName = args.Current.Name;
const entityType = args.Current.EntityType.Name;
const projectId = args.Current.Project.Id;
const api = context.getService("targetprocess/api/v2");
const projects = await api.queryAsync("Project", {
select: "ProjectCF",
where: `id=${projectId}`
});
const projectCf = projects[0];
...
6. CPU Heavy operations in nested loops
Example 1
We have rule that syncronizes planned start/end dates across some hierarchy.
It uses function getDateTime that parses date from string via reqexp inside loop
function getDateTime(d) {
return new Date(/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d)([+-][0-2]\d:[0-5]\d|Z)/.exec(d)[1]);
};
...
for (let i = 0; i < countOfIterations; i++) {
...
for (let j = 0; j < countOfIterations2; j++) {
...
const someDate = getDateTime(someDateStr);
...
}
...
}
Using regular expression for parsing is really expensive in terms of CPU usage. Using it in a loop or nested loop may lead to extreme CPU consumption by rule. If there is a heuristic that date strings in similar example have good chance to repeat at different iterations of loop, a good solution for reducing CPU consumption may be memorization.
Solution:
const getDateTimeMemo = new Map();
const regexp = new RegExp(/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d)([+-][0-2]\d:[0-5]\d|Z)/);
function getDateTimeMemoized(d) {
let date = getDateTimeMemo.get(d);
if (!date) {
date = new Date(regexp.exec(d)[1]);
getDateTimeMemo.set(d, date);
}
return date;
};
...
for (let i = 0; i < countOfIterations; i++) {
...
for (let j = 0; j < countOfIterations2; j++) {
...
const someDate = getDateTimeMemoized(someDateStr);
...
}
...
}
Updated over 2 years ago