Skip to content

[2.x] srcsetFor() on AvatarUploader makes 3 remote exists() calls per user serialized — ~120ms+ per page on remote-disk installs #4635

@imorland

Description

@imorland

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions