โ† Semua pembelajaran / Astro Nol โ†’ Portal Berita
Fase 11 ยท Performa & Capstone

Uji beban ke angka trafikmu

Uji beban yang memukul satu URL berulang kali mengukur kecepatan cache, bukan kapasitas. Ini cara membuat ujinya berarti.

Sumber asli grafana.com Resmi Rangkuman ~9 menit baca

Intisari

  • Uji ke origin langsung untuk mengukur kapasitas; lewat Cloudflare untuk mengukur pengalaman pembaca.
  • Variasikan URL. Satu URL berulang menghangatkan setiap cache dan memberi angka palsu.
  • Model trafik portal berita: banyak pembaca anonim, sedikit member, sesekali artikel viral.
  • Uji juga jalur yang tidak di-cache: server island, login, webhook.
  • Cari titik patah, bukan cuma "lulus" โ€” kamu perlu tahu di mana batasnya.

Empat skenario yang berbeda

SkenarioMengukurTarget ke
Kapasitas originBerapa yang bisa dilayani Astro sendiriOrigin langsung
Pengalaman pembacaLatensi yang dirasakanLewat Cloudflare
Cache dinginApa yang terjadi setelah purge everythingLewat Cloudflare, setelah purge
Artikel viralSatu URL, trafik ekstremLewat Cloudflare

Skenario campuran yang realistis

// uji/beban.js
import http from "k6/http";
import { check, group } from "k6";
import { Rate, Trend } from "k6/metrics";

const gagalIsland = new Rate("gagal_island");
const waktuIsland = new Trend("waktu_island");

const ORIGIN = __ENV.TARGET;
const TOKEN = __ENV.ORIGIN_TOKEN;
const H = { "X-Origin-Token": TOKEN };

export const options = {
  scenarios: {
    // 85% pembaca anonim membuka artikel โ€” bentuk trafik sesungguhnya
    pembaca_anonim: {
      executor: "ramping-arrival-rate",
      startRate: 10, timeUnit: "1s",
      preAllocatedVUs: 100, maxVUs: 500,
      stages: [
        { duration: "2m", target: 20 },
        { duration: "5m", target: 60 },    // puncak yang diperkirakan
        { duration: "3m", target: 120 },   // 2ร— puncak
        { duration: "2m", target: 0 },
      ],
      exec: "bacaArtikel",
    },
    // 10% membuka beranda
    beranda: {
      executor: "constant-arrival-rate",
      rate: 8, timeUnit: "1s",
      duration: "12m", preAllocatedVUs: 30, maxVUs: 100,
      exec: "bukaBeranda",
    },
    // 5% member dengan sesi โ€” memicu server island
    member: {
      executor: "constant-arrival-rate",
      rate: 4, timeUnit: "1s",
      duration: "12m", preAllocatedVUs: 20, maxVUs: 60,
      exec: "memberBaca",
    },
  },

  thresholds: {
    "http_req_duration{skenario:pembaca_anonim}": ["p(95)<300"],
    "http_req_failed": ["rate<0.001"],
    "waktu_island": ["p(95)<100"],
    "gagal_island": ["rate<0.001"],
  },
};

// Variasikan URL: pembaca sungguhan tidak membuka artikel yang sama
function slugAcak() {
  const n = Math.floor(Math.random() * 2000);
  return `artikel-uji-${n}`;
}

export function bacaArtikel() {
  const res = http.get(`${ORIGIN}/berita/${slugAcak()}`, { headers: H });
  check(res, { "artikel 200": (r) => r.status === 200 });
}

export function bukaBeranda() {
  const res = http.get(`${ORIGIN}/`, { headers: H });
  check(res, { "beranda 200": (r) => r.status === 200 });
}

export function memberBaca() {
  group("member", () => {
    const halaman = http.get(`${ORIGIN}/berita/${slugAcak()}`, {
      headers: { ...H, Cookie: `__Host-sesi=${__ENV.SESI_UJI}` },
    });
    check(halaman, { "halaman 200": (r) => r.status === 200 });

    // Server island: inilah yang benar-benar memukul origin per pembaca
    const island = http.get(`${ORIGIN}/_server-islands/KonteksPembaca`, {
      headers: { ...H, Cookie: `__Host-sesi=${__ENV.SESI_UJI}` },
    });
    waktuIsland.add(island.timings.duration);
    gagalIsland.add(island.status !== 200);
  });
}

ramping-arrival-rate, bukan ramping-vus. Bedanya penting: eksekutor berbasis VU akan melambat saat sistemmu melambat โ€” sepuluh VU yang menunggu respons lambat menghasilkan lebih sedikit request per detik, sehingga uji beban justru meringankan sistem yang sedang kepayahan. Eksekutor berbasis laju kedatangan mempertahankan tekanan apa pun yang terjadi, yang jauh lebih menyerupai pembaca sungguhan.

Menyiapkan datanya

-- Uji beban butuh artikel yang benar-benar ada dan bervariasi ukurannya
INSERT INTO artikel (judul, slug, isi, ringkasan, status, terbit_pada, penulis_id, premium)
SELECT
  CONCAT('Artikel uji ', n),
  CONCAT('artikel-uji-', n),
  REPEAT('<p>Paragraf isi artikel untuk uji beban.</p>', 20 + (n % 60)),
  CONCAT('Ringkasan artikel uji ', n),
  'terbit', NOW() - INTERVAL (n % 365) DAY, 1, n % 5 = 0
FROM angka WHERE n < 2000;

Variasikan panjang artikel. Kalau semua artikel ujimu sama panjang, kamu tidak akan pernah menemukan bahwa artikel 40.000 kata membuat render p99-mu meledak. Portal berita punya distribusi panjang yang lebar โ€” dari berita singkat sampai laporan mendalam.

Menjalankan

# 1. Kapasitas origin โ€” ini yang menentukan jumlah task
TARGET=https://astro-origin.portal.contoh.id \
ORIGIN_TOKEN=$TOKEN SESI_UJI=$SESI \
  k6 run uji/beban.js

# 2. Pengalaman pembaca lewat Cloudflare
TARGET=https://portal.contoh.id k6 run uji/beban.js

# 3. Cache dingin โ€” jalankan SETELAH purge, di lingkungan staging
#    Jangan pernah melakukan ini di produksi saat jam sibuk.

Membaca hasilnya

     http_req_duration..............: p(50)=31ms  p(95)=118ms p(99)=284ms
     http_req_failed................: 0.00%
     waktu_island...................: p(50)=9ms   p(95)=41ms
     iterations.....................: 43821  60.8/s

     โœ“ artikel 200
     โœ“ halaman 200
GejalaKemungkinan penyebab
p95 naik tajam pada laju tertentuKapasitas terlampaui โ€” catat angkanya, itu titik patahmu
Error mulai muncul, CPU rendahPool koneksi database โ€” kembali ke Fase 2
p99 โ‰ซ p95Beberapa halaman jauh lebih berat; cari mana
Latensi naik perlahan sepanjang ujiKebocoran memori atau koneksi
Task di-restart di tengah ujiOOM โ€” periksa max-old-space-size
Island jauh lebih lambat dari halamanKueri sesi tanpa index, atau membaca dari writer

Cari titik patah

// Naikkan terus sampai rusak. Ini yang paling berguna dari semua uji.
export const options = {
  scenarios: {
    patah: {
      executor: "ramping-arrival-rate",
      startRate: 20, timeUnit: "1s",
      preAllocatedVUs: 200, maxVUs: 2000,
      stages: [
        { duration: "2m", target: 50 },
        { duration: "2m", target: 100 },
        { duration: "2m", target: 200 },
        { duration: "2m", target: 400 },
        { duration: "2m", target: 800 },
      ],
      exec: "bacaArtikel",
    },
  },
};

"Lulus uji beban" tidak memberitahumu apa pun tentang seberapa jauh kamu dari batas. Yang ingin kamu ketahui: pada laju berapa p95 mulai memburuk, dan apa yang patah lebih dulu โ€” CPU, memori, pool koneksi, atau database. Angka itu yang menentukan seberapa besar lonjakan yang bisa kamu hadapi tanpa autoscaling sempat bereaksi.

Pantau sisi sistem selama uji

MetrikYang dicari
CPU task ECSHarus naik seiring beban; kalau tidak, ada bottleneck lain
Memori taskHarus stabil; naik terus = kebocoran
DatabaseConnectionsJangan mendekati batas
CPU RDSKalau ini yang jenuh, menambah task tidak menolong
Jumlah taskApakah autoscaling bereaksi seperti yang diharapkan?
Log kueri lambatKueri mana yang muncul di bawah beban

Kapan menjalankannya

KapanSkenario
Sebelum cutoverSemuanya, termasuk titik patah
Setelah perubahan arsitektur besarKapasitas origin
Sebelum peristiwa yang diperkirakan ramaiKapasitas + cache dingin
Per kuartalKapasitas, untuk mendeteksi regresi perlahan

Latihan: siapkan 2.000 artikel uji dengan panjang bervariasi, lalu jalankan skenario campuran ke origin langsung. Catat p95 pada puncak yang kamu perkirakan. Lalu jalankan uji titik patah dan catat dua angka: laju di mana p95 melewati 300 ms, dan apa yang jenuh lebih dulu. Bandingkan hasilnya dengan hitungan kapasitas yang kamu buat di Fase 10.

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