Pengenalan Masalah

Dalam sistem backend kami, terjadi fenomena di mana update data melalui mutation GraphQL sukses, tetapi klien tetap menerima data lama dari query karena cache Redis tidak ter-invalidasi. Di paragraf berikut ini saya jelaskan gejala awal sekaligus pendekatan debugging dan perbaikannya secara praktis.

Gejala Awal dan Observasi

Gejala awal muncul dari laporan tim QA: setelah mutation updateOrderStatus, UI tidak menunjukkan status terbaru dalam hitungan detik, padahal respons mutation sudah mencerminkan nilai baru. Beberapa log dari gateway GraphQL menunjukkan status HTTP 200, namun query getOrder masih mengembalikan cached value.

Log

Tim mencatat timestamp berikut:

  • Mutation berhasil pada 2024-10-01T10:15:23Z.
  • Query setelah mutation saat proses polling menuliskan x-cache: HIT di header log cache layer.

Dari log ini jelas cache masih menyajikan record lama.

Metrics & Tracing

Grafana menunjukkan latency tetap rendah, namun cache hit rate tetap tinggi tanpa spike invalidasi. Trace request GraphQL memperlihatkan pipeline berikut: validation → resolver → cache lookup. Tidak ada langkah eksplisit untuk invalidasi cache setelah resolver selesai.

Identifikasi Root Cause

Penyebab utama adalah pipeline cache tidak memanggil invalidasi setelah mutation, karena pengaturan Redis cache key yang tidak konsisten. Resolusi read dari query menggunakan key order:{orderId}, sementara mutation sempat diarahkan ke order_details:{orderId} karena refactor lama. Akibatnya, code invalidasi menghapus key order_details:123 yang tidak dipakai oleh read path.

Selain itu, TTL cache (5 menit) membuat data lama terus hidup selama key tidak dihapus. Dalam kondisi yang seharusnya kita targeted invalidation tanpa membiarkan TTL memaksa refresh.

Tindakan Perbaikan

1. Koreksi Logic Invalidasi

Change request mengarahkan ulang invalidasi ke key yang dipakai query. Pada resolver mutation kami menambahkan helper untuk menghapus beberapa key relevan.

async function updateOrderStatus(parent, { id, status }, { cache }) {
  const updatedOrder = await orderService.updateStatus(id, status);
  const cacheKeys = [
    `order:${id}`,
    `orderSummary:${id}`
  ];
  await Promise.all(cacheKeys.map((key) => cache.del(key)));
  return updatedOrder;
}

Helper cache.del mengeksekusi operasi Redis DEL. Penting agar cache invalidation dipanggil setelah update database agar konsistensi terpenuhi.

2. Deploy dan Verify

Setelah commit code, tes manual menggunakan mutation di staging memperlihatkan query langsung menampilkan data terbaru. Log menunjukkan x-cache: MISS untuk request pertama setelah mutation, lalu hit untuk request berikutnya.

3. Rollback Plan

Kami menyiapkan rollback cepat dengan git revert dan redeploy jika cache invalidation menyebabkan regressi lain, tetapi tidak perlu dijalankan karena perubahan berhasil.

Langkah Preventif

Monitoring

  • Tambahkan alert jika cache hit rate terlalu tinggi saat mutation berjalan.
  • Lacak jumlah cache invalidations per mutation di monitoring dashboard.

Automated Test

Tambahkan integrasi test yang memicu mutation lalu mengecek response query langsung setelahnya memastikan data sinkron. Contoh pseudo-test:

it('invalidates cache after update', async () => {
  await cache.set('order:123', 'stale');
  await mutate(updateOrderStatus, { id: 123, status: 'shipped' });
  const fresh = await query(getOrder, { id: 123 });
  expect(fresh.status).toBe('shipped');
  expect(cache.get('order:123')).not.toBe('stale');
});

Code Review Checklist

Tambahkan item review: Apakah mutation menghapus cache atau memperbarui key yang sama dengan query? Ini mencegah regresi serupa saat refactor cache key.

Ikhtisar Lesson Learned

  • Observasi cepat lewat log dan trace penting untuk membedakan antara masalah update data dan cache.
  • Cache key mismatch adalah sumber bug umum—selalu dokumentasikan dan standar key yang digunakan oleh query dan mutation.
  • Invalidasi manual lebih dapat diandalkan daripada menunggu TTL ketika data harus segera konsisten.
  • Monitoring & test memungkinkan pendeteksian dini jika cache tidak sinkron lagi.

Dengan pendekatan debugging ini, tim dapat memastikan cache Redis memuat data terbaru setelah mutation GraphQL dan mencegah kasus serupa masuk ke produksi.