โ† Semua pembelajaran / Laravel Nol โ†’ Enterprise
Fase 9 ยท Deploy di AWS

S3 & CloudFront

Setiap permintaan yang dijawab CloudFront adalah permintaan yang tidak pernah menyentuh kontainermu. Bagi aplikasi yang isinya sama untuk semua pembaca, memindahkan sebanyak mungkin ke lapisan ini mengubah kebutuhan kapasitasmu secara mendasar.

Intisari

  • S3 untuk berkas yang diunggah; CloudFront di depannya, tanpa bucket yang bisa diakses publik.
  • Aset ber-hash Vite boleh di-cache selamanya โ€” nama berubah saat isi berubah.
  • CloudFront juga bisa menjadi tepi untuk halaman HTML, bukan cuma aset.
  • Cache policy menentukan apa yang masuk kunci cache; cookie sesi di dalamnya menghancurkan rasio hit.
  • Invalidasi ada kuotanya; TTL pendek plus stale-while-revalidate lebih hemat dan lebih sederhana.

Laravel dan S3

FILESYSTEM_DISK=s3
AWS_DEFAULT_REGION=ap-southeast-1
AWS_BUCKET=portal-media
AWS_URL=https://cdn.portal.example.com
// Tidak ada access key โ€” kredensial datang dari task role ECS
$path = $request->file('sampul')->store('artikel/sampul', 's3');

$url = Storage::disk('s3')->url($path);              // memakai AWS_URL โ†’ lewat CloudFront

// Untuk berkas privat: URL bertanda tangan berumur pendek
$url = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(15));

Setel AWS_URL ke domain CloudFront, bukan ke domain S3. Tanpa itu, setiap gambar diambil langsung dari S3 โ€” tanpa cache tepi, dengan latensi yang lebih tinggi bagi pembaca yang jauh, dan biaya transfer keluar S3 yang lebih mahal daripada lewat CloudFront.

Bucket tidak boleh publik

Block all public access : ON
Akses hanya lewat       : Origin Access Control (OAC) milik distribusi CloudFront
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "HanyaLewatCloudFront",
    "Effect": "Allow",
    "Principal": { "Service": "cloudfront.amazonaws.com" },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::portal-media/*",
    "Condition": {
      "StringEquals": {
        "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1ABCDEF"
      }
    }
  }]
}

Dengan pola ini, bucket tidak pernah bisa dijangkau langsung. Satu-satunya jalan masuk adalah lewat CloudFront โ€” yang berarti semua kontrol yang kamu pasang di sana (WAF, header, cache) benar-benar tidak bisa dilewati.

Origin dan perilaku

Distribusi CloudFront: cdn.portal.example.com
โ”œโ”€โ”€ Origin "s3-media"  โ†’ bucket portal-media (lewat OAC)
โ””โ”€โ”€ Origin "alb-app"   โ†’ ALB aplikasi

Behavior (urutan berpengaruh):
  /media/*    โ†’ s3-media   cache 1 tahun,  tanpa cookie
  /build/*    โ†’ alb-app    cache 1 tahun,  tanpa cookie
  /api/*      โ†’ alb-app    TANPA cache,    teruskan semua header
  /admin/*    โ†’ alb-app    TANPA cache,    teruskan cookie
  default *   โ†’ alb-app    cache pendek,   tanpa cookie

Urutan behavior menentukan segalanya, dan yang paling spesifik harus di atas. Satu kesalahan urutan bisa membuat /admin/* tertangkap oleh behavior default yang meng-cache โ€” dan halaman admin milik satu orang tersaji ke orang lain. Perlakukan daftar ini dengan kehati-hatian yang sama seperti daftar rute.

Cache policy: kunci cache menentukan rasio hit

Yang masuk kunci cacheAkibatnya
Tidak ada (hanya URL)Rasio hit tertinggi
Query string tertentuWajar untuk paginasi dan filter
Header Accept-LanguageSatu salinan per bahasa
Cookie sesiSatu salinan per pengguna โ€” rasio hit runtuh
Semua query stringParameter pelacakan iklan memecah cache jadi ribuan salinan

Ini kesalahan yang paling sering merusak rasio hit. Menyertakan semua query string berarti /artikel/kabar-pagi?utm_source=twitter dan ?utm_source=facebook menjadi dua entri cache berbeda untuk halaman yang isinya identik. Daftarkan secara eksplisit query string mana yang benar-benar mengubah keluaran โ€” biasanya cuma page dan beberapa filter.

Meng-cache halaman HTML di tepi

// Middleware dari Fase 7, sekarang berpasangan dengan CloudFront
if (! $request->user() && $request->isMethod('GET') && $response->isSuccessful()) {
    $response->header('Cache-Control',
        'public, max-age=0, s-maxage=300, stale-while-revalidate=600');
}
DirektifEfek
max-age=0Browser selalu bertanya โ€” pembaruan cepat terlihat
s-maxage=300CloudFront menyimpan 5 menit โ€” beban servermu turun drastis
stale-while-revalidate=600Tidak ada pengguna yang menunggu penyegaran

Untuk halaman artikel yang dibaca ribuan orang, kombinasi ini berarti servermu merender halaman itu sekali setiap lima menit, bukan sekali per pembaca. Perbedaan kapasitas yang dihasilkannya tidak bisa dikejar oleh optimasi kode apa pun.

Menangani pengguna yang login

Behavior default:
  Cookie yang diteruskan  : tidak ada
  Cache berdasarkan       : URL + query string terdaftar

Kalau halaman publik dan halaman personal berbagi URL, kamu punya masalah. Halaman depan yang menampilkan "Halo, Ani" di pojok kanan tidak bisa di-cache bersama. Dua jalan keluar: (1) buat bagian personal itu diambil lewat permintaan terpisah dari JavaScript, sehingga HTML-nya sama untuk semua orang; atau (2) pisahkan URL-nya. Yang pertama biasanya lebih baik untuk situs konten โ€” halaman utuh tetap bisa di-cache, dan hanya potongan kecilnya yang dinamis.

Invalidasi

aws cloudfront create-invalidation \
  --distribution-id E1ABCDEF \
  --paths "/artikel/kabar-pagi" "/kategori/politik" "/"
HalCatatan
Kuota gratisSejumlah path per bulan; selebihnya berbayar
WaktuBeberapa menit sampai menyebar ke semua lokasi tepi
Wildcard/artikel/* dihitung sebagai satu path
Aset ber-hashTidak pernah perlu diinvalidasi

Strategi yang praktis: TTL pendek untuk sebagian besar halaman, invalidasi hanya untuk yang mendesak. Artikel yang baru dikoreksi layak diinvalidasi seketika; halaman kategori bisa menunggu lima menit. Mencoba memetakan setiap URL yang terpengaruh oleh setiap perubahan adalah pekerjaan yang tidak pernah selesai dan selalu ada yang terlewat.

Yang perlu dipantau

MetrikTarget
CacheHitRate> 90% untuk situs konten
OriginLatencyLatensi aplikasimu saat cache meleset
4xxErrorRate / 5xxErrorRateMendekati nol
Byte dari origin vs totalSemakin kecil rasionya, semakin baik

Latihan: pasang CloudFront di depan ALB, buat behavior terpisah untuk /build/*, dan aktifkan cache halaman untuk pengunjung anonim. Lalu periksa dengan curl -sI https://โ€ฆ/artikel/x | grep -i x-cache dua kali berturut-turut โ€” yang kedua harus berbunyi Hit from cloudfront.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.