Invalidasi Cache Redis: Naikkan Versi, Jangan Hapus Key
Ganti cara invalidasi cache jawaban chat AI: bump versi namespace dengan INCR supaya key lama unreachable, bukan dihapus satu-satu.
Ringkasan
Jawaban chat AI yang di-cache di Redis jadi basi tiap artikel berubah, dan masalah utamanya bukan SCAN yang blocking melainkan fan-out invalidasi yang rumit. Solusinya versioned key: tiap mutasi nge-bump counter versi via INCR, key lama jadi unreachable dan mati sendiri lewat TTL 24 jam. Bonusnya ada lookup mundur ke versi sebelumnya dan fallback persona saat LLM down.
Chat user balas pakai artikel yang kemarin saya revisi total. Jawabannya masih ambil paragraf versi lama, lengkap dengan klaim yang sudah saya hapus. Cache-nya. Jawaban chat AI saya cache di Redis biar hemat token dan latensi rendah, dan sekarang cache itu jadi bom waktu: setiap kali saya ubah artikel atau knowledge base, semua jawaban tersimpan langsung basi.
Endpoint-nya POST /articles/ask, jawaban LLM di-stream via SSE, pola yang juga dipakai panduan streaming LLM dengan cache Redis [4]. Karena streaming, jawaban yang udah jadi saya simpan utuh di Redis, lengkap dengan persona dan konteks artikel yang dipakai. Bayarannya: knowledge berubah, cache nggak ikut tau.
Dugaan pertama saya salah, tapi bukan di situ
Insting pertama: hapus aja semua key terkait. Jalankan SCAN, cocokin pola prefix cache jawaban, kumpulin, lalu DEL satu-satu. Terus saya baca docs perintah SCAN dan sadar dugaan saya soal kenapa ini buruk itu salah. Saya kira SCAN itu blocking kayak perintah KEYS, bakal nge-freeze Redis. Ternyata nggak. SCAN itu incremental, O(1) per call, dibuat justru buat mengganti KEYS yang bisa bikin server berhenti lama [1].
Yang bener-bener jadi masalah itu fan-out. Cache key jawaban saya nge-refer ke artikel, knowledge note, dan persona sekaligus. Satu artikel bisa dipakai ratusan jawaban, satu knowledge note nge-refer ke banyak artikel. Buat invalidasi saya harus tahu semua kombinasi key yang kena, dan itu butuh banyak round trip ke Redis, tiap kali data berubah. Glossary Redis sendiri nyebut koordinasi antar layer di cache invalidation itu bagian yang kompleks [3]. Bukan blocking-nya. Ilmu yang harus dipunyai, itu masalahnya.
Versioned key: invalidasi itu bikin unreachable, bukan delete
Solusinya geser pertanyaannya. Daripada "key mana aja yang harus dihapus", jadi "gimana bikin key lama nggak pernah diakses lagi". Versioned key: setiap namespace punya nomor versi di Redis, dan nomor itu di-bake ke dalam cache key. Data berubah, versi naik, key lama otomatis jadi sampah yang nggak pernah di-hit [5].
Cache key saya jadi kayak gini:
Contohnya begini: ask:v1:kn:2:art:7:e3b0c4 (angka 2 dan 7 itu nilai versi saat ini, ditutup hash pendek dari pertanyaan).
kn_ver dan art_ver itu counter per namespace. Saat saya edit artikel, hook mutasi di aplikasi nge-bump art_ver pakai INCR. Atomic, dan ini emang pola counter resmi di docs INCR [2]. Bump dijalanin fire-and-forget pakai asyncio.create_task, dipanggil setelah DB commit, jadi latency request nulis artikel nggak kena round trip Redis.
Buat perubahan yang ngehit dua namespace sekaligus, bump-nya dibungkus MULTI/EXEC biar dua counter naik bareng dan nggak ada klien lain yang nyempil di tengah [6].
Ada trade-off yang harus jujur saya akui: key lama nggak hilang. Dia tetap di memori, makan ruang, sampai TTL 86400 detik (24 jam) bikin dia mati sendiri. Saya bayar memori buat nggak perlu DEL massal. Buat skala saya, 24 jam key jawaban, itu murah.
Baca mundur, dan persona cadangan
Ada satu masalah yang muncul setelah bump: user yang lagi lihat jawaban versi lama. Solusi saya sederhana, pas lookup jangan cuma cek key versi sekarang, tapi MGET window historis maksimal 8 key, dari versi terbaru mundur ke belakang. Jadwal artikel berubah dua kali dalam sejam? Jawaban versi sebelumnya masih valid buat dibandingkan, dan hemat satu panggilan LLM.
Terus SSE-nya sendiri. Bump versi itu nggak bisa nyuruh stream yang lagi jalan buat berhenti, dan nggak usah. Stream aktif udah punya konteks di memorinya, biarin selesai. Yang penting request berikutnya dapat versi baru.
Satu lagi yang saya anggap wajib: fallback persona. Kalau LLM-nya down dan cache miss di semua versi, daripada kasih error mentah, jawab dengan persona statis yang jelasin sistem lagi nggak tersedia. Tanpa ini, gabungan cache invalidasi dan LLM yang lagi down bikin user lihat halaman rusak. Cached hallucination juga risiko yang saya waspadai: jawaban basi yang di-cache itu bukan cuma nggak informatif, dia yakin banget soal hal yang udah nggak benar.
Saya pribadi lebih milih pola versioned key daripada pub/sub invalidation antar worker. Pub/sub butuh semua worker aktif nge-listen, dan satu worker yang restart di momen salah itu udah cukup buat dia ngembaliin cache basi. Versioned key nggak bisa gagal kayak gitu, karena key basi itu bukan "belum dihapus", dia unreachable secara struktur.
Sumber:
- Redis Docs, SCAN [1]
- Redis Docs, INCR [2]
- Redis Glossary, Cache invalidation [3]
- Upstash Blog, SSE streaming LLM responses [4]
- Sonal Sadafal, Cache-Busting with Versioned Keys [5]
- Redis Docs, Transactions [6]
[1] https://redis.io/docs/latest/commands/scan [2] https://redis.io/docs/latest/commands/incr [3] https://redis.io/glossary/cache-invalidation [4] https://upstash.com/blog/sse-streaming-llm-responses [5] https://medium.com/@sonal.sadafal/cache-busting-with-versioned-keys-keep-your-cache-fresh-fast-and-reliable-0e96a7194a27 [6] https://redis.io/docs/latest/develop/using-commands/transactions