Client dapat kehilangan HTTP response setelah server melakukan commit terhadap mutasi yang diminta. Dari sisi client, status operasi belum pasti: koneksi gagal, tetapi kegagalan itu tidak menunjukkan apakah durable state sudah berubah. Retry terhadap POST yang sama kemudian dapat membuat order, percobaan pembayaran, reservasi, atau mutasi lain untuk kedua kalinya.

Idempotency key memberi retry sebuah identitas stabil yang terpisah dari satu percobaan transport tertentu. Server dapat mengaitkan request berulang yang membawa identitas tersebut dengan satu operasi logis. Mekanisme ini mempersempit ambiguitas pada batas API, tetapi key saja bukan jaminan. Scope, persistensi, perbandingan request, kontrol konkurensi, dan kebijakan replay menentukan arti sebenarnya dari pengiriman berulang.

Percobaan transport dan operasi logis memiliki identitas berbeda

TCP connection, instance HTTP request, trace span, dan mutasi aplikasi dapat memiliki lifetime yang berbeda. Sebuah retry membuat percobaan transport baru meskipun caller bermaksud menjalankan operasi aplikasi yang sama.

Idempotency key mewakili identitas pada level aplikasi tersebut. Client membuat key sebelum percobaan pertama dan menggunakannya kembali hanya untuk retry dari mutasi yang sama. Mutasi baru memperoleh key baru.

Pemisahan ini penting karena metadata transport tidak cukup stabil untuk mengidentifikasi intent. Source port dapat berubah. Connection dapat diganti. Proxy dapat melakukan retry terhadap request. Trace identifier dapat menggambarkan observasi, bukan identitas bisnis. Tidak satu pun dari nilai tersebut secara otomatis menetapkan apakah dua pengiriman merupakan satu mutasi atau dua mutasi.

Karena itu server memerlukan mapping eksplisit seperti:

(scope, idempotency_key) -> operation record

scope merupakan bagian dari kontrak. Nilainya dapat mencakup authenticated account, API tenant, endpoint, atau namespace lain. Tanpa scope yang terdefinisi, nilai key yang sama dari caller berbeda dapat bertabrakan.

Request pertama yang diterima menetapkan identitas request

Key yang berulang hanya berguna jika server dapat mendeteksi pemakaian ulang secara tidak sengaja dengan semantik request yang berbeda. Misalnya, client mengirim key K dengan satu nilai amount, lalu mengirim key yang sama dengan amount berbeda. Mengembalikan hasil mutasi pertama tanpa memeriksa request dapat menyembunyikan defect pada caller.

Salah satu desain yang umum menyimpan canonical request fingerprint bersama key. Fingerprint dapat mencakup field yang mendefinisikan mutasi sambil mengecualikan nilai yang hanya berkaitan dengan transport. Saat key berulang, server membandingkan request baru dengan identitas yang tersimpan.

Aturan perbandingan harus eksplisit. Hash terhadap raw JSON bytes, misalnya, menganggap perbedaan serialisasi yang tidak signifikan sebagai request berbeda kecuali API memang mensyaratkan identitas byte-for-byte. Semantic fingerprint dapat menormalisasi field tertentu, tetapi normalisasi tersebut kemudian menjadi bagian dari aturan ekuivalensi API.

Record yang disimpan secara konseptual dapat berisi:

key
scope
request_fingerprint
state
result_reference
created_at
expires_at

Perbedaan antara request berulang dan fingerprint yang tersimpan tidak boleh diam-diam menjadi mutasi kedua dengan key yang sama. API memerlukan conflict response yang terdefinisi untuk kasus tersebut.

Duplikat konkuren memerlukan ownership atomik

Dua request dengan key baru yang sama dapat tiba hampir bersamaan. Pola check lalu insert tidak memadai jika kedua request dapat melihat key belum ada sebelum salah satunya mencatat ownership.

Transisi dari “key belum ada” menjadi “operation claimed” memerlukan batas atomik. Database uniqueness constraint pada (scope, key) merupakan salah satu primitive yang umum. Satu request membuat operation record; request pesaing menemukan record yang sudah ada dan mengikuti jalur duplikat.

Atomic claim tersebut tidak otomatis membuat business mutation menjadi atomik. Operation record dan perubahan durable state tetap memerlukan strategi konsistensi. Jika keduanya berada pada transactional database yang sama, keduanya sering dapat masuk ke satu transaction atau state machine dengan transisi yang dilindungi transaction. Jika mutasi melintasi sistem eksternal, API tidak dapat menciptakan satu local transaction untuk resource yang tidak berbagi transaction yang sama.

Karena itu mekanisme key lebih tepat diposisikan sebagai koordinasi duplikat, bukan jaminan universal exactly-once execution. External effect dapat memerlukan idempotency identity sendiri, kontrak deduplication, atau proses rekonsiliasi.

Record in-progress memiliki semantik yang terlihat

Duplikat dapat tiba ketika request pertama masih dieksekusi. Pada titik itu, completed response mungkin belum tersedia untuk replay.

Operation record memerlukan state yang membedakan active claim dari completed result. Request berulang kemudian dapat menerima response yang terdefinisi, menunggu completion dengan kebijakan berbatas, atau melakukan polling terhadap resource lain. Pilihannya bergantung pada kontrak API.

Menjalankan mutasi kembali akan merusak batas koordinasi. Menganggap setiap in-progress record sebagai sukses permanen juga tidak tepat karena percobaan pertama masih dapat gagal.

Crash recovery menambahkan transisi state lain. Process dapat mengklaim key lalu berhenti sebelum mencatat completion. Tanpa aturan recovery, key dapat tersangkut. Jika worker lain boleh mengambil alih, aturan takeover harus membedakan resumption yang aman dari pengulangan external side effect dengan hasil yang belum pasti.

Ini adalah ketidakpastian yang sama yang memicu kebutuhan key, tetapi kini berada di dalam batas server. Durable operation state mengurangi ambiguitas hanya sejauh setiap side effect berpartisipasi dalam protokol yang dapat dipulihkan.

Replay response merupakan bagian dari kontrak

Setelah completion, request duplikat biasanya memerlukan hasil operasi logis awal, bukan eksekusi baru. Server dapat menyimpan response asli, menyimpan referensi ke resource yang dibuat, atau merekonstruksi response dari durable state.

Pilihan tersebut tidak ekuivalen. Replay status dan body yang tersimpan mempertahankan hasil API awal. Rekonstruksi pada waktu berikutnya dapat mengekspos state yang sudah berubah setelah operasi awal. Mengembalikan hanya resource reference dapat memadai jika kontrak API menetapkan representasi tersebut, tetapi hasilnya tidak identik dengan replay response pertama.

Header juga memerlukan kebijakan. Sebagian header menggambarkan representasi awal, sedangkan header lain dibuat per percobaan transport. Server tidak boleh menganggap setiap header dari response pertama layak dipakai pada response berikutnya.

Failure result memerlukan presisi serupa. Jika validasi gagal sebelum key mengklaim mutasi, server dapat mengizinkan request yang sudah diperbaiki memakai key yang sama. Jika eksekusi sudah dimulai dan menghasilkan durable terminal failure, pengiriman berulang mungkin perlu melakukan replay terhadap kegagalan tersebut. Batas antara kedua kasus harus terikat pada operation state machine, bukan aturan generik bahwa semua error disimpan.

Retention menetapkan jendela deduplication

Idempotency record memakai storage, sehingga sistem biasanya menyimpannya untuk periode terbatas. Expiration mengubah semantik: setelah record hilang, key yang sama dapat terlihat baru lagi.

API dengan demikian memiliki jendela deduplication, baik dinyatakan secara langsung maupun muncul dari storage policy. Perilaku retry client harus berada di dalam jendela tersebut jika client mengharapkan duplicate suppression.

Expiration juga berinteraksi dengan request yang tertunda. Retry dapat tertahan di queue, proxy, atau client scheduler lebih lama dari perkiraan. Jika request tiba setelah record expired, server dapat menerimanya sebagai operasi baru. Retention karena itu merupakan bagian dari korektness, bukan sekadar housekeeping.

Menghapus record hanya berdasarkan creation time juga dapat berbahaya untuk operasi yang berjalan lama. Record tidak seharusnya expired ketika masih melindungi mutasi aktif, kecuali state machine memiliki aturan recovery terpisah yang tetap menjaga koordinasi duplikat.

Idempotency tidak membuat sembarang retry aman

Key yang stabil menangani pengiriman berulang dari satu mutasi yang memang dimaksudkan sama. Key tidak menentukan apakah caller perlu melakukan retry, berapa lama retry berlanjut, atau apakah operasi masih berguna setelah deadline.

Retry policy tetap memerlukan batas. Caller dapat berhenti setelah deadline meskipun server masih menyelesaikan operasi. Backoff dapat mengurangi tekanan berulang saat terjadi partial failure. Retry budget dapat membatasi amplifikasi di banyak layer service.

Key juga tidak dapat menggabungkan dua operasi yang memang dimaksudkan terpisah. Jika caller secara tidak sengaja memakai kembali key untuk pembelian berikutnya, perilaku yang tepat adalah conflict detection, bukan deduplication ke pembelian sebelumnya.

Pembedaan ini menjaga mekanisme tetap sempit: key mengikat percobaan yang dinyatakan caller sebagai satu mutasi logis. Key tidak menyimpulkan intent dari payload yang mirip.

Durable record merupakan batas sinkronisasi yang sebenarnya

Fitur API yang terlihat adalah request header atau field, tetapi mekanisme substantifnya adalah durable operation record di belakangnya. Record tersebut menetapkan ownership, identitas request, execution state, result semantics, dan retention.

Ketika bagian-bagian itu eksplisit, retry dapat melewati connection failure tanpa otomatis menggandakan mutasi. Ketika bagian tersebut diabaikan, idempotency key dapat berubah menjadi sekadar label pada request berulang.

Batas desain yang berguna bukan sekadar “menerima key”, melainkan “mendefinisikan satu operasi logis yang identitas dan state-nya bertahan melewati percobaan transport berulang”. Kontrak tersebut membuat penanganan duplikat dapat diperiksa pada layer yang sama dengan pemilik mutasi.