1.20 release notes
RSSDocument Engine 1.20 helps busy deployments remain responsive when processing large documents or many remote files. It adds new controls for resource loading, and APIs for search and PDF layers now behave more consistently.
Before attempting to upgrade to Document Engine 1.20, make sure your application runs as expected on your current version. If you’re on version 1.6.1 or later, you can upgrade directly to 1.20. If you’re on an earlier version, follow the step-by-step upgrade path outlined in our upgrade(opens in a new tab) guide.
Breaking changes
This release includes the following breaking changes.
Overloaded remote-URL fetches return 503
A Build request rejected because the remote-URL fetch pool is exhausted now returns 503 instead of 400, with reason set to overload. Integrations that treat this condition as a client error, or that match on 400 to detect it, must handle 503 and retry instead.
IAM roles for service accounts use the regional STS endpoint
With AWS Identity and Access Management (IAM) roles for service accounts, Document Engine now requests credentials from the AWS Security Token Service (STS) endpoint in the ASSET_STORAGE_S3_REGION region — for example, sts.eu-central-1.amazonaws.com. Previously, it always used sts.us-east-1.amazonaws.com. If your network allows outbound traffic only to specific hosts, allow the STS host of your bucket region before you upgrade.
STS must also be active in that region for the AWS account that owns the role. AWS activates STS by default in most regions, but an administrator can deactivate it. If STS isn’t active, the credential request fails with RegionDisabled. See AWS STS Regions and endpoints(opens in a new tab).
Renders without ocg_layers use the document’s layer visibility
The optional ocg_layers parameter for rendering page images, covers, and tiles determines which PDF layers are shown. Previously, the effect of omitting it was unspecified. Before 1.17.0, Document Engine used the PDF’s default layer visibilities when you omitted the parameter. Version 1.17.0 changed this to show all layers. Because some documents hide layers such as guides by default, the original behavior is the more expected one. Document Engine 1.20 switches back to the PDF’s default layer visibilities and documents this as the expected behavior.
Web SDK viewers connected to Document Engine initially show the document’s default layers. Page image and cover requests can render a different set by listing the layers in ocg_layers. Browsers may keep showing covers and tiles they cached before the upgrade for up to a day.
SYNC_ENGINE removed
Document Engine no longer supports the SYNC_ENGINE environment variable. It selected between two implementations of Instant sync writes. SYNC_ENGINE=hooks has been the default since December 2023, and SYNC_ENGINE=old selected the legacy implementation.
Document Engine now always uses the default implementation and ignores SYNC_ENGINE. Deployments that don’t set it see no change. Deployments that set SYNC_ENGINE=old start normally and use the default implementation. No data migration is required. You can remove the variable from your configuration.
SVG images don’t load external resources by default
When Document Engine converts or processes a document with GdPicture, SVG images no longer load external resources. This applies to SVG files and to SVG images inside other formats, such as Word documents. Previously, an SVG image could load pictures and other SVG content from local files and from HTTP and HTTPS URLs. Now, an SVG image can use only the content inside it, such as data: URIs and references to elements in the same file. Document Engine skips other references, and the conversion continues without them.
If your documents need external SVG resources, set the new SVG_EXTERNAL_RESOURCES option:
local_filesloads files from the file system of Document Engine.allalso loads HTTP and HTTPS URLs. Redirects aren’t followed.
REMOTE_URL_FETCH_POLICY doesn’t apply to these requests, and all doesn’t filter destination addresses. An SVG image can then make Document Engine send requests to private, loopback, and link-local addresses. Use all only when you trust the source of every document.
Deprecations
This release doesn’t include any deprecations.
Async jobs finish after retry exhaustion
Async jobs now report failed when their final processing attempt is discarded, including after worker crashes or timeouts. Regular async job cleanup updates jobs already left in_progress after their background job was discarded or removed.
Non-ASCII download file names
PDF downloads from GET /api/documents/:documentId/pdf, POST /api/documents/:documentId/pdf, and POST /api/documents/:documentId/pdf_with_operations now send an RFC 6266 compliant Content-Disposition header when the document title contains non-ASCII or other unsafe characters. Previously, the title was written into the header as raw UTF-8, which strict HTTP clients and proxies could reject or mangle.
The header now carries an ASCII-only filename fallback and a percent-encoded filename* parameter with the exact name, which current browsers and HTTP clients use. Download file names longer than 255 bytes are shortened to 255 bytes, keeping the file name extension.
First-page prerendering limits
With the Redis tile cache enabled, Document Engine prerenders the tiles of a document’s first page in the background whenever a client opens the document. Each prerendering uses one pspdfkitd worker until the worker renders all the tiles. For a very large first page, this can take thousands of tiles. If clients open several such documents at once, their prerenderings could take every worker, and tile requests from other clients then fail or slow down.
Document Engine now runs at most two first-page prerenderings at the same time on each instance. When the limit is reached, opening another document skips its prerendering, and Document Engine renders its tiles when clients request them. A prerendering also stops when a worker can’t be used, instead of logging a crash.
Two new options control this behavior:
FIRST_PAGE_PRERENDERING_ENABLED— Set it tofalseto stop first-page prerendering but keep the Redis tile cache. The default istrue. Prerendering needs aPSPDFKIT_WORKER_POOL_SIZEof at least2. With fewer workers, Document Engine doesn’t prerender and logs a warning at startup.FIRST_PAGE_PRERENDERING_MAX_CONCURRENCY— The maximum number of prerenderings that run at the same time. It’s clamped to one less thanPSPDFKIT_WORKER_POOL_SIZE. The default is2.
Two Prometheus metrics show the effect:
first_page_prerendering_duration_milliseconds— How long each prerendering took.first_page_prerendering_skipped_total{reason="max_concurrency"}— How many prerenderings were skipped because the limit was reached.
FTS index builds run with the default configuration
In earlier releases, ASYNC_WORKER_QUEUES replaced the complete list of background job queues on a node. This also removed the internal search_index queue, including with the default value default=5. With SEARCH_FTS_BUILD_ON_WRITE or SEARCH_FTS_ENABLED set, full-text search (FTS) index build jobs stayed in the available state. Document Engine built no index, and search continued to use the per-query scan.
Document Engine now always processes the internal search_index queue on every node that uses persistent database storage. ASYNC_WORKER_QUEUES controls only async operation queues. Build jobs that are already waiting run after the upgrade. You don’t need to recreate documents.
If you added search_index to ASYNC_WORKER_QUEUES as a workaround, you can remove it. Document Engine ignores internal queues in ASYNC_WORKER_QUEUES and ASYNC_ADMISSION_QUEUES and logs a warning at startup. Async operations can’t select an internal queue.
A new option keeps index builds off a node:
SEARCH_FTS_BUILD_WORKER— Set it tofalseto stop this node from processing FTS index build jobs. The default istrue. At least one node must keep the default value. When clustering is enabled, a build runs on the document owner node, which can be a node with this option set tofalse. To keep builds off a node completely, process them only on worker nodes withCLUSTERING_ENABLED=false. Theasync_jobs_queue_state_jobsmetric withqueue="search_index"andstate="available"shows build jobs that no node processes.
Full-text search returns the same results as the regular search
With SEARCH_FTS_ENABLED=true, plain text searches now return the same pages, preview text, highlight ranges, and highlight rectangles as with the FTS index disabled.
Previously, a phrase search returned each word of the phrase separately, and a word also matched longer words with the same stem. Highlights ended before the end of the word, and matches inside a word or across a line break were missing.
The index makes searches on large documents faster when the search term occurs on few pages. In our tests on a 2,000-page document, a search for a name that occurs on about 100 pages took 0.3 seconds instead of 5 seconds. A search term that occurs on most pages takes about as long as with the index disabled.
Document Engine builds new indexes after the upgrade, and each later release builds its own indexes too. Until a document has an index for the running release, its searches take as long as with the index disabled. The first search enqueues an index build. When Document Engine stores the new index, it deletes the indexes of earlier releases for the same PDF from asset storage. It deletes only indexes older than one day.
Configurable remote-URL downloading
Document Engine fetches documents from a remote URL through a fixed-size pool, shared by Build API URL inputs and documents added by URL. That pool and its queue are now configurable:
DOWNLOADER_WORKER_POOL_SIZEsets how many fetches run at once, per node. It defaults to16.DOWNLOADER_BROKER_CLIENT_QUEUE_TIMEOUTsets how long, in milliseconds, a fetch waits for a free worker before it is rejected. It defaults to5000.DOWNLOADER_BROKER_CLIENT_QUEUE_MAX_SIZEsets how many fetches can wait at once. It defaults to128.
The defaults match the values Document Engine used before, so an existing deployment behaves as it did.
A Build request rejected because the pool is exhausted now returns 503 instead of 400, with the failing input named in failingPaths. A 400 told callers the request itself was malformed, which stopped them from retrying work that would have succeeded moments later. Asynchronous Build jobs retry a rejected fetch rather than failing on the first attempt.
Database migrations
This release adds an index on async_jobs.oban_job_id for unfinished async jobs. It helps the async job reaper find jobs whose background processing has ended without updating their public status. The migration creates the index concurrently, so it doesn’t block normal reads and writes to the table. No manual action is required.
This release also removes two redundant indexes from the records table, records_layer_id_form_field_content_idx and records_layer_id_idx1. Neither narrows a query beyond the layer lookup that the remaining records indexes provide, so removing them reduces storage and per-write index maintenance. The migration drops both concurrently, so it doesn’t block reads or writes to the table. It also completes cleanly on databases where they were already removed. No manual action is required.