Affected capability
Assets
Feature description
Related to #349, pimcore/pimcore#19057 and pimcore/pimcore#19382.
Context
While working on pimcore/pimcore#19057 and its implementation PR pimcore/pimcore#19382, I investigated why image-heavy Pimcore pages can sometimes take several seconds to render or display their images, especially when remote Flysystem storage is used.
The investigation identified two separate latency problems:
- Twig dimension resolution can read source or thumbnail files only to determine the HTML
width and height attributes.
- The first browser request for a missing thumbnail can perform the complete source download, image decode, transformation, encoding and storage write.
pimcore/pimcore#19382 is the separate fix for the first problem. It avoids physical file inspection when dimensions can be reproduced reliably.
This proposal addresses the second problem. It is intentionally separate because it introduces public configuration, a Messenger transport and new background-processing behavior, so it belongs on 2026.x.
Problem
Pimcore generates missing image thumbnails on demand. This is an important fallback, but it also means the first visitor requesting a common thumbnail can become the process that generates it.
A cold thumbnail request can involve:
browser requests missing thumbnail
→ load source from local or remote storage
→ materialize the source locally
→ decode through GD or Imagick
→ apply resize, crop and other transformations
→ encode JPEG, PNG, WebP or AVIF
→ write the derivative to thumbnail storage
→ update thumbnail status information
→ optionally dispatch image optimization
→ return the generated file
For large photographs, TIFF files, rasterized SVG files, responsive variants, automatic formats or remote storage, this can take several seconds.
Pimcore already provides pimcore:thumbnails:image for controlled catalog warming. There is currently no opt-in mechanism that automatically warms selected named thumbnail configurations after a new image upload or actual binary change.
Proposed behavior
Add an opt-in asynchronous pre-generation mechanism for explicitly selected named image thumbnail configurations.
Example configuration:
pimcore:
assets:
image:
thumbnails:
pre_generate:
definitions:
- hero
- card
max_variants_per_config: 25
max_definitions: 20
An empty definitions list keeps the feature disabled by default.
The first version should require explicit named configurations. Wildcards should not be accepted because they could unexpectedly generate every thumbnail definition in a project.
Processing flow
image uploaded or binary replaced
→ existing image-update processing completes
→ one message is dispatched per selected named configuration
→ dedicated thumbnail-generation workers process the messages
→ common derivatives normally exist before browsers request them
The feature should be triggered by the existing binary-change path, not by every asset save or version creation. Metadata-only changes and version-only saves should not intentionally enqueue another warming pass.
Existing on-demand generation remains the fallback:
worker unavailable
message delayed
message stale
configuration not selected
dynamic configuration used
variant limit exceeded
generation unsuccessful
→ existing on-demand generation handles the request
Dedicated Messenger transport
Pre-generation should use a dedicated asynchronous transport:
pimcore_image_thumbnail_generate
Thumbnail warming can be CPU-, memory- and temporary-disk-intensive. It should not compete with required asset processing, document processing or video work in pimcore_asset_update.
A separate transport allows independent:
- worker counts;
- CPU and memory limits;
- autoscaling;
- retry configuration;
- failed-message monitoring;
- queue-age alerts;
- graceful shutdown settings.
A synchronous transport would remain technically possible, but it would remove the intended workload isolation and should be discouraged in the documentation.
Message identity and stale-work protection
Each message should contain:
asset ID
persisted source checksum
data modification date
named thumbnail configuration
The worker reloads the image and validates the checksum and modification date before performing expensive work.
This makes old messages safe:
binary A queues warming
binary B replaces A
message for A is consumed later
→ stale message becomes a no-op
The producer should use the checksum already persisted during binary processing. It should not calculate a missing checksum through storage while dispatching the optional warming message.
If no persisted checksum is available, event-driven warming should be skipped. Normal on-demand generation and the existing command remain available.
Queue deduplication and generation coordination
Equivalent initial messages waiting for first delivery can be deduplicated using:
asset ID
+ checksum
+ data modification date
+ configuration name
This is a best-effort queue-load optimization, not the correctness boundary.
Once processing starts, Pimcore's existing deterministic thumbnail path and per-derivative generation lock remain authoritative. After acquiring the lock, the worker checks storage again before loading the source or writing the derivative.
The responsibilities remain separate:
message deduplication
→ reduce duplicate queued work
stale-message validation
→ reject obsolete source versions
existing derivative lock
→ coordinate physical generation
Variant planning
For each selected named configuration, the worker should generate a bounded, deduplicated set containing:
- the untouched base configuration;
- its configured high-resolution factor, including fractional values;
- default and media-query variants;
- integer DPR variants through
max_srcset_dpi_factor;
- enabled automatic output formats such as WebP and AVIF.
Equivalent physical outputs should be generated only once.
Work is bounded by:
max_definitions
×
max_variants_per_config
Proposed defaults:
20 selected definitions per image change
25 variants per named configuration
Installations processing large TIFF files, high-resolution photographs, rasterized SVG files, AVIF output or complex transformations can start with lower limits.
Source materialization
One asset and named-configuration message should use one lazy source snapshot.
The source should only be materialized after:
output path calculation
→ existing derivative check
→ derivative lock
→ second storage check
Therefore:
all derivatives already exist
→ no source read
first missing derivative
→ source materialized once
remaining missing variants in the message
→ reuse the same stable local source path
The snapshot should:
- verify that the local source exists;
- verify that it is readable and non-empty;
- preserve the source extension for extension-dependent image delegates;
- cache and rethrow source-wide materialization failures;
- use Pimcore-managed temporary files;
- clean temporary files before a retry.
Source reuse is scoped to one asset and one named-configuration message. Different named configurations intentionally remain separate jobs for bounded execution and retry granularity.
Each output variant still performs its own image decode and transformation. Decoded-image reuse is outside this proposal.
Failure behavior
Temporary image-processing and storage failures should be visible to Messenger.
Proposed retry configuration:
maximum retries: 5
initial delay: 2 seconds
multiplier: 2
maximum delay: 60 seconds
failure transport: pimcore_image_thumbnail_generate_failed
A temporary failure for one variant should not prevent later variants in the same message from being attempted.
After processing and temporary-file cleanup, the first retryable failure can be re-thrown so Messenger retries the message. Outputs already written during the previous attempt are found through the normal storage checks and skipped.
Source-wide failures, such as an unreadable source or an adapter being unable to load it, should stop the current batch and let Messenger retry the message. The same source failure should not be logged once for every output variant.
Known permanent configuration failures, such as unsupported output formats or maximum-scaling violations, should be logged without unnecessary retries.
Existing-catalog warming
Enabling this feature should not enqueue the complete existing asset catalog, and changing a thumbnail definition should not automatically create an unbounded deployment-time workload.
Existing assets can continue to be warmed explicitly with Pimcore's command:
bin/console pimcore:thumbnails:image \
--thumbnails=hero,card \
--processes=4
I propose using a shared variant resolver for the command and the asynchronous worker so both follow the same base, media, DPR and automatic-format rules.
This creates one explicit behavior decision for maintainers:
- projects with
max_srcset_dpi_factor above 2 may generate additional configured DPR outputs when running the command;
- fractional high-resolution factors on the untouched base configuration are retained;
--skip-medias, --skip-high-res, --skip-avif and --skip-webp continue to restrict the corresponding expansions.
This behavior should be approved and documented rather than introduced as an unmentioned command refactor.
Compatibility
The feature is designed to remain additive:
- disabled by default;
- no database schema change;
- no changes to thumbnail hashes or storage paths;
- no change to existing on-demand generation;
- no wildcard expansion;
- no automatic whole-catalog enqueue;
- no change to dynamic or unlisted configurations;
- no change to optimizer message formats;
- existing per-derivative locking remains in place;
- browser requests remain the fallback when warming has not completed.
Non-goals
This proposal does not attempt to solve:
- CDN cache warmth or delivery latency;
- Twig or DataObject query performance;
- low-quality-placeholder storage reads;
- image optimizer backlog;
- decoded-image reuse across variants;
- publication gating;
- every possible dynamic thumbnail configuration.
Expected result
For selected common configurations:
image binary changes
→ background generation completes
→ browser requests an existing derivative
This removes the normal first-visitor thumbnail-generation path without removing Pimcore's resilient on-demand behavior.
Together with pimcore/pimcore#19382, the two contributions address separate image latency causes:
pimcore/pimcore#19382
→ avoid unnecessary remote reads during Twig dimension resolution
this proposal
→ avoid first-request generation for selected common derivatives
The production measurement that motivated pimcore/pimcore#19057 was approximately:
79 remote storage calls
5,735 ms page-render time
compared with:
0 remote storage calls
170 ms page-render time
after eliminating unnecessary dimension-resolution I/O.
These figures relate to the #19057 dimension path. This pre-generation proposal targets the later cold-thumbnail request and does not claim the same benchmark.
Implementation
- dedicated generation and failure transports;
- bounded opt-in configuration;
- one stale-aware message per asset and named configuration;
- queue-side initial-message deduplication;
- failed-send deduplication-lock cleanup;
- shared variant planning;
- fractional DPR support;
- media and automatic-format expansion;
- lazy source snapshot reuse;
- source validation and extension preservation;
- retryable generation failures;
- permanent-error classification;
- source-wide batch termination;
- existing derivative locking;
- operational documentation.
Affected capability
Assets
Feature description
Related to #349, pimcore/pimcore#19057 and pimcore/pimcore#19382.
Context
While working on pimcore/pimcore#19057 and its implementation PR pimcore/pimcore#19382, I investigated why image-heavy Pimcore pages can sometimes take several seconds to render or display their images, especially when remote Flysystem storage is used.
The investigation identified two separate latency problems:
widthandheightattributes.pimcore/pimcore#19382 is the separate fix for the first problem. It avoids physical file inspection when dimensions can be reproduced reliably.
This proposal addresses the second problem. It is intentionally separate because it introduces public configuration, a Messenger transport and new background-processing behavior, so it belongs on
2026.x.Problem
Pimcore generates missing image thumbnails on demand. This is an important fallback, but it also means the first visitor requesting a common thumbnail can become the process that generates it.
A cold thumbnail request can involve:
For large photographs, TIFF files, rasterized SVG files, responsive variants, automatic formats or remote storage, this can take several seconds.
Pimcore already provides
pimcore:thumbnails:imagefor controlled catalog warming. There is currently no opt-in mechanism that automatically warms selected named thumbnail configurations after a new image upload or actual binary change.Proposed behavior
Add an opt-in asynchronous pre-generation mechanism for explicitly selected named image thumbnail configurations.
Example configuration:
An empty
definitionslist keeps the feature disabled by default.The first version should require explicit named configurations. Wildcards should not be accepted because they could unexpectedly generate every thumbnail definition in a project.
Processing flow
The feature should be triggered by the existing binary-change path, not by every asset save or version creation. Metadata-only changes and version-only saves should not intentionally enqueue another warming pass.
Existing on-demand generation remains the fallback:
Dedicated Messenger transport
Pre-generation should use a dedicated asynchronous transport:
Thumbnail warming can be CPU-, memory- and temporary-disk-intensive. It should not compete with required asset processing, document processing or video work in
pimcore_asset_update.A separate transport allows independent:
A synchronous transport would remain technically possible, but it would remove the intended workload isolation and should be discouraged in the documentation.
Message identity and stale-work protection
Each message should contain:
The worker reloads the image and validates the checksum and modification date before performing expensive work.
This makes old messages safe:
The producer should use the checksum already persisted during binary processing. It should not calculate a missing checksum through storage while dispatching the optional warming message.
If no persisted checksum is available, event-driven warming should be skipped. Normal on-demand generation and the existing command remain available.
Queue deduplication and generation coordination
Equivalent initial messages waiting for first delivery can be deduplicated using:
This is a best-effort queue-load optimization, not the correctness boundary.
Once processing starts, Pimcore's existing deterministic thumbnail path and per-derivative generation lock remain authoritative. After acquiring the lock, the worker checks storage again before loading the source or writing the derivative.
The responsibilities remain separate:
Variant planning
For each selected named configuration, the worker should generate a bounded, deduplicated set containing:
max_srcset_dpi_factor;Equivalent physical outputs should be generated only once.
Work is bounded by:
Proposed defaults:
Installations processing large TIFF files, high-resolution photographs, rasterized SVG files, AVIF output or complex transformations can start with lower limits.
Source materialization
One asset and named-configuration message should use one lazy source snapshot.
The source should only be materialized after:
Therefore:
The snapshot should:
Source reuse is scoped to one asset and one named-configuration message. Different named configurations intentionally remain separate jobs for bounded execution and retry granularity.
Each output variant still performs its own image decode and transformation. Decoded-image reuse is outside this proposal.
Failure behavior
Temporary image-processing and storage failures should be visible to Messenger.
Proposed retry configuration:
A temporary failure for one variant should not prevent later variants in the same message from being attempted.
After processing and temporary-file cleanup, the first retryable failure can be re-thrown so Messenger retries the message. Outputs already written during the previous attempt are found through the normal storage checks and skipped.
Source-wide failures, such as an unreadable source or an adapter being unable to load it, should stop the current batch and let Messenger retry the message. The same source failure should not be logged once for every output variant.
Known permanent configuration failures, such as unsupported output formats or maximum-scaling violations, should be logged without unnecessary retries.
Existing-catalog warming
Enabling this feature should not enqueue the complete existing asset catalog, and changing a thumbnail definition should not automatically create an unbounded deployment-time workload.
Existing assets can continue to be warmed explicitly with Pimcore's command:
bin/console pimcore:thumbnails:image \ --thumbnails=hero,card \ --processes=4I propose using a shared variant resolver for the command and the asynchronous worker so both follow the same base, media, DPR and automatic-format rules.
This creates one explicit behavior decision for maintainers:
max_srcset_dpi_factorabove2may generate additional configured DPR outputs when running the command;--skip-medias,--skip-high-res,--skip-avifand--skip-webpcontinue to restrict the corresponding expansions.This behavior should be approved and documented rather than introduced as an unmentioned command refactor.
Compatibility
The feature is designed to remain additive:
Non-goals
This proposal does not attempt to solve:
Expected result
For selected common configurations:
This removes the normal first-visitor thumbnail-generation path without removing Pimcore's resilient on-demand behavior.
Together with pimcore/pimcore#19382, the two contributions address separate image latency causes:
The production measurement that motivated pimcore/pimcore#19057 was approximately:
compared with:
after eliminating unnecessary dimension-resolution I/O.
These figures relate to the #19057 dimension path. This pre-generation proposal targets the later cold-thumbnail request and does not claim the same benchmark.
Implementation