Purge selektif dengan Cache Tag
Purge Everything pada portal dengan 500 ribu request per jam berarti mengirim seluruh trafik itu ke origin sekaligus. Purge by tag menghindarinya sepenuhnya.
Intisari
- Purge by tag dan by prefix tersedia di semua plan sejak April 2025; yang berbeda hanya rate limit-nya.
- Header
Cache-Tagdari origin memberi label pada respons; satu respons boleh punya banyak tag. - Maksimum 100 tag per permintaan purge, dan panjang total header
Cache-Tagdibatasi. - Jangan pernah Purge Everything di jam sibuk โ origin-mu akan menerima seluruh trafik sekaligus.
- Purge harus dipanggil setelah transaksi database commit, bukan di dalamnya.
Memberi label
// Di halaman artikel
Astro.response.headers.set(
"Cache-Tag",
[
`artikel-${artikel.id}`,
`kategori-${artikel.kategoriSlug}`,
`penulis-${artikel.penulisId}`,
"tipe-artikel",
].join(","),
);
// Di halaman kategori โ tag yang beririsan, disengaja
Astro.response.headers.set(
"Cache-Tag",
[`kategori-${slug}`, "tipe-daftar", "beranda-terkait"].join(","),
);
Irisan tag inilah yang membuat sistemnya bekerja. Saat artikel disunting, purge
artikel-4471 membersihkan halaman artikelnya; purge kategori-ekonomi sekaligus
membersihkan halaman kategori dan beranda yang menampilkannya.
Memanggil purge
// src/lib/purge.ts
import { env } from "./env";
export async function purgeTag(tag: string[]): Promise<boolean> {
if (tag.length === 0) return true;
// Maksimum 100 tag per permintaan.
const potongan: string[][] = [];
for (let i = 0; i < tag.length; i += 100) potongan.push(tag.slice(i, i + 100));
for (const p of potongan) {
try {
const res = await fetch(
`https://api.cloudflare.com/client/v4/zones/${env.CF_ZONE_ID}/purge_cache`,
{
method: "POST",
signal: AbortSignal.timeout(8000),
headers: {
Authorization: `Bearer ${env.CF_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ tags: p }),
},
);
if (!res.ok) {
console.error(JSON.stringify({
level: "error", pesan: "purge gagal", status: res.status, tag: p,
}));
return false;
}
} catch (e) {
console.error(JSON.stringify({ level: "error", pesan: "purge error", e: String(e) }));
return false;
}
}
return true;
}
Token API-nya harus dibuat dengan izin sesempit mungkin. Buat token khusus dengan hanya Zone โ Cache Purge โ Purge pada satu zona. Token global API key milik akunmu bisa melakukan apa saja terhadap seluruh domainmu โ termasuk memindahkan DNS. Kalau container-mu dikompromikan, bedanya adalah antara "cache dibersihkan" dan "domain dibajak".
Memanggilnya di tempat yang benar
export const terbitkanArtikel = defineAction({
input: z.object({ id: z.number().int().positive() }),
handler: async ({ id }, ctx) => {
if (!boleh(ctx.locals.member?.peran, "artikel:terbitkan")) {
throw new ActionError({ code: "FORBIDDEN" });
}
// 1. Transaksi database dulu, dan selesai.
const artikel = await dbTulis.transaction().execute(async (trx) => {
await trx.updateTable("artikel")
.set({ status: "terbit", terbit_pada: new Date() })
.where("id", "=", id).where("status", "=", "tinjau")
.executeTakeFirstOrThrow();
return trx.selectFrom("artikel")
.select(["id", "kategori_id", "penulis_id"])
.where("id", "=", id)
.executeTakeFirstOrThrow();
});
// 2. Purge SETELAH commit.
await purgeTag([
`artikel-${artikel.id}`,
`kategori-${await slugKategori(artikel.kategori_id)}`,
`penulis-${artikel.penulis_id}`,
"beranda",
]);
return { ok: true };
},
});
Purge di dalam transaksi adalah bug yang tampak halus tapi nyata. Kalau kamu purge lalu transaksinya rollback, kamu baru saja membersihkan cache untuk perubahan yang tidak pernah terjadi โ dan permintaan berikutnya akan mengisi ulang cache dengan data lama. Lebih buruk: purge sebelum commit berarti ada jendela di mana cache sudah kosong tapi database belum berubah, sehingga versi lama di-cache ulang dan bertahan sampai TTL berikutnya.
Peta tag untuk portalmu
| Peristiwa | Tag yang di-purge |
|---|---|
| Artikel disunting | artikel-{id} |
| Artikel diterbitkan | artikel-{id}, kategori-{slug}, beranda, penulis-{id} |
| Artikel ditarik | Sama |
| Kategori diganti nama | kategori-{slug}, tipe-daftar |
| Data perusahaan diperbarui | perusahaan-{kode}, tipe-data |
| Menu situs berubah | Semua โ pakai prefix, bukan tag |
| Deploy baru | Tidak perlu โ aset ber-hash punya nama baru |
Kenapa Purge Everything berbahaya di trafikmu
Sebelum purge : 93% dilayani edge, ~21 req/detik ke origin
Purge Everything
Sesudah purge : 0% dilayani edge, ~347 req/detik ke origin
Origin-mu menerima 16 kali lipat beban normalnya, seketika, sampai
cache terisi kembali. Autoscaling butuh menit untuk bereaksi.
Yang terjadi lebih dulu adalah pool database habis.
Kalau kamu benar-benar harus purge semuanya โ misalnya setelah mengubah layout global โ
lakukan di jam paling sepi, naikkan jumlah task ECS lebih dulu, dan pertimbangkan memakai purge by
prefix bertahap per bagian situs alih-alih sekaligus. Dan pastikan
stale-while-revalidate-mu panjang, karena itu yang menahan gelombangnya.
Purge by prefix
await fetch(`https://api.cloudflare.com/client/v4/zones/${env.CF_ZONE_ID}/purge_cache`, {
method: "POST",
headers: { Authorization: `Bearer ${env.CF_API_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({
prefixes: ["portal.contoh.id/berita/ekonomi/"],
}),
});
Berguna untuk perubahan yang menyentuh seluruh cabang URL โ misalnya struktur kategori berubah. Batasnya 100 prefix per permintaan.
Kalau purge gagal
| Pilihan | Konsekuensi |
|---|---|
| Gagalkan action-nya | Editor melihat error padahal artikel sudah terbit |
| Catat dan lanjutkan | Artikel basi maksimal selama TTL โ untuk artikel 300 detik, itu bisa diterima |
| Antre dan coba lagi | Paling benar, butuh infrastruktur antrean |
Untuk portal berita, pilihan kedua hampir selalu tepat: kegagalan purge berarti pembaca melihat versi lama selama beberapa menit, sementara menggagalkan penerbitan berarti redaksi terhambat. Pasang alarm kalau tingkat kegagalan purge naik.
Latihan: pasang header Cache-Tag di halaman artikel dan kategori, lalu tulis
purgeTag(). Uji urutannya: muat artikel sampai cf-cache-status: HIT, ubah
judulnya di database, muat lagi (masih HIT dengan judul lama), panggil purge, lalu muat lagi โ harus
MISS dengan judul baru. Buat token API dengan izin purge saja, dan verifikasi ia tidak bisa
melakukan hal lain.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.