Saat aturan tiga jam listrik gratis Australia mengaktifkan kuota, backend penagihan kami malah masuk ke loop retry tanpa henti. Penyebabnya adalah Metering Energi Gratis Memicu Retry Tak Terhenti yang berakar pada kontrak API idempotensi yang tidak sinkron, cache status layanan rate limit, dan validasi kuota yang terlewat. Dalam artikel ini kita langsung melihat gejala, observabilitas, serta perbaikan konkret agar sistem kembali stabil.

Gejala dan Observabilitas ketika Metering Energi Gratis Memicu Retry Tak Terhenti

Gejala pertama terlihat pada antrean job metering yang gagal namun terus direqueue dari background worker: setiap kali meter membaca pulsa listrik selama periode gratis, event gagal|retry muncul terus. Observability dashboard memperlihatkan spike latency dan retry-count pada Billing API, sementara meter service mencatat HTTP 409 dengan pesan quota already in process.

Data observability yang membantu:

  • Log terstruktur: menyertakan trace id, job id, dan state kuota.
  • Metrics: jumlah retry per job, time-to-success, serta cache hit ratio pada rate-limit service.
  • Trace distributed: menunjukkan meter->billing->rate-limit->billing (retry lagi).
2024-09-08T10:23:41.122Z billing-service retry=3 job=meter-234 result=quota-conflict
2024-09-08T10:23:41.124Z rate-limit cache_hit=true key=free-hours-123 user=abc123
2024-09-08T10:23:41.126Z billing-service start processing job=meter-234 idempotent_key=free-hours-123

Meski kode 409 menunjukkan penanganan retry yang diperlukan, tidak ada guard untuk menghentikan siklus ketika kuota sudah diproses dan cache belum diperbarui.

Analisis Akar Masalah Sistem Penagihan

Kontrak API dan Idempotensi Tumpang Tindih

Sistem memiliki dua endpoint: satu untuk pencatatan metering dan satu lagi untuk menyelesaikan penagihan. Keduanya menggunakan idempotency key berbasis kombinasi userId dan rentang gratis tiga jam. Saat metering mengirim event, server mencatat state in-progress. Namun, setelah kuota dinyatakan selesai, worker lain juga mencoba memproses karena cache tidak segera menyimpan state completed. Kontrak API tidak membedakan antara “kuota sedang diproses” dan “kuota selesai”, sehingga client terus retry dengan asumsi masih bisa selesai.

Query yang digunakan untuk memeriksa state:

SELECT status FROM free_energy_usage WHERE user_id = $1 AND period = $2 FOR UPDATE

Tapi jika job gagal setelah mengambil lock dan tidak mengubah status, retry berikutnya tidak menemukan status konsisten karena service menulis status hanya pada akhir pipe.

Konsistensi State Caching dan Perilaku Retry

Rate limit service menggunakan cache in-memory untuk mempercepat validasi kuota, tapi tidak menunggu eventual write ke database. Ketika meter service mengirim event kedua dalam selang beberapa detik, cache masih memegang nilai pending, men-trigger retry tanpa batas. Kami juga menemukan bahwa circuit breaker tidak memutus koneksi karena retryCount di-reset setiap job baru.

Strategi Perbaikan dan Mitigasi

Kami menerapkan tiga langkah perbaikan konkret:

  1. Perbaiki kontrak API: tambahkan status eksplisit LOCKED, COMPLETED, ABORTED; respon 409 sekarang menyertakan retry-after. API idempotent memeriksa status terakhir dan segera menolak retry jika status COMPLETED.
  2. Tambahkan circuit breaker dengan timeout di client metering agar tidak men-trigger job baru setelah serangkaian retry gagal; circuit breaker juga berfungsi sebagai backoff sehingga sistem tidak overload saat kuota bermasalah.
  3. Validasi kuota di billing service dengan menambahkan guard tambahan yang memeriksa timestamp akhir pemrosesan dan memastikan cache di-update walau terjadi kegagalan parsial.
function processFreeEnergy(event) {
  const key = `${event.userId}-${event.period}`;
  const state = db.selectStateForUpdate(key);

  if (state.status === 'COMPLETED') {
    return { status: 'IGNORED', message: 'Quota already settled' };
  }

  if (state.status === 'LOCKED' && state.lockedAt > Date.now() - LOCK_TIMEOUT) {
    return { status: 'RETRY', retryAfter: 5000 };
  }

  try {
    db.updateState(key, { status: 'LOCKED', lockedAt: Date.now() });
    // proses actual billing
    db.updateState(key, { status: 'COMPLETED', completedAt: Date.now() });
    cache.set(key, 'COMPLETED');
  } finally {
    circuitBreaker.success();
  }
}

Catatan: circuit breaker di atas reset ketika success dipanggil, dan LOCK_TIMEOUT mencegah lock lama menimbulkan retry tak terbatas.

Trade-off: menambahkan lock dan cache update menambah latency, tapi meningkatkan konsistensi dan menghilangkan retries tak perlu.

Pengujian dan Dampak

Pengujian melibatkan dua aspek:

  • Unit test: mem-verifikasi status berganti dengan urutan yang benar, termasuk failure path di mana update cache gagal.
  • End-to-end staging: menggunakan simulasi meter selama periode gratis tiga jam dan memonitor total retry. Setelah perbaikan, jumlah retry turun 95% dan tidak ada job yang stuck di retry queue lebih dari 20 detik.

Log setelah perbaikan menunjukkan pola yang stabil:

2024-09-08T11:13:22.001Z billing-service job=meter-345 status=LOCKED
2024-09-08T11:13:22.099Z billing-service status=COMPLETED cache_update=true
2024-09-08T11:13:22.101Z meter-service retries=0 feedback=done

Impact: siklus retry berhenti, throughput kamus meter meningkat, dan alert rate-limit tidak lagi memicu notifikasi false-positive.

Kesimpulan

Menganalisis Metering Energi Gratis Memicu Retry Tak Terhenti mengajarkan bahwa observabilitas log, kontrak API yang jelas, dan validasi state adalah kunci debugging. Dengan menambahkan status eksplisit, circuit breaker, dan guard kuota, sistem penagihan menjadi tangguh terhadap loop retry yang sebelumnya menghentikan layanan gratis tiga jam bagi pelanggan Australia.