Uji paritas — membuktikan Astro sama benarnya
"Sudah saya cek, kelihatannya sama" tidak cukup untuk 200 ribu halaman. Ini cara membandingkannya dengan mesin.
Intisari
- Bandingkan data yang penting, bukan HTML mentah — markup memang sengaja berbeda.
- Ekstrak: judul, kanonik, JSON-LD, jumlah paragraf, jumlah gambar, status HTTP.
- Jalankan terhadap sampel yang mewakili: artikel populer, artikel arsip, artikel premium, kasus aneh.
- Uji bayangan mengirim salinan trafik nyata ke Astro tanpa pembaca melihat hasilnya.
- Selisih yang disengaja harus didaftarkan, supaya laporannya tetap berguna.
Bandingkan apa
| Bandingkan | Jangan bandingkan |
|---|---|
| Status HTTP | HTML mentah |
<title> | Kelas CSS |
| URL kanonik | Urutan atribut |
| JSON-LD (field yang penting) | Spasi & indentasi |
Teks judul <h1> | Struktur DOM |
| Jumlah paragraf badan artikel | Markup pembungkusnya |
| Jumlah & URL gambar | Ukuran yang dihasilkan |
| Ada tidaknya blok paywall | Teks persis ajakannya |
Skrip pembanding
// skrip/paritas.ts
import { parseHTML } from "linkedom";
interface Sidik {
status: number;
judul: string | null;
kanonik: string | null;
h1: string | null;
jumlahParagraf: number;
jumlahGambar: number;
adaPaywall: boolean;
tipeJsonLd: string | null;
gratis: boolean | null;
}
async function sidik(url: string): Promise<Sidik> {
const res = await fetch(url, {
redirect: "manual",
headers: { "User-Agent": "uji-paritas/1.0" },
signal: AbortSignal.timeout(15_000),
});
if (res.status >= 300) {
return { status: res.status, judul: null, kanonik: res.headers.get("location"),
h1: null, jumlahParagraf: 0, jumlahGambar: 0, adaPaywall: false,
tipeJsonLd: null, gratis: null };
}
const { document } = parseHTML(await res.text());
const ld = document.querySelector('script[type="application/ld+json"]');
let json: any = null;
try { json = ld ? JSON.parse(ld.textContent ?? "") : null; } catch {}
return {
status: res.status,
judul: document.querySelector("title")?.textContent?.trim() ?? null,
kanonik: document.querySelector('link[rel="canonical"]')?.getAttribute("href") ?? null,
h1: document.querySelector("h1")?.textContent?.trim() ?? null,
jumlahParagraf: document.querySelectorAll(".badan-artikel p").length,
jumlahGambar: document.querySelectorAll(".badan-artikel img").length,
adaPaywall: document.querySelector(".paywall, .badan-berbayar") !== null,
tipeJsonLd: json?.["@type"] ?? null,
gratis: json?.isAccessibleForFree ?? null,
};
}
const ABAI = new Set(["jumlahGambar"]); // selisih yang disengaja
for (const jalur of daftarUrl) {
const [lama, baru] = await Promise.all([
sidik(`https://ci3.internal${jalur}`),
sidik(`https://astro.internal${jalur}`),
]);
for (const k of Object.keys(lama) as (keyof Sidik)[]) {
if (ABAI.has(k)) continue;
if (lama[k] !== baru[k]) {
console.log(JSON.stringify({ jalur, field: k, lama: lama[k], baru: baru[k] }));
}
}
}
Daftar ABAI harus eksplisit dan ditinjau. Beberapa selisih memang disengaja —
jumlah gambar berbeda karena Astro memakai srcset, atau judul berbeda karena kamu
memperbaiki formatnya. Mendaftarkannya menjaga laporan tetap pendek dan berarti. Laporan yang berisi
ribuan selisih yang diketahui akan berhenti dibaca.
Sampel yang mewakili
-- Bukan 100 artikel acak. Ambil yang mewakili tiap kelas.
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE status='terbit' ORDER BY hits DESC LIMIT 100) -- terpopuler
UNION
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE status='terbit' ORDER BY terbit_pada DESC LIMIT 100) -- terbaru
UNION
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE status='terbit' ORDER BY terbit_pada ASC LIMIT 100) -- tertua
UNION
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE premium=1 AND status='terbit' LIMIT 50) -- berbayar
UNION
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE kategori_id IS NULL LIMIT 20) -- tanpa kategori
UNION
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE gambar IS NULL LIMIT 20) -- tanpa gambar
UNION
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE CHAR_LENGTH(isi) > 50000 LIMIT 20) -- sangat panjang
UNION
(SELECT CONCAT('/berita/', slug) FROM artikel
WHERE judul REGEXP '[^a-zA-Z0-9 -]' LIMIT 20); -- karakter khusus
Empat baris terakhir yang menemukan bug. Artikel tanpa kategori, tanpa gambar, dengan isi
sangat panjang, atau berjudul mengandung karakter khusus adalah kasus yang tidak pernah muncul saat
pengembangan — tapi ada ribuan di arsipmu. Ini persis tempat null yang tidak ditangani
bersembunyi.
Uji bayangan dengan trafik nyata
# Ambil URL yang benar-benar diminta satu jam terakhir, kirim salinannya ke Astro
tail -n 20000 /var/log/nginx/access.log \
| awk '$9 == 200 && $6 == "\"GET" {print $7}' \
| grep '^/berita/' | sed 's/?.*//' | sort -u \
| while read -r p; do
s=$(curl -s -o /dev/null -w '%{http_code}' -m 10 "https://astro.internal$p")
[ "$s" != "200" ] && echo "$s $p"
done
Ini menangkap kelas kesalahan yang tidak akan muncul dari sampel manapun: URL dengan karakter yang di-encode aneh, slug yang mengandung karakter yang tidak kamu duga, dan artikel yang datanya tidak konsisten dengan cara yang hanya terjadi beberapa kali di seluruh arsip.
Paritas performa
for h in ci3.internal astro.internal; do
printf '%-16s ' "$h"
for i in $(seq 1 20); do
curl -s -o /dev/null -w '%{time_total} ' "https://$h/berita/artikel-uji"
done | tr ' ' '\n' | grep -v '^$' | sort -n | awk '{a[NR]=$1} END {printf "p50=%.3f p95=%.3f\n", a[int(NR*0.5)], a[int(NR*0.95)]}'
done
Bandingkan dengan cache dimatikan di kedua sisi. Membandingkan Astro yang dilayani dari edge dengan CI3 yang selalu MISS akan memberimu angka yang mengesankan tapi tidak berarti. Yang ingin kamu ketahui adalah: pada beban yang sama, mana yang lebih cepat merender dari nol. Keuntungan cache-nya sudah kamu hitung terpisah di Fase 8.
Gerbang sebelum memindahkan trafik
| Kriteria | Ambang |
|---|---|
| Selisih status HTTP | Nol |
| Selisih kanonik | Nol |
Selisih JSON-LD @type / isAccessibleForFree | Nol |
| Selisih judul | < 0,1%, semuanya ditinjau |
| Halaman dengan paragraf nol | Nol |
| Paywall muncul di artikel gratis | Nol |
| Isi premium bocor tanpa sesi | Nol |
| p95 render | ≤ CI3 |
Latihan: tulis skrip sidik() dan jalankan terhadap 50 URL dari kueri sampel di
atas, membandingkan CI3 dengan Astro lokal. Perbaiki setiap selisih atau daftarkan sebagai disengaja.
Lalu jalankan uji bayangan terhadap seribu URL dari log akses satu jam terakhir dan catat berapa yang
tidak mengembalikan 200 — angka itu biasanya lebih dari nol, dan tiap satu adalah bug yang baru saja
kamu hindari di produksi.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.