Then we need to create the sync-script as sync.js
file.
const axios = require("axios");
const { uniqueNamesGenerator, adjectives, colors, animals } = require("unique-names-generator");
const jiraContext = {
jiraBaseUrl: "https://jira-test-ax2421s.codeclou.com", // without trailing slash
customFieldId: 10336,
contextId: 10436,
username: "admin",
password: "adminpw",
};
const generateRandomOptionname = () => {
return uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
};
const getBaseUrlAndAuthForAxios = (jiraContext) => {
return {
restBaseUrl: `${jiraContext.jiraBaseUrl}/rest/jiracustomfieldeditorplugin/1`,
axiosOptions: {
auth: {
username: jiraContext.username,
password: jiraContext.password,
},
},
};
};
const createCustomfieldOption = async (jiraContext, optionValue) => {
const { restBaseUrl, axiosOptions } = getBaseUrlAndAuthForAxios(jiraContext);
try {
const res = await axios.post(
`${restBaseUrl}/user/customfields/${jiraContext.customFieldId}/contexts/${jiraContext.contextId}/options`,
{ optionvalue: optionValue },
axiosOptions
);
return res;
} catch (err) {
console.log(
"unexpected error during createCustomfieldOption",
err && err.response && err.response.data ? err.response.data : err
);
}
};
const getCurrentMilliSeconds = () => {
// this is more precise as new Date()
var hrTime = process.hrtime();
return Math.floor(hrTime[0] * 1000 + hrTime[1] / 1000000);
};
const main = async () => {
try {
const beforeBatch = getCurrentMilliSeconds();
for (let counter = 0; counter < 10000; counter++) {
const before = getCurrentMilliSeconds();
const optionValue = generateRandomOptionname() + "-" + counter;
await createCustomfieldOption(jiraContext, optionValue);
const after = getCurrentMilliSeconds();
console.log(`creating option ${optionValue} took ${after - before} ms`);
// Wait 100ms after each REST API Call to give Jira time to internally process data
await new Promise((resolve) => setTimeout(resolve, 100));
}
const afterBatch = getCurrentMilliSeconds();
console.log("BATCH TOOK MS:");
console.log(afterBatch - beforeBatch);
} catch (err) {
console.log("unexpected error", err);
}
};
main();
Now we can execute the sync-script with NodeJS.
The script prints the overall time it took in milliseconds at the end. You can expect it to run 1.5 hours.
During the run we manually deleted and updated options on the same field and context to increase the load and to measure the response time of those operations.
The whole batch run took 110 minutes to create 10,000 options.
The environment on AWS was located in Oregon (us-west-2) and our machine that imported the data was located in Germany (therefore a higher network latency).
operation | average during run | average after run |
---|---|---|
create option | 650ms | 80ms |
update option | 250ms | 60ms |
delete option | 10,500ms | 10,000ms |
sort options | - | 10,000ms |
Avoid delete and instead disable options. Delete is an expensive operation that will take a long time since it has to internally reenumerate the sequence of all options.
Avoid sorting options alphabetically during the import run. Do this after your script finished importing all options. It is an expensive operation as well.
Do create, update or disable options. These operations are performance optimized since app version 2.9.1.
Try to add timeouts after each REST API Call of 100ms. Or after a batch of 100 options a longer wait of 3 seconds. This depends on the performance of your Jira instance.
If the REST API response times are very slow, the load on Jira might be to high or the Jira installation might not run on recommended hardware for the actual instance size (see below.)
Check your DataCenter infrastructure fo the recommended size profile and adjust your hardware to the recommendations accordingly. For example many customfields can slow down Jira. With 800+ customfields your setup should be of type Large or XLarge instance size.
Cleanup unused customfields, they slow down Jira. Read Managing custom fields in Jira effectively and cleanup your fields.
Run your imports during low load times on the weekend or at night.
Use Jira 8.13+ which contains performance optimizations for customfields. Also Customfield Editor for Jira 2.9.1 contains performance optimizations as well.
Should you not use the AWS CloudFormation template the following Jira config files might be interesting for you.
The database config dbconfig.xml
of Jira is autogenerated by the AWS CloudFormation template.
<?xml version="1.0" encoding="UTF-8"?>
<jira-database-config>
<name>defaultDS</name>
<delegator-name>default</delegator-name>
<database-type>postgres72</database-type>
<schema-name>public</schema-name>
<jdbc-datasource>
<url>jdbc:postgresql://jiradb.c23e.us-west-2.rds.amazonaws.com:5432/jira</url>
<username>atljira</username>
<password>Password123</password>
<driver-class>org.postgresql.Driver</driver-class>
<pool-min-size>20</pool-min-size>
<pool-max-size>20</pool-max-size>
<pool-min-idle>10</pool-min-idle>
<pool-max-idle>20</pool-max-idle>
<pool-max-wait>10000</pool-max-wait>
<validation-query>select 1</validation-query>
<time-between-eviction-runs-millis>60000</time-between-eviction-runs-millis>
<min-evictable-idle-time-millis>180000</min-evictable-idle-time-millis>
<pool-remove-abandoned>true</pool-remove-abandoned>
<pool-remove-abandoned-timeout>60</pool-remove-abandoned-timeout>
<pool-test-while-idle>true</pool-test-while-idle>
<pool-test-on-borrow>false</pool-test-on-borrow>
</jdbc-datasource>
</jira-database-config>
This is a reflection of what we entered during the CloudFormation setup (defaults).
Same goes for the setenv.sh
which is also auto generated and sets 12 GB of memory on each cluster node. Leaving 4 GB of memory free to the system for background tasks.
JVM_SUPPORT_RECOMMENDED_ARGS="-XX:+ExplicitGCInvokesConcurrent -XX:ReservedCodeCacheSize=512M"
# You can use variable below to modify garbage collector settings.
# For Java 8 we recommend default settings
# For Java 11 and relatively small heaps we recommend: -XX:+UseParallelGC
# For Java 11 and larger heaps we recommend: -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent
JVM_GC_ARGS="-XX:+ExplicitGCInvokesConcurrent"
# The following 2 settings control the minimum and maximum given to the JIRA Java virtual machine.
JVM_MINIMUM_MEMORY="12288m"
JVM_MAXIMUM_MEMORY="12288m"
# The following setting configures the size of JVM code cache.
# A high value of reserved size allows Jira to work with more installed apps.
JVM_CODE_CACHE_ARGS="-XX:InitialCodeCacheSize=32m -XX:ReservedCodeCacheSize=512m"
To summarize - your Jira instance should be up-to-date to run versions that include performance optimizations for customfields. This means running Jira 8.13+ and Customfield Editor for Jira 2.9.1+.
Lastly make sure your Jira instance meets the correct size recommendations of Atlassian.