Flarum\User\AvatarUploader::srcsetFor() calls $this->uploadDir->exists() once per HiDPI variant (1×, 2×, 3×) every time a user with an uploaded avatar is serialized. On installs where flarum-avatars is backed by a local disk these calls are essentially free, but where it's backed by S3 (or any remote Flysystem adapter that maps exists() to a HEAD request), each call is a network round-trip.
The accessor on User:
public function getAvatarSrcsetAttribute(): ?string
{
$value = $this->getRawOriginal('avatar_url');
if ($value && ! str_contains($value, '://')) {
return resolve(AvatarUploader::class)->srcsetFor($value);
}
return static::$avatarDriver->avatarSrcset($this);
}
reaches srcsetFor() for every locally-stored avatar, which then runs:
foreach (self::SIZES as $suffix => $size) {
$path = $this->variantPath($basePath, $suffix);
if ($this->uploadDir->exists($path)) {
$existing[$path] = $size / 100;
}
}
That's 3 exists() calls × number of users with avatars on the page. The result is recomputed from scratch on every request — there's no caching, and the underlying data (which variants exist on disk) is effectively immutable for the lifetime of an avatar (avatar paths are randomly generated and replaced wholesale on re-upload).
Reproduction
- Forum on Flarum 2.x with
flarum-avatars disk pointed at S3 (AwsS3V3Adapter).
/api/discussions response includes ~20 discussions; user objects from firstUser/lastPostedUser/recipients are included.
- Profiling a single request shows 13 users with stored avatars triggering
srcsetFor() and 39 S3 HeadObject calls (~21ms each in eu-central-1) — ~840ms of TTFB attributable to this single accessor.
Disabling the per-variant exists() check (returning all three variant URLs unconditionally as a workaround) drops total request time on /api/discussions from ~2.4s to ~1.15s in our environment.
Environment
- Flarum core 2.0.0-rc.1
flarum-avatars disk: Illuminate\Filesystem\AwsS3V3Adapter (Flysystem 3.x via league/flysystem-aws-s3-v3:^3)
- 25k discussions / 100k users / 168k posts
- ~20 users serialized per page render
Impact
Any 2.x install with a remote-storage avatars disk pays this cost on every page render, scaled by the number of users serialized. Forums with active discussion lists and many users with uploaded avatars are the worst case.
Flarum\User\AvatarUploader::srcsetFor()calls$this->uploadDir->exists()once per HiDPI variant (1×, 2×, 3×) every time a user with an uploaded avatar is serialized. On installs whereflarum-avatarsis backed by a local disk these calls are essentially free, but where it's backed by S3 (or any remote Flysystem adapter that mapsexists()to a HEAD request), each call is a network round-trip.The accessor on
User:reaches
srcsetFor()for every locally-stored avatar, which then runs:That's 3
exists()calls × number of users with avatars on the page. The result is recomputed from scratch on every request — there's no caching, and the underlying data (which variants exist on disk) is effectively immutable for the lifetime of an avatar (avatar paths are randomly generated and replaced wholesale on re-upload).Reproduction
flarum-avatarsdisk pointed at S3 (AwsS3V3Adapter)./api/discussionsresponse includes ~20 discussions; user objects fromfirstUser/lastPostedUser/recipientsare included.srcsetFor()and 39 S3 HeadObject calls (~21ms each in eu-central-1) — ~840ms of TTFB attributable to this single accessor.Disabling the per-variant
exists()check (returning all three variant URLs unconditionally as a workaround) drops total request time on/api/discussionsfrom ~2.4s to ~1.15s in our environment.Environment
flarum-avatarsdisk:Illuminate\Filesystem\AwsS3V3Adapter(Flysystem 3.x vialeague/flysystem-aws-s3-v3:^3)Impact
Any 2.x install with a remote-storage avatars disk pays this cost on every page render, scaled by the number of users serialized. Forums with active discussion lists and many users with uploaded avatars are the worst case.