In this page

Overview

This page describes tuning Better PDF Exporter for Jira for improved performance. It most importantly explains techniques to create custom PDF templates that can efficiently export large amount of data from Jira. By "large amount" we mean PDF files with 500 - 10,000 pages.

If you export smaller data sets using your custom template, you will unlikely face performance problems. In that case, you can just skip this page.

Please note that albeit we do our best to make sure that the Better PDF Exporter app performs well under a wide variety of circumstances and the techniques below are generally good practices, every template and every situation may have its unique performance characteristics.

If you are having performance problems:

  1. Read this page and make those practical changes that are applicable to your template
  2. Ask for our help

Techniques

Use short page sequences

The <fo:page-sequence> FO object is used as a container for page output elements. A page sequence specifies how a series of pages is created (e.g. page dimensions) and laid out within the document.

Somewhat similar to database transactions, this is adviced to close page sequences as fast as possible for efficient resource management. When you close a page sequence, the followings happen:

  1. Visible: with the next page sequence a new page will start, effectively resulting in a page break.
  2. Invisible: the PDF renderer's layout engine releases all the memory that was previously allocated to calculate the layout for the current page sequence. With many pages, many paragraphs and other page elements, many words to break, this can grow very large.

Therefore a scalable PDF template should create a larger number of short page sequences instead of a lower number of long ones.

For example, the default issue-fo.vm template creates one page sequence per issue. (If the user wants to avoid the page breaks between the issues, this is possible to configure the template that way. In that case, the document will be a single page sequence, eventually putting higher load for the layout engine.)

Use auto-paging

Before this technique, please make sure that you understood the previous one and the importance of short page sequences.

There are situations when creating multiple page sequences is necessary, but not intuitive! For instance, the issue-navigator-fo.vm exports a potentially long table (with one row per issue) which is not straight-forward to break into page sequences. Where would you intuitively break a table with 10,000 rows?

The problem is that a table that spans over 200 pages requires quite some memory to be laid out correctly: when the renderer is creating the 200th page, it still needs to maintain and eventually re-layout the very first one! To lower the load by creating shorter page sequences, we implemented a simple technique called "auto-paging" in issue-navigator-fo.vm. You can use this as sample when facing similar problems in your custom template.

The idea of "auto-paging" is actually very simple:

  1. If the number of issues is less than a configurable limit specified by $startAutoPagingAt, the whole table is a single page sequence. We don't expect any problem for a smaller data set.
  2. If the number of issue exceeds that limit, the template will create a new page sequence after every Nth issue. The batch size can be configured via $rowsPerPage.

That's it. If you export 5000 issues, then the first 32 will go into the first page sequence, second 32 into the second, and so on. In other words, there is an "upper limit" set for a page sequence's complexity.

It scales really nicely to even tens of thousands of pages with one minor issue. Evey time the batch size is reached, the template will start a new page sequence which will cause a page break at that point. If this happens in the middle of the current page, then you will get a half-page blank area. This is the price of linear scaling, and you only need to pay it for large exports exceeding the configurable limit. So it makes sense.

You can configure it via the following variables in issue-navigator-fo.vm:

#set($startAutoPagingAt = 1000) ## when issue count is equal to or greater than this, the template will start "auto paging" for optimization
#set($rowsPerPage = 32) ## number of rows per page at "auto paging" (see previous)

Use "wide" blocks

To reduce the load on the text breaking engine, use "wide" blocks (i.e. blocks with an appropriate horizontal dimension) instead of narrow ones. When you force long piences of texts into narrow (even if tall) blocks, the engine will evaluate lots of potential solutions to find the best way to break the text. This is inefficient and should be avoided.

Important: by "block" we mean not only <fo:block> elements, but all non-inline elements, including table cells, as well.

For example, if the columns in your table become narrow when rendered in "A4" page size, you could:

  1. Turn the page to landscape orientation.
  2. Change to "A3" page size.
  3. Change to landscape orientation and "A3" page size.

Abbreviate long texts

As mentioned in the previous technique, long texts in narrow blocks are killing performance. (Long texts in wide blocks are no problem.) So, if you have little space, you could keep the text short by cutting that after the Nth character.

For example, keeping only the first 100 characters of the issue summary is as simple as:

<fo:block>$xmlutils.escape($stringutils.abbreviate($issue.summary, 100))</fo:block>

Disable complex scripts

Complex scripts is a feature in the PDF renderer that helps working with right-to-left writing (e.g. Arabic) and South-Asian languages (e.g. Thai). This is enabled by default and it will somewhat increase the memory requirements for processing documents.

If you are working with European languages only, you may want to turn it off:

  1. Login to Jira as administrator.
  2. Go to AdministrationAdd-onsPDF Templates.
  3. Open the fop-config.xml file.
  4. Apply the change explained at point 3 in the Disabling complex scripts section.
  5. Save your changes.

Although it may not make a big difference, this is easy to turn it off, so it is worth to try.

Increase the application memory (heap size)

Exporting can be a extremely memory-intensive task, depending on the scale of the data and the complexity of the template.

Why? While generating the final PDF file, the complete PDF document model is kept in memory so that the layout engine can go back and forth to any little piece in any page and re-calculate the whole document layout at any time during the rendering process. Note that storing the document in memory using a mutable and structured representation consumes much more memory than when that is finally written to the final PDF file. In other words, the process of creating a PDF file can require several magnitude more memory than its final filesize.

There are two typical symptoms of not giving enough memory for the renderer:

  1. The export file is created successfully, but it requires more time than it should, as the Java Virtual Machine (JVM) gets busy with memory management and garbage collection.
  2. The JVM may eventually run out of memory, resulting in a classic Java OutOfMemoryError.

The solution is trivial: increase the available memory following Atlassian's guide.

Note:

  • If you have a non-optimized template (see all other items!), it will not magically "fix" that, but even in that case it will mitigate the problems caused by inefficiency.

Other techniques

Limit the number of concurrent PDF renderings

Exporting large PDF documents can be resource intensive. Most importantly, it may require lots of memory to compile the in-memory document model. Although the memory will be released after the document rendering is completed, the rendering process itself may put significant load to the Jira server temporarily.

If you run multiple large PDF document renderings at the same time (concurrently), the individual loads will effectively add up, therefore the total load can be even more significant. But note that typical PDFs with 1-20 pages are not generating significant load, not even when generated concurrently.

To control the total load, it is possible to set the maximum number of concurrent renderings via the standard Java system property called jpdf.threads.maxConcurrent. You should set this system property to an integer like 3 or 5. For a detailed how-to, please see the Setting properties and options on startup page in the official Jira documentation.

How does it work? If you set this to 1 (most extreme setting), then the second user trying to export a PDF concurrently with an already running export will receive a mini PDF (which is inexpensive to create by the app). The mini PDF explains that he needs to wait a bit until the first rendering completes. If he re-tries the export later, he will receive the actual PDF document.

It's that simple.

Limit the number of concurrent PDF renderings in automation rules

Note that limiting the concurrent renderings may lead to unwanted behavior when used with certain types of automations.

When using the Scheduled trigger and unchecking the Process all issues produced by this trigger in bulk option, the Automation app will process all issue separately and with parallel threads. That means, if you want to export 100 issues each to its own PDF document, then rendering the 100 PDF documents will be attempted to renderer in parallel and will meet the limit immediately. This is "as expected", but we wanted to mention this explicitly.

Questions?

Ask us any time.