Autoscaling & menghitung kapasitas
Autoscaling yang tidak memperhitungkan batas koneksi database akan menjatuhkan portalmu tepat saat trafik memuncak. Ini hitungannya, dari awal.
Intisari
- Setelah cache diperbaiki, origin-mu melihat ~21 req/detik rata-rata, bukan 139.
- Hitung dari waktu render, bukan dari CPU: task = (req/detik ร detik per request) รท target pemakaian.
- Batas atas autoscaling harus diturunkan dari
max_connections(Fase 2), bukan dari CPU. - Naik cepat, turun lambat. Scale-in yang agresif menghasilkan osilasi.
- Portal berita punya pola trafik yang bisa diprediksi โ jadwalkan kapasitas, jangan hanya bereaksi.
Dari trafik ke jumlah task
Total request : 1.250.000 / jam
Cache hit (target) : 93%
Ke origin : ~87.500 / jam โ 24 req/detik rata-rata
Portal berita: puncak biasanya 2,5โ3ร rata-rata
Puncak ke origin : ~70 req/detik
Dari 70 req/detik itu:
~45 req/detik halaman on-demand (cache MISS)
~25 req/detik server island (tidak pernah di-cache)
Server island tidak pernah di-cache, jadi jumlahnya berskala langsung dengan pembaca โ bukan dengan cache miss. Ini konsekuensi arsitektur yang perlu dihitung terpisah: makin bagus cache halamanmu, makin besar porsi beban origin yang berasal dari island. Kalau kamu punya tiga island per halaman alih-alih satu (Fase 5), angka ini tiga kali lipat.
Dari beban ke kapasitas
Ukur dulu waktu render yang sebenarnya:
Halaman artikel (MISS) : ~35 ms CPU
Server island : ~8 ms CPU
Halaman kategori : ~50 ms CPU
Beban CPU pada puncak:
45 req/dtk ร 0,035 dtk = 1,58 detik-CPU per detik
25 req/dtk ร 0,008 dtk = 0,20 detik-CPU per detik
= 1,78 detik-CPU per detik
Node = satu inti untuk JavaScript. Target pemakaian 60% supaya
ada ruang untuk lonjakan:
1,78 รท 0,60 = 2,97 โ 3 task minimum untuk melayani puncak
Ditambah margin ketersediaan (kehilangan satu AZ dari tiga):
3 รท (2/3) = 4,5 โ desiredCount 5, minimum 4
Angka waktu render harus kamu ukur, bukan disalin dari sini. Angka di atas adalah contoh yang masuk akal untuk halaman artikel Astro dengan beberapa kueri; portalmu bisa dua kali lebih cepat atau tiga kali lebih lambat tergantung berapa kueri per halaman dan seberapa besar HTML-nya. Fase 11 membahas cara mengukurnya.
Batas atas: dari database, bukan dari CPU
Dari Fase 2:
connectionLimit reader : 12
connectionLimit writer : 4
Total per task : 16
RDS db.m6g.large, max_connections โ 680
Dipakai CI3 selama transisi : 150
Cron, admin, BI : 50
Cadangan : 80
Tersedia untuk Astro : 400
Maksimum task = 400 รท 16 = 25
Setel MaxCapacity ke 20, bukan 25. Margin itu untuk task yang sedang dimatikan
saat deploy โ selama rolling update, task lama dan baru hidup bersamaan, dan koneksinya berjumlah dua
kali lipat sesaat. Autoscaling yang menyentuh batas persis saat deploy adalah cara menjatuhkan database
di momen paling tidak nyaman.
Kebijakan penskalaan
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/portal/portal-web \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 4 --max-capacity 20
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/portal/portal-web \
--scalable-dimension ecs:service:DesiredCount \
--policy-name cpu-target \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 60.0,
"PredefinedMetricSpecification": {"PredefinedMetricType": "ECSServiceAverageCPUUtilization"},
"ScaleOutCooldown": 60,
"ScaleInCooldown": 300
}'
| Pengaturan | Nilai | Kenapa |
|---|---|---|
TargetValue | 60% | Ruang untuk lonjakan sebelum task baru siap |
ScaleOutCooldown | 60 detik | Naik cepat โ trafik berita bisa melonjak dalam menit |
ScaleInCooldown | 300 detik | Turun lambat โ mencegah osilasi |
MinCapacity | 4 | Bertahan kehilangan satu AZ |
MaxCapacity | 20 | Dari batas koneksi database |
Asimetri cooldown itu disengaja dan penting. Menambah task saat tidak perlu hanya memboroskan sedikit uang; mengurangi task saat masih perlu berarti latensi memburuk dan autoscaling harus menambah lagi โ dan Fargate butuh sekitar satu menit untuk menyiapkan task baru. Osilasi naik-turun itu lebih buruk daripada sekadar berjalan sedikit berlebih.
Portal berita punya pola yang bisa diprediksi
# Naikkan sebelum jam sibuk pagi, bukan setelah trafiknya datang
aws application-autoscaling put-scheduled-action \
--service-namespace ecs \
--resource-id service/portal/portal-web \
--scalable-dimension ecs:service:DesiredCount \
--scheduled-action-name pagi \
--schedule "cron(30 23 * * ? *)" \
--scalable-target-action MinCapacity=8,MaxCapacity=20
# 23:30 UTC = 06:30 WIB
aws application-autoscaling put-scheduled-action \
--service-namespace ecs \
--resource-id service/portal/portal-web \
--scalable-dimension ecs:service:DesiredCount \
--scheduled-action-name malam \
--schedule "cron(0 16 * * ? *)" \
--scalable-target-action MinCapacity=4,MaxCapacity=20
# 16:00 UTC = 23:00 WIB
Autoscaling reaktif selalu terlambat. Ia baru bertindak setelah CPU naik, dan task baru butuh sekitar semenit lagi untuk siap โ jadi ada jendela di mana kapasitasmu kurang. Portal berita punya pola harian yang sangat konsisten: lonjakan pagi saat orang membaca berita, lonjakan siang, dan penurunan malam. Menjadwalkan kapasitas dasar menghapus jendela itu, dan autoscaling reaktif tinggal menangani yang tidak terduga.
Metrik alternatif
| Metrik | Cocok kalau |
|---|---|
| CPU rata-rata | Default yang baik untuk render yang terikat CPU |
ALBRequestCountPerTarget | Beban per request cukup seragam |
| Metrik kustom: kedalaman antrean | Ada pekerjaan latar |
| Metrik kustom: waktu tunggu pool DB | Ketika database yang jadi batas |
Baris terakhir layak dipertimbangkan setelah kamu berjalan beberapa bulan. Kalau task-mu menunggu koneksi database alih-alih sibuk merender, menambah task justru memperburuk keadaan โ dan CPU tidak akan memberitahumu itu.
Verifikasi dengan uji beban
// k6: naik bertahap sampai puncak yang diperkirakan
import http from "k6/http";
import { check } from "k6";
export const options = {
stages: [
{ duration: "2m", target: 20 },
{ duration: "5m", target: 70 }, // puncak ke origin
{ duration: "3m", target: 140 }, // 2ร puncak
{ duration: "2m", target: 0 },
],
thresholds: {
http_req_duration: ["p(95)<500"],
http_req_failed: ["rate<0.001"],
},
};
export default function () {
// Langsung ke origin, melewati Cloudflare โ ini menguji kapasitas asli
const res = http.get(`https://astro-origin.portal.contoh.id/berita/uji-${__VU % 500}`, {
headers: { "X-Origin-Token": __ENV.ORIGIN_TOKEN },
});
check(res, { "200": (r) => r.status === 200 });
}
Uji ke origin langsung, bukan lewat Cloudflare. Kalau kamu menguji lewat Cloudflare, kamu mengukur kecepatan cache โ yang memang cepat, dan tidak memberitahumu apa pun tentang kapasitas origin-mu. Yang ingin kamu ketahui: berapa banyak yang bisa dilayani Astro sendiri saat cache tidak menolong.
Perhatikan juga __VU % 500: memakai URL yang bervariasi. Menguji satu URL yang sama
berulang kali akan menghangatkan setiap cache di jalur dan memberimu angka yang terlalu optimis.
Latihan: hitung kapasitasmu dari awal memakai angka portalmu sendiri: cache hit ratio target,
request per detik ke origin, dan waktu render yang kamu ukur. Bandingkan hasilnya dengan
MaxCapacity yang diturunkan dari max_connections RDS-mu โ yang lebih kecil
yang menang. Lalu jalankan uji beban k6 dan periksa apakah autoscaling benar-benar bereaksi seperti yang
kamu harapkan.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.