Repository navigation
[6.x] Use the container listing for asset existence, size and last modified - #15854
Open
DanielDarrenJones wants to merge 1 commit into
Open
DanielDarrenJones wants to merge 1 commit into
DanielDarrenJones wants to merge 1 commit into
Conversation
DanielDarrenJones
marked this pull request as ready for review
November 12, 2026 00:07
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An asset container keeps a listing of every file on its disk, with each file's size and timestamp. It's loaded on any request that touches an asset. Two things don't make use of it, and both get expensive as a container grows, most of all when its disk is remote.
Checking an asset exists
Asset::exists()asks the container for all its files and checks whether the path is among them. That filters every row of the listing, copies the keys into a new collection, and searches it from the start.metaExists()does the same over the meta files.exists()is called frommeta(), which is called for every value read from an asset, so a page showing 50 assets calls it over a thousand times. On a container of 20,000 images that is about 135 ms per request for the two filters and 0.13 ms per call after that. It's CPU rather than disk, so it costs the same on a local disk.AssetContainerContentsgetshasFile($path),hasMetaFile($path)andfile($path), which look the path up directly and apply the same rules as before (a file, not inside.meta, not.DS_Store,.gitkeepor.gitignore).Asset::exists()andmetaExists()use them.Size and last modified
Sorting or filtering assets by a value builds a Stache index for it, which asks every asset in the container for that value. Size and last modified come from the asset's meta, and a meta that isn't cached is read from its
.metafile: one read per asset. Both are visible, sortable columns in the asset browser by default, so one click on a column header does it.On 20,000 images the first sort by size made 19,748 reads. That took about 18 seconds on a local disk. On a disk where a read takes 40 ms it is about 13 minutes.
Asset::size()andlastModified()now use the listing, and fall back to the meta when the listing has no entry for the file (as with the Eloquent driver, whose listing holds only directories).generateMeta()uses the listing's values too instead of asking the disk again.reupload()refreshes the file's listing entry so the size can't be out of date afterwards.Width, height, duration and blueprint fields still come from the meta, so sorting by those is unchanged.
One existing test changed
it_generates_meta_on_demand_if_a_required_value_is_missingcalledsize()to trigger a meta rebuild.size()no longer needs the meta, so the test callsmimeType()instead.Benchmark
A generated site with one asset container of 20,000 images in 200 folders, each with its
.metafile, and entries that reference them through assets fields. The container's disk is a Flysystem adapter wrapped round the local one that counts every call and adds 40 ms to each (120 ms per 1,000 keys listed), so Statamic takes the paths it takes on S3. No real S3 was used: the call counts are exact, the times are PHP time plus that modelled network time. Requests went throughphp -Swith OPcache, production env, watcher off, against6.xat 7097691 (Apple M1 Pro, PHP 8.5). Single runs; PHP-only timings moved by up to about 30% between runs, call counts did not..metafile yet (remote disk)Tests
Each fails without the change:
AssetTest::checking_existence_does_not_filter_the_whole_container_listing, which also covers the paths that must not count as assets.AssetTest::it_gets_size_and_last_modified_from_the_container_listing_without_touching_meta.AssetTest::generating_meta_uses_the_size_and_timestamp_the_listing_already_has.