Konteks dan cakupan
Sebuah kunci konfigurasi produk yang sangat populer (hot key) menerima 50.000 pembacaan per detik. Basis data sumber aman hingga 200 kueri per detik, dan proses pembangunan ulang cache membutuhkan waktu 800 ms pada p95. Data yang di-cache berstatus segar (fresh) selama 10 menit, dan bisnis mentoleransi keusangan data (staleness) paling lama 30 detik. Layanan ini berjalan pada 100 instance aplikasi stateless yang berbagi cache jarak jauh (remote cache) dan basis data sumber yang sama.
Rancang jalur pembacaan dan penyegaran yang lengkap. Cakup soft expiry, hard expiry, pemuatan awal saat cache kosong (cold load), crash pada refresher, pemadaman cache (cache outage), dan perubahan data sumber selama proses penyegaran. Jelaskan bagaimana Anda akan membuktikan bahwa desain tersebut tidak sekadar memindahkan beban konkuren ke basis data. Nilai throughput, latensi, dan kedaluwarsa adalah input wawancara, bukan klaim performa tentang suatu produk.
Ini adalah pertanyaan keandalan backend. Serangkaian pertanyaan wawancara SRE publik tahun 2026 saat ini secara eksplisit menyajikan skenario cache stampede yang disebabkan oleh kedaluwarsanya kunci penting di bawah beban 100.000 permintaan per detik; versi ini mengubah skenario tersebut menjadi batasan rekayasa yang terukur. Ini berbeda dari implementasi kebijakan eviksi LRU dalam proses karena masalah utamanya adalah konkurensi lintas instance dan semantik kegagalan.
Apa yang dinilai oleh pewawancara
Pertama, kandidat harus mengukur peristiwa kedaluwarsa secara kuantitatif. Jika setiap permintaan yang datang melewati cache selama pembangunan ulang 800 ms, sekitar 50,000 × 0.8 = 40,000 permintaan dapat mencoba menjangkau basis data asal. Angka tersebut jauh melampaui batas aman 200 kueri per detik. Menambahkan cache tidak menyelesaikan masalah beban tersinkronisasi ketika entri cache hilang.
Kedua, jawaban yang kuat memisahkan tiga lapisan perlindungan. Local request coalescing hanya membatasi satu proses; jika masing-masing dari 100 instance memilih satu refresher, sistem masih dapat mengirimkan 100 pembangunan ulang secara bersamaan. Distributed lease biasanya mengurangi penyegaran lintas-instance menjadi satu, tetapi kedaluwarsa lease, jeda proses, dan partisi jaringan masih dapat menimbulkan penyegaran yang tumpang tindih. Oleh karena itu, origin concurrency bulkhead atau rate limit harus tetap menjadi pertahanan terakhir yang independen.
Ketiga, semantik kedaluwarsa harus dibuat eksplisit. Data segar langsung dikembalikan. Setelah soft expiry, data boleh dikembalikan dalam batas jendela usang 30 detik sementara penyegaran dicoba di latar belakang. Setelah hard expiry, sistem tidak boleh menyajikan data usang selamanya: permintaan yang tidak memiliki hak penyegaran harus menunggu dalam batas waktu tertentu, melakukan degradasi layanan, atau gagal daripada semuanya mengakses basis data asal.
Terakhir, pewawancara ingin mendengar bagaimana desain tersebut mencegah refresher lama menimpa nilai yang lebih baru. Lease hanya memberikan eksklusi selama jendela validitasnya; ini bukan jaminan exactly-once. Entri cache memerlukan versi atau generasi sumber, dan operasi penulisan harus membandingkan versi sebelum mengganti data.
Pertanyaan untuk diklarifikasi
- Apakah data usang benar-benar aman disajikan? Skenario ini mengizinkan paling lama 30 detik. Saldo, otorisasi, dan pemotongan inventaris mungkin memerlukan perilaku yang lebih ketat.
- Apa yang diukur oleh batas kapasitas basis data asal? Perlakukan 200 kueri per detik sebagai batas aman seluruh basis data, sambil menanyakan batas konkurensi dan batas waktu (timeout) per kunci.
- Bisakah cold load mengembalikan nilai default? Secara default tidak ada nilai usang yang tersedia, sehingga hanya satu refresher yang mengakses basis data asal dan follower menunggu dalam batas waktu tertentu. Nilai default statis adalah bentuk degradasi produk eksplisit jika tersedia.
- Bagaimana entri diinvalidasi? Gunakan penanda waktu kesegaran logis dan hard-expiry alih-alih menghapus setiap salinan secara fisik pada detik yang sama. Peristiwa perubahan sumber dapat menyegarkan atau menginvalidasi entri lebih awal.
- Apakah arsitektur ini multi-region? Mulailah dengan 100 instance di satu region. Multi-region memerlukan alokasi anggaran kapasitas ke basis data asal; lock lintas-region tidak boleh diasumsikan dapat menyelesaikan setiap mode kegagalan.
- Haruskah pemanggil mengetahui bahwa data yang diterima usang? Respons internal dan telemetri setidaknya harus mencatat
stale_agedan alasan degradasi. Persyaratan produk menentukan apakah pengguna akhir dapat melihatnya. - Apa yang terjadi jika cache gagal berfungsi? Tentukan degradasi dan anggaran beban basis data asal terlebih dahulu. Kesalahan cache tidak boleh menyebabkan setiap permintaan langsung mengueri basis data.
Jawaban 30 detik
“Saya akan menyimpan fresh_until, stale_until, dan versi sumber bersama nilainya di dalam cache. Hit data segar dikembalikan secara langsung. Selama jendela usang 30 detik, kembalikan nilai lama dan pilih satu refresher menggunakan singleflight per proses ditambah distributed lease dengan token kepemilikan dan TTL. Pada kondisi cold miss atau hard miss, follower hanya menunggu dalam batas waktu tertentu dan membaca ulang dengan jeda acak (jitter). Penulisan penyegaran membandingkan versi sumber, dan pelepasan lease memverifikasi kecocokan token. Karena berakhirnya lease masih dapat memungkinkan tumpang tindih, basis data juga membutuhkan bulkhead per kunci dan global. Saya akan memvalidasi QPS basis data asal, konkurensi, dan usia keusangan maksimum dengan uji beban saat kedaluwarsa, simulasi crash refresher, lease overrun, dan simulasi pemadaman cache.”
Pembahasan mendalam langkah demi langkah
Mulailah dengan mendefinisikan struktur entri cache daripada hanya menyimpan nilai bisnis:
CacheEntry {
value
source_version
generated_at
fresh_until
stale_until
}fresh_until menandai akhir dari periode kesegaran 10 menit, dan stale_until memperpanjangnya tidak lebih dari 30 detik. TTL fisik kunci jarak jauh harus mencakup stale_until ditambah margin pembersihan kecil; jika tidak, cache akan menghapus nilai yang sebenarnya masih aman disajikan selama masa soft expiry. Jendela usang adalah anggaran bisnis dan tidak boleh diperpanjang secara diam-diam setelah kegagalan penyegaran berulang.
Jalur pembacaan dapat dinyatakan sebagai pseudocode:
entry = cache.get(key)
now = clock.now()
if entry exists and now < entry.fresh_until:
return entry.value
if entry exists and now < entry.stale_until:
try_refresh_async(key)
return entry.value
return rebuild_or_wait(key, request_deadline)Jalur soft-expiry melindungi latensi permintaan. Permintaan pertama yang mendeteksi soft expiry akan mencoba melakukan penyegaran latar belakang sementara permintaan lain tetap menggunakan nilai lama. Singleflight per proses menggabungkan panggilan penyegaran berdasarkan kunci; implementasi publik di Go mendefinisikan ini sebagai satu eksekusi yang sedang berjalan per kunci yang hasilnya dibagikan ke pemanggil duplikat. Ini tidak melintasi batas proses, sehingga tidak cukup jika hanya berdiri sendiri di 100 instance.
Gunakan lease dengan batas waktu untuk penyegaran lintas-instance. Kandidat membuat token acak yang tidak dapat digunakan ulang dan mengeksekusi:
SET refresh:{key} {token} NX PX {lease_ms}Setelah mendapatkan lease, baca ulang cache untuk mengantisipasi jika refresher lain baru saja selesai, dan kueri basis data asal hanya jika penyegaran memang masih diperlukan. Lepaskan lease hanya jika nilainya saat ini masih sama dengan token milik pemilik. Penggunaan DEL biasa tidak aman: refresher lama dapat mengalami jeda (pause) hingga lease kedaluwarsa, penggantinya dapat memperoleh lease baru, dan proses lama dapat kembali aktif lalu menghapus lease milik penggantinya. Panduan distributed-lock Redis juga mewajibkan nilai unik dan pemeriksaan kepemilikan untuk pelepasan yang aman.
Atur lease_ms di atas p99 penyegaran yang terukur ditambah margin jaringan dan penjadwalan, bukan terpaku secara mekanis pada p95 sebesar 800 ms. Lease yang terlalu pendek meningkatkan risiko tumpang tindih; lease yang terlalu panjang menunda pengambilalihan setelah terjadi crash. Berakhirnya lease hanya mengizinkan kandidat baru untuk mencoba; ini tidak membuktikan bahwa operasi lama telah berhenti. Oleh karena itu, pembacaan ke basis data asal tetap membutuhkan layanan singleflight per kunci atau bulkhead basis data, dan penulisan cache harus mentoleransi eksekusi yang tumpang tindih.
Baca versi sumber yang monoton bersama datanya, seperti versi baris atau urutan event. Ganti cache hanya jika new.source_version >= cached.source_version. Jika sumber tidak memiliki versi yang andal, alokasikan generasi penyegaran dengan atomic increment pada penyimpanan koordinasi bersama dan bandingkan secara atomik di cache. Cache tetap bukan merupakan sumber kebenaran tunggal (source of truth); penyegaran yang tertunda tidak boleh menimpa versi yang lebih baru yang sudah dituliskan oleh suatu event perubahan.
Hard expiry atau pemuatan pertama tidak memiliki nilai lama yang dapat diterima. Pemilik lease melakukan pembangunan ulang hanya setelah masuk ke dalam bulkhead basis data asal. Follower membaca ulang cache pada interval pendek dengan jitter dalam batas deadline permintaan, alih-alih melakukan polling secara serempak. Ketika batas waktu tunggu tersebut habis, kembalikan respons terdegradasi yang eksplisit atau error. Nilai default statis dapat digunakan jika diizinkan oleh produk, namun ketersediaan semu tidak boleh mengorbankan sistem dengan mengirimkan seluruh 50.000 permintaan ke basis data asal.
Pertahanan terakhir pada basis data asal harus mencakup batas konkurensi per kunci, batas konkurensi penyegaran global, dan anggaran laju kueri. Dalam kondisi normal hanya satu pembangunan ulang yang berjalan untuk satu kunci. Selama terjadi kegagalan lease, bulkhead tetap menjaga total beban basis data asal di dalam batas aman. Ketika kapasitas habis, penyegaran harus gagal dengan cepat (fail fast) atau masuk ke antrean berbatas; permintaan yang mengalami soft-expired terus menggunakan nilai yang berusia kurang dari 30 detik, sementara hard miss mengikuti kebijakan degradasi yang telah ditentukan.
Ketika cache tidak tersedia, aplikasi tidak boleh mengalihkan seluruh laju pembacaan ke basis data. Near-cache read-only berumur pendek dapat menyediakan nilai usang yang masih dapat diterima, namun semua pembacaan asal yang diperlukan harus tetap melewati bulkhead global. Permintaan tanpa nilai near-cache harus diturunkan kualitasnya (degrade) atau digagalkan. Lakukan pemanasan kunci populer (warm hot keys) dengan laju terkendali selama pemulihan, alih-alih membiarkan setiap instance mengisi ulang secara bersamaan. Jitter TTL acak membantu ketika banyak kunci berbeda kedaluwarsa bersamaan, tetapi tidak menyelesaikan masalah pembangunan ulang konkuren pada satu hot key.
Mode kegagalan yang terkait harus tetap dibedakan. Cache stampede atau hot-key breakdown adalah pembangunan ulang konkuren setelah hot key yang ada menjadi tidak tersedia. Cache avalanche adalah kedaluwarsanya banyak kunci secara simultan atau pemadaman seluruh cache. Cache penetration adalah pencarian berulang untuk data yang sebenarnya tidak ada. Jitter TTL terutama membantu menangani avalanche, sementara negative cache berdurasi pendek atau Bloom filter membantu menangani penetration; keduanya tidak menggantikan peran request coalescing untuk skenario ini.
Data yang dapat diprediksi kepopulerannya dapat disegarkan sebelum fresh_until. Pendekatan validasi ulang dini probabilistik (probabilistic early-revalidation) yang dipublikasikan oleh Cloudflare meningkatkan probabilitas penyegaran seiring mendekatnya waktu kedaluwarsa, sehingga mengurangi perebutan lock di bawah laju permintaan tinggi. Ketentuan statis seperti “segarkan 1% permintaan” tidak aman karena perilakunya berubah-ubah mengikuti volume lalu lintas. Jendela usang yang berbatas dan penyegaran latar belakang juga cocok dengan semantik HTTP stale-while-revalidate: konten usang hanya diizinkan dalam interval eksplisit saat validasi ulang berlangsung secara asinkron.
Untuk lingkungan multi-region, utamakan cache regional dan refresher regional dengan anggaran QPS serta konkurensi basis data asal yang telah dialokasikan. Satu lease global tunggal menambah latensi lintas-region dan risiko partisi pada jalur pembacaan. Jika setiap region berbagi satu basis data asal yang sama, control plane dapat mengalokasikan anggaran penyegaran atau basis data asal dapat menyediakan layanan pembangunan ulang terpusat. Dalam kedua kasus tersebut, jumlah total anggaran regional harus tetap berada di dalam batas 200 kueri per detik.
Pantau metrik laju fresh-hit, stale-served, dan hard-miss; usia keusangan (stale age); percobaan dan kegagalan penyegaran; pemanggil bersama singleflight; perebutan dan kedaluwarsa lease; latensi penyegaran; QPS, konkurensi, serta penolakan pada basis data asal; dan latensi serta error pada cache. Buat peringatan untuk anggaran basis data asal yang habis, nilai yang mendekati stale_until, dan kegagalan penyegaran yang berkelanjutan, alih-alih hanya mengandalkan rasio cache hit.
Uji invariannya secara langsung. Buat kunci kedaluwarsa di bawah beban 50.000 permintaan per detik dan pastikan hanya satu pembangunan ulang basis data asal per kunci pada kondisi normal. Simulasikan crash pada refresher sebelum proses penulisan dan verifikasi bahwa data usang tetap tersedia serta penerus mengambil alih setelah lease kedaluwarsa. Berikan jeda pada refresher lama melebihi durasi lease dan verifikasi bahwa ia tidak dapat mengganti versi yang lebih baru. Nonaktifkan cache dan verifikasi bahwa basis data asal tetap beroperasi dalam batas 200 kueri per detik dan bulkhead konkurensinya. Buat banyak kunci kedaluwarsa bersamaan dan verifikasi efektivitas jitter TTL beserta anggaran global.
Contoh jawaban yang kuat
“Saya akan mulai dari beban kasus terburuk. Lima puluh ribu permintaan per detik dikalikan dengan pembangunan ulang 800 ms menghasilkan sekitar 40.000 permintaan yang datang selama jendela kedaluwarsa, sementara basis data hanya aman untuk 200 kueri per detik. Tidak boleh ada follower yang langsung lolos ke basis data asal.
Saya akan menyimpan nilai tersebut di cache bersama versi sumbernya, fresh_until, dan stale_until. Kembalikan secara langsung selama 10 menit, lalu sajikan hingga 30 detik tambahan sementara mencoba melakukan penyegaran. Setiap proses pertama-tama menggabungkan beban lokal dengan singleflight, kemudian kandidat menggunakan SET lock token NX PX lease untuk memilih satu refresher lintas-instance. Pemilik memeriksa ulang cache sebelum mengueri basis data. Pelepasan lease membandingkan token, dan penulisan cache membandingkan versi sumber sehingga refresher lama tidak dapat menghapus lease baru atau menimpa data yang lebih baru.
Pada cold load atau setelah 30 detik, tidak ada nilai usang yang dapat diterima. Satu permintaan melakukan pembangunan ulang sementara follower membaca ulang dengan jitter hingga batas deadline mereka, lalu menggunakan degradasi default eksplisit atau mengembalikan error. Basis data juga memiliki bulkhead per kunci dan global karena kedaluwarsa lease dapat memungkinkan refresher yang tumpang tindih; sebuah lock tidak menggantikan perlindungan kapasitas.
Saya akan melakukan uji beban tepat pada batas kedaluwarsa dan menginjeksikan crash refresher, jeda yang lebih lama dari lease, kegagalan cache, dan pembaruan sumber yang konkuren. Kriteria penerimaan mencakup QPS basis data asal tidak lebih tinggi dari 200, satu pembangunan ulang normal per kunci, usia keusangan tidak lebih dari 30 detik, dan tidak ada regresi versi akibat penulisan yang tertunda. Hal ini memberikan batasan terukur pada latensi, kesegaran data, dan keamanan basis data asal.”
Kesalahan umum
- Hanya menambahkan jitter TTL acak → Ini menyebarkan waktu kedaluwarsa pada kunci yang berbeda tetapi tidak menghentikan pembangunan ulang konkuren pada satu hot key → Gabungkan pekerjaan per kunci dan pertahankan bulkhead basis data asal.
- Hanya menggunakan singleflight dalam proses → Seratus instance masih dapat menciptakan 100 refresher → Kombinasikan penggabungan lokal dengan lease lintas-instance.
- Membaca basis data sebelum memperoleh hak penyegaran → Lonjakan konkurensi telah terlanjur mencapai basis data asal → Lakukan pemilihan terlebih dahulu, periksa ulang cache, lalu gunakan anggaran basis data asal.
- Mengambil lock
SETNXtanpa TTL → Refresher yang mengalami crash dapat memblokir pembaruan tanpa batas waktu → Gunakan lease berbatas waktu dengan mekanisme pengambilalihan. - Melepaskan dengan
DELbiasa → Refresher lama dapat menghapus lease milik proses penerusnya → Lepaskan secara atomik hanya jika token unik masih cocok. - Menganggap lease sebagai eksekusi exactly-once → Proses yang terhenti melebihi TTL dapat berjalan tumpang tindih dengan penerusnya → Gunakan bulkhead basis data asal dan penulisan berversi untuk mentoleransi tumpang tindih.
- Menyajikan data usang selamanya setelah kegagalan → Usia data kehilangan batas atasnya → Sajikan hanya sebelum
stale_until, lalu lakukan degradasi atau gagal secara eksplisit. - Mengirim semua lalu lintas ke basis data asal saat pemadaman cache → 50.000 pembacaan per detik akan membebani basis data asal yang hanya aman untuk 200 kueri → Gunakan nilai near-cache jika diizinkan dan arahkan setiap pembacaan asal melalui anggaran bersama.
- Mencampuradukkan stampede, avalanche, dan penetration → Solusi tidak lagi sesuai dengan jenis kegagalan → Gunakan request coalescing, jitter TTL, dan negative caching untuk masalahnya masing-masing.
- Hanya memantau hit rate → Hit rate yang tinggi dapat menyembunyikan kegagalan penyegaran dan lonjakan singkat pada basis data asal → Pantau juga usia keusangan, konkurensi penyegaran, lease, dan anggaran basis data asal.
Pertanyaan lanjutan
Pertanyaan lanjutan 1: Bagaimana jika bisnis sama sekali tidak boleh menyajikan data usang?
Hapus respons data usang dan biarkan follower menunggu hanya dalam batas waktu tertentu. Sediakan kapasitas pembangunan ulang yang cukup dan kembalikan kegagalan secara eksplisit tanpa mengorbankan keamanan basis data asal.
Pertanyaan lanjutan 2: Berapa lama seharusnya TTL lease?
Mulailah dari p99 penyegaran yang terukur, batas waktu jaringan, dan jeda penjadwalan, lalu tambahkan margin dan amati seberapa sering proses berjalan melampaui durasi lease. Nilai p95 sebesar 800 ms tidak cukup jika hanya digunakan sendiri.
Pertanyaan lanjutan 3: Bagaimana jika data sumber berubah selama proses penyegaran?
Baca dan sertakan versi sumber, lalu perbarui cache secara kondisional. Versi lebih baru yang dipasang oleh event perubahan tidak boleh ditimpa oleh penyegaran yang tertunda.
Pertanyaan lanjutan 4: Bagaimana jika seluruh kluster cache tidak tersedia?
Sajikan nilai near-cache yang dapat diterima, pertahankan setiap pembacaan asal yang diperlukan di belakang bulkhead global, turunkan layanan jika tidak ada salinan yang tersedia, dan lakukan pemanasan kunci pada laju terkendali selama pemulihan.
Pertanyaan lanjutan 5: Di mana peran negative caching?
Cache hasil konfirmasi "tidak ditemukan" dengan TTL pendek untuk mencegah penetration. Ini independen dari penggabungan penyegaran untuk hot key yang ada.
Pertanyaan lanjutan 6: Kapan penyegaran awal probabilistik berguna?
Untuk data yang dapat dihitung ulang, mentoleransi keusangan, dan bertrafik tinggi. Probabilitas harus bergantung pada sisa waktu kesegaran dan lalu lintas yang diamati, dengan kegagalan penyegaran dan anggaran basis data asal tetap ditegakkan.
Pertanyaan lanjutan 7: Haruskah region berbagi satu lock yang sama?
Biasanya tidak. Lakukan penyegaran secara regional dan alokasikan anggaran kapasitas basis data asal. Koordinasi terpusat hanya dibenarkan jika penyegaran tunggal global diwajibkan dan latensi serta partisi lintas-region dapat diterima.
Pertanyaan lanjutan 8: Bagaimana Anda membuktikan bahwa desain tersebut berhasil?
Jalankan beban 50.000 permintaan per detik melintasi soft dan hard expiry sambil menginjeksikan crash, jeda proses, kegagalan cache, dan race condition pada versi. Pastikan satu penyegaran normal per kunci, beban basis data asal sesuai anggaran, keusangan maksimal 30 detik, dan tidak ada penurunan (rollback) versi.