Portal/Notes πŸ“
Programming

Idempotensi

Implementasi idempotensi pada API β€” mencegah duplikasi pembayaran dan efek samping yang tidak diinginkan dengan idempotency key.

Versi Bahasa Indonesia. Baca dalam Bahasa Inggris

Menerapkan idempotensi memastikan bahwa request API yang berulang memiliki efek yang sama dengan satu request, bahkan jika di-retry karena masalah jaringan atau retry dari sisi klien. Dalam konteks API layanan pembayaran, ini sangat penting untuk mencegah duplikasi biaya atau efek samping yang tidak diinginkan.

Bagaimana Cara Menerapkannya?

Gunakan Idempotency Key

  • Klien menghasilkan identifier unik (idempotency_key) untuk setiap request.
  • Key ini dikirim bersama request API (misalnya di header atau body).
  • Server menggunakan key ini untuk melacak apakah request sudah diproses.
  • Hasilkan UUID atau identifier unik serupa untuk setiap request yang dimulai klien.

Simpan dan Periksa Status Request

  • Pertahankan catatan request yang sudah diproses dengan idempotency_key yang sesuai.
  • Ketika request diterima:
    • Jika key ada, kembalikan respons yang tercatat sebelumnya.
    • Jika key tidak ada, proses request dan simpan hasilnya.
    • Jika key duplikat digunakan dengan data request yang berbeda, kembalikan error.

Tangani Race Condition

  • Gunakan database lock atau operasi atomik untuk memastikan beberapa request simultan dengan key yang sama ditangani dengan benar.

Tetapkan Masa Berlaku Idempotency Key

  • Simpan catatan request yang diproses untuk waktu terbatas (misalnya 24-48 jam) untuk menghindari penyimpanan data tanpa batas.

Gunakan Status Code yang Tepat

  • 201 Created: Untuk request baru yang berhasil diproses.
  • 200 OK: Untuk request duplikat yang mengembalikan hasil cache.

Contoh Kasus: Payment Service API

Struktur Request

Klien mengirim request POST untuk memulai pembayaran.

API Endpoint: POST /payments

Request Body:

{
  "idempotency_key": "123e4567-e89b-12d3-a456-426614174000",
  "amount": 100.00,
  "currency": "USD",
  "payment_method": "credit_card",
  "customer_id": "98765"
}

Implementasi Server-Side

Pseudocode:

func CreatePayment(w http.ResponseWriter, r *http.Request) {
    var req PaymentRequest
    json.NewDecoder(r.Body).Decode(&req)

    // Periksa apakah idempotency key sudah ada di database
    existingPayment, found := GetPaymentByIdempotencyKey(req.IdempotencyKey)
    if found {
        // Kembalikan respons sebelumnya jika request sudah diproses
        w.WriteHeader(http.StatusOK)
        json.NewEncoder(w).Encode(existingPayment)
        return
    }

    // Proses pembayaran
    payment := ProcessPayment(req)

    // Simpan hasil di database
    SavePaymentWithIdempotencyKey(req.IdempotencyKey, payment)

    // Kembalikan hasil
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(payment)
}

Dampak Latensi

Dampak dari memeriksa atau menegakkan idempotency key di database tergantung pada beberapa faktor, seperti sistem database, indexing, desain skema, dan beban keseluruhan pada database.

Memeriksa Idempotency Key

Ketika klien mengirim request dengan idempotency_key, server biasanya melakukan query ke database untuk memeriksa apakah key tersebut ada.

Contoh Query

SELECT * FROM payments WHERE idempotency_key = 'some-unique-key';

Dampak Latensi

  • Jika field idempotency_key di-index (misalnya UNIQUE constraint atau primary key), pencariannya cepat (O(log N) untuk B-tree index).
  • Tanpa index, database perlu melakukan full table scan, menyebabkan performa lebih lambat (O(N)).
  • Di bawah beban berat, waktu query database dapat meningkat, terutama jika database tidak dioptimalkan atau kurang skalabilitas horizontal.

Menggunakan UNIQUE Constraint

Jika key duplikat dimasukkan, database akan menolak record baru dan melempar error. Ini ditangani secara efisien oleh sebagian besar database relasional karena pemeriksaan adalah bagian dari operasi penyisipan (atomik).

Database melakukan pencarian index sebelum penyisipan:

  • Jika key sudah ada, operasi insert gagal segera.
  • Jika key tidak ada, record dimasukkan.

Dampak Latensi

  • Dampaknya minimal karena UNIQUE constraint bergantung pada index.
  • Database modern seperti PostgreSQL atau MySQL dapat menangani jutaan baris dengan peningkatan latensi minimal ketika UNIQUE index dikonfigurasi dengan benar.

Perkiraan Overhead Latensi

  • Index Lookup: Biasanya dalam kisaran beberapa milidetik (1-5 ms) untuk tabel yang di-index dengan baik dengan ukuran sedang (jutaan baris).
  • Insert dengan UNIQUE Constraint: Menambahkan overhead yang dapat diabaikan (~1-2 ms) dibandingkan dengan insert non-unique.
OperasiPerkiraan Latensi
Query untuk memeriksa idempotency key1-5 ms
Insert dengan pemeriksaan UNIQUE constraint1-3 ms

Jika latensi menjadi perhatian kritis di API Anda, menyimpan cache idempotency key di in-memory store yang cepat (seperti Redis) sangat disarankan.

Caching Idempotency Keys

Menerapkan caching idempotensi di Redis adalah cara efisien untuk mengurangi beban database dan meningkatkan latensi API sambil mempertahankan manfaat idempotensi. Redis, sebagai in-memory store, menawarkan operasi berkecepatan tinggi yang ideal untuk tujuan ini.

Implementasi

Simpan Idempotency Keys di Redis

  • Ketika klien mengirim request dengan idempotency_key:
  • Periksa apakah key ada di Redis.
    • Jika ada:
      • Kembalikan hasil cache.
    • Jika tidak ada:
      • Proses request.
      • Simpan idempotency_key dan hasil di Redis dengan waktu kedaluwarsa.

Tetapkan Waktu Kedaluwarsa

  • Gunakan TTL (Time-to-Live) untuk membatasi berapa lama key disimpan di Redis. TTL tipikal mungkin 24-48 jam, tergantung pada logika bisnis.
  • Ini memastikan Redis tidak menyimpan key basi tanpa batas, mengoptimalkan penggunaan memori.

Menangani Kasus Tepi

Idempotency Keys Kedaluwarsa

  • Jika klien mencoba lagi setelah key kedaluwarsa di Redis:
    • Periksa database untuk pembayaran yang sudah diproses sebagai fallback.
    • Jika tidak ada catatan, perlakukan sebagai request baru.

Redis Downtime

  • Terapkan mekanisme fallback untuk melakukan query ke database jika Redis tidak tersedia.
  • Gunakan pola [[circuit_breaker]] untuk menangani gangguan dengan baik.

Request Konkuren

  • Redis mendukung operasi atomik seperti SETNX (Set if Not Exists), yang mencegah race condition.

  • Contoh:

    SETNX idempotency:<idempotency_key> <response_payload>

Kesimpulan

Menyimpan cache idempotency keys di Redis secara signifikan mengurangi latensi database dan meningkatkan responsivitas API Anda. Dengan menggabungkan TTL, operasi atomik, dan mekanisme fallback, pendekatan ini memberikan keandalan dan performa untuk sistem dengan traffic tinggi.


Edit on GitHub

Last updated on

Idempotensi | Faisal Affan