Files
AYS-User/docs/PLAN-image-optimization.md
2026-03-25 07:40:34 +08:00

5.1 KiB
Raw Permalink Blame History

PLAN: Service Image Optimization

Problem

Full-size S3 images (up to 3-4MB each) are downloaded for 120px thumbnails in the Discover tab. With 10 cards visible, that's 30-40MB of unnecessary data per page load, causing slow scroll performance and excessive bandwidth usage.

Current Flow

Provider uploads photo → Spatie stores FULL SIZE in S3 → API returns raw S3 URL → App downloads FULL image for 120px card

Image Stats (DB)

  • 6,101 active services with image attachments
  • Images stored in main-bucket-ays.s3.ap-southeast-1.amazonaws.com
  • No thumbnails or resized variants generated

Options Analysis

Option Effort Impact Cost
A. Backend thumbnail on upload Medium High Free (server CPU)
B. On-the-fly resize via API Low High Free (server CPU per request)
C. Flutter-side only (memCache) Very Low Medium None
D. CloudFront + Lambda@Edge High Highest AWS costs

Why?

  • A generates thumbnails once on upload — no repeated CPU cost
  • C optimizes Flutter memory immediately — quick win, no backend needed
  • Together they solve both bandwidth AND memory issues

Phase 1: Flutter Quick Win (No backend change needed)

[MODIFY] cached_image_widget.dart

  1. Cap memCacheWidth/memCacheHeight to reasonable limits based on display size
    • Card thumbnails (120px height): memCacheHeight: 360 (3x for retina)
    • Provider avatars (16px): memCacheHeight: 48
  2. Set fadeInDuration to 150ms for snappier feel
  3. Add maxWidthDiskCache / maxHeightDiskCache to limit disk cache bloat
CachedNetworkImage(
  imageUrl: url,
  memCacheHeight: (height * 3).toInt().clamp(0, 720),
  memCacheWidth: null, // Let aspect ratio be preserved
  maxHeightDiskCache: (height * 3).toInt().clamp(0, 720),
  fadeInDuration: Duration(milliseconds: 150),
  ...
)

Impact: Images still download full-size but decode to smaller in-memory, reducing RAM by ~70%. Scroll performance improves immediately.


Phase 2: Backend Thumbnail Generation

[MODIFY] helper.phpstoreMediaFile()

Add thumbnail generation using Spatie Media Library conversions:

// In Service model, register conversions:
public function registerMediaConversions(Media $media = null): void
{
    $this->addMediaConversion('thumb')
        ->width(400)
        ->height(300)
        ->sharpen(10)
        ->quality(80)
        ->performOnCollections('service_attachment')
        ->nonQueued(); // or ->queued() for async
}

[MODIFY] ServiceResource.php — Return thumbnail URLs

Add attchments_thumb field alongside existing attchments:

'attchments_thumb' => $this->getMedia('service_attachment')
    ->map(fn($m) => $m->getUrl('thumb'))
    ->filter()
    ->values()
    ->all(),

[MODIFY] Flutter service_data_model.dart

Parse the new attchments_thumb field:

List<String>? attachmentsThumbnail;
// In fromJson:
attachmentsThumbnail = json['attchments_thumb'] != null
    ? List<String>.from(json['attchments_thumb'])
    : null;

[MODIFY] discover_service_card.dart

Prefer thumbnail URL for card display:

final imageUrl = data.attachmentsThumbnail?.isNotEmpty == true
    ? data.attachmentsThumbnail!.first
    : (data.attachments?.isNotEmpty == true ? data.attachments!.first : '');

Phase 3: Backfill Existing Images (One-time)

[NEW] Artisan command to regenerate thumbnails

// php artisan media-library:regenerate --only-missing

Spatie's built-in command processes all existing media and creates missing conversions. For 6,000+ images this may take 30-60 minutes.


Verification Plan

Phase 1 (Flutter-only)

  • Profile RAM usage before/after with DevTools
  • Compare scroll FPS in Discover tab
  • Confirm images still display correctly

Phase 2 (Backend)

  • Upload a new service → verify thumbnail created in S3
  • API response includes attchments_thumb with valid URL
  • Flutter card loads thumbnail (~30-50KB) instead of original (~2-4MB)
  • Compare network tab: bandwidth reduction should be ~90%

Phase 3 (Backfill)

  • Run regeneration command on staging first
  • Verify all existing services have thumbnails
  • Deploy to production

Estimated Savings

Metric Before After (Phase 2)
Image per card ~2-4 MB ~30-50 KB
10 cards loaded ~30 MB ~400 KB
Bandwidth reduction ~95%
Memory per card ~12 MB decoded ~1 MB decoded

Risk & Notes

Important

Phase 1 is a safe, backend-free change — can deploy immediately. Phase 2 requires backend deployment + S3 write permissions for thumbnails.

Warning

Phase 3 (backfill) will generate new S3 objects for all 6,000+ services. Estimate storage increase: ~6,000 × 50KB = ~300 MB additional S3 storage.

Note

The Spatie Media Library conversion is the industry-standard approach for Laravel. No additional packages needed — it's already a dependency.