Deduplikasi dan idempotensi sama-sama mengurangi efek pengulangan, tetapi bekerja pada boundary berbeda. Deduplikasi menahan pesan yang sudah terlihat, sedangkan idempotensi memastikan operasi berulang menghasilkan keadaan yang sama. Mengandalkan salah satunya saja dapat meninggalkan celah pada retry atau replay.
Perbedaannya terletak pada pertanyaan yang dijawab
Deduplikasi bertanya, “apakah saya sudah melihat identitas pesan ini dalam periode tertentu?” Jika ya, pesan dialirkan ke cabang duplikat atau dihentikan. Idempotensi bertanya, “jika operasi ini dijalankan lagi, apakah hasil akhirnya tetap sama?” Ia biasanya ditegakkan oleh API tujuan, unique constraint, ledger, atau operasi yang memang bersifat set.
| Aspek | Deduplikasi | Idempotensi |
|---|---|---|
| Boundary | Sebelum/di dalam workflow | Pada efek tujuan |
| State | Key dengan TTL | Key atau aturan domain |
| Setelah retensi | Pesan dapat dianggap baru | Masih aman bila key dipertahankan |
| Contoh | Tolak event_id berulang | Return hasil pembayaran pertama |
Kualitas perlindungan bergantung pada kunci
Kunci harus stabil pada seluruh attempt untuk kejadian yang sama. UUID yang dibuat ulang oleh workflow setiap menerima request tidak dapat mendeteksi pengulangan dari sumber. Gunakan event_id, nomor transaksi, kombinasi tenant dan external ID, atau hash canonical payload bila kontrak memang tidak menyediakan identitas.
Hash payload perlu hati-hati: perubahan urutan field atau nilai nonbisnis dapat membuat key berbeda, sementara dua kejadian sah dengan payload sama dapat dianggap duplikat. Kunci yang berasal dari identitas domain biasanya lebih dapat dijelaskan ketika audit atau rekonsiliasi dilakukan.
TTL dan retensi adalah keputusan bisnis
State deduplikasi tidak selalu disimpan selamanya. TTL perlu mencakup jendela retry dan replay yang masuk akal. TTL terlalu pendek membuka peluang duplikasi terlambat; TTL terlalu panjang menambah state dan mungkin menolak kejadian yang sah bila sumber menggunakan ulang identifier.
NitroNet menyediakan node Deduplikasi dengan state PostgreSQL dan TTL. Pada eksekusi sinkron, reservasi key dilepas ketika downstream gagal, kecuali kebijakan keep_on_failure dipilih. Pada jalur asynchronous, lifecycle delivery terpisah sehingga reservasi bertahan sampai TTL. Perbedaan ini perlu dipahami saat mendesain replay.
Menggabungkan deduplikasi dan idempotensi
- Validasi identitas.
Tolak request tanpa event ID yang diwajibkan kontrak.
- Deduplikasi di ingress.
Kurangi pemrosesan ulang dengan key dan TTL yang terdokumentasi.
- Teruskan key ke tujuan.
Gunakan key yang sama pada header atau idempotency header.
- Tegakkan constraint.
Unique key atau ledger menjadi pagar ketika state workflow tidak tersedia.
- Rekonsiliasi hasil.
Hubungkan respons lama dan baru menggunakan correlation ID.
Contoh pemrosesan order
Sistem e-commerce mengirim order_event_id dan order_id. Node Deduplikasi memakai event ID agar event yang sama tidak menjalankan mapping lagi. Tulis DB menggunakan order ID dengan unique constraint. Bila insert timeout setelah commit dan delivery diulang, constraint mencegah order kedua; workflow dapat memeriksa record yang ada sebelum menentukan respons.
Event lain untuk order yang sama—misalnya status berubah—harus mempunyai event ID berbeda. Karena itu deduplikasi tidak boleh hanya memakai order ID bila beberapa kejadian sah dapat terjadi sepanjang lifecycle order.
Perhatikan concurrency ketika dua pesan dengan key sama tiba hampir bersamaan. Pemeriksaan “cek lalu simpan” yang tidak atomik memungkinkan keduanya lolos sebelum state tercatat. Reservasi perlu dilakukan sebagai satu operasi database dengan constraint atau locking yang tepat. Uji paralel harus menjadi bagian test, bukan hanya mengirim request yang sama secara berurutan.
Definisikan juga respons cabang duplikat. Sebagian konsumen cukup menerima hasil sukses sebelumnya, sebagian perlu HTTP conflict, dan proses event streaming mungkin hanya mencatat bahwa pesan diabaikan. Keputusan harus konsisten dengan kontrak sumber. Jangan menghapus bukti duplicate terlalu cepat; jumlah dan sumber pengulangan dapat mengungkap bug retry, webhook provider yang berulang, atau penyalahgunaan endpoint.
Dokumentasikan format key agar perubahan sumber tidak memecah perlindungan. Normalisasi casing, separator, tenant scope, serta versi event sebelum membuat key. Jika kontrak berganti, dukung periode transisi atau version prefix sehingga key lama tetap dapat dikenali. Jangan menggabungkan organisasi berbeda ke namespace yang sama. Pantau collision dan cabang duplicate untuk memastikan kunci tidak terlalu luas maupun terlalu sempit.
Tetapkan owner state dan prosedur ketika storage deduplikasi tidak tersedia. Fail-open meningkatkan risiko duplikat, sedangkan fail-closed menghentikan trafik; pilihan harus mengikuti dampak bisnis dan terlihat pada status workflow.
Batasan dan failure policy
State dapat kedaluwarsa dan efek eksternal berada di boundary lain. Pastikan API tujuan idempoten, gunakan unique constraint, serta definisikan replay dan rekonsiliasi.
Uji request paralel, retry setelah timeout, replay setelah TTL, dan kegagalan downstream. Pelajari risiko at-least-once delivery serta batas retry untuk menempatkan kedua kontrol ini secara tepat.