Dua client dapat membaca resource yang sama, mengedit field berbeda, lalu mengirim update dengan selang beberapa detik. Jika server menerima kedua write tanpa memeriksa representasi yang menjadi dasar edit masing-masing client, request yang datang belakangan dapat diam-diam menggantikan state dari write sebelumnya. Transport berhasil, tetapi aplikasi kehilangan perubahan konkuren.
HTTP menyediakan mekanisme conditional request untuk batas ini. Server dapat menyertakan entity tag pada representasi, lalu client mengirim kembali tag tersebut melalui If-Match saat mengajukan request yang mengubah state. Update hanya berjalan selama representasi yang dipilih masih memenuhi precondition yang diberikan.
Validator merepresentasikan state yang dipilih server
ETag adalah validator opaque yang dipilih origin server. Response dapat menyertakannya bersama representasi:
HTTP/1.1 200 OK
ETag: "rev-42"
Content-Type: application/json
{"name":"Ada","role":"editor"}Client tidak perlu menghasilkan "rev-42" dari JSON. Client menyimpan validator bersama representasi yang diterimanya. Server dapat membentuk tag dari nomor revisi, content digest, atau skema lain, tetapi cara pembentukannya merupakan pilihan implementasi.
Untuk concurrency control, properti yang penting adalah semantik validator, bukan bentuk visualnya. Strong entity tag menunjukkan representasi yang byte-for-byte ekuivalen menurut strong comparison HTTP. Weak tag, yang ditulis dengan prefiks W/, tidak memenuhi If-Match karena header tersebut menggunakan strong comparison function.
Perbedaan ini mencegah konsep kemiripan semantik yang cocok untuk cache diperlakukan sebagai precondition update yang eksak.
If-Match membawa pemeriksaan ke batas write
Client yang mengedit representasi di atas dapat mengirim:
PUT /accounts/7 HTTP/1.1
If-Match: "rev-42"
Content-Type: application/json
{"name":"Ada","role":"admin"}Origin server mengevaluasi If-Match sebelum menjalankan method yang diminta. Jika representasi yang dipilih saat ini masih cocok dengan "rev-42", request dapat diproses. Jika write lain sudah diterima dan mengubah representasi beserta validatornya, precondition gagal.
Untuk kegagalan precondition If-Match, HTTP menetapkan status 412 Precondition Failed. Client memperoleh hasil concurrency yang eksplisit, bukan response sukses yang menyembunyikan perubahan yang tertimpa.
Batas implementasi yang kritis berada di antara validasi dan mutasi. Aplikasi yang membaca revisi saat ini, melepas proteksi database, lalu melakukan write tanpa operasi database bersyarat dapat menciptakan kembali race yang sama di dalam server. Precondition HTTP perlu dipetakan ke operasi storage yang mempertahankan kondisi tersebut sampai write berlangsung.
Pada relational store, bentuknya dapat berupa:
UPDATE accounts
SET name = ?, role = ?, version = version + 1
WHERE id = ? AND version = ?;Update dengan nol row menunjukkan bahwa expected version tidak lagi mengidentifikasi state row saat ini. Detail transaction dan isolation bergantung pada database serta operasi di sekitarnya, tetapi comparison tidak boleh terpisah dari mutasi yang hendak dijaganya.
Version token bukan lock
ETag tidak memesan resource untuk satu client. Dua client dapat memegang "rev-42" pada saat yang sama. Keduanya dapat menyiapkan write, dan tidak ada yang memperoleh ownership hanya karena memiliki validator.
Efek concurrency muncul ketika server menserialisasi atau menerapkan mutasi secara bersyarat. Satu request dapat memindahkan resource ke state baru seperti "rev-43". Request berikutnya yang masih membawa "rev-42" kemudian gagal memenuhi precondition.
Ini merupakan optimistic concurrency: contention dideteksi pada saat mutasi, bukan dicegah dengan mempertahankan lock yang terlihat client selama interval edit. Pola ini sesuai untuk client yang dapat terputus dan API request-response biasa karena tidak ada lock lease yang harus bertahan selama waktu edit pengguna, gangguan jaringan, atau restart proses.
Konsekuensinya, rejection menjadi bagian normal dari kontrak. API dengan pola ini memerlukan response yang jelas untuk stale write dan kebijakan client untuk mengambil state terbaru, menampilkan conflict, menggabungkan field, atau membatalkan perubahan.
PUT dan PATCH memiliki batas merge berbeda
Conditional request tidak menentukan semantik update. PUT dan PATCH sama-sama dapat membawa If-Match, tetapi arti request body keduanya tetap berbeda.
Representasi PUT umumnya menggambarkan replacement state untuk target resource. Jika client membentuk representasi tersebut dari data usang, penerimaan tanpa kondisi dapat menimpa field yang sebenarnya tidak hendak diubah.
Request PATCH membawa patch document dengan semantik yang bergantung pada media type. Patch dapat mengurangi penggantian field lain secara tidak sengaja, tetapi tidak menghilangkan write conflict. Dua patch tetap dapat menyasar field yang sama, atau satu patch dapat bergantung pada state yang telah diubah request lain.
If-Match menempatkan kedua bentuk di belakang batas versi representasi yang sama. Apakah server harus menolak setiap perubahan yang terjadi di antaranya atau mengizinkan concurrency per-field yang lebih halus merupakan kebijakan aplikasi di atas HTTP. Satu ETag untuk resource secara sengaja memperlakukan representasi terpilih sebagai satu unit concurrency.
If-Match star menyatakan keberadaan, bukan revisi tertentu
If-Match juga menerima *. Untuk request yang mengubah state, bentuk ini berhasil saat origin server memiliki representasi saat ini untuk target resource. Bentuk tersebut tidak menyatakan bahwa resource berada pada versi tertentu.
Kedua bentuk berikut karena itu memiliki arti berbeda:
If-Match: "rev-42"mensyaratkan strong match dengan entity tag yang diberikan, sedangkan:
If-Match: *menyatakan kondisi yang terkait dengan keberadaan representasi saat ini.
API yang memerlukan deteksi stale write karena itu biasanya mengembalikan dan menerima strong validator tertentu. Mengganti token tersebut dengan * menghapus identitas revisi yang membawa pemeriksaan concurrency.
Scope validator harus sama dengan scope mutasi
Validator hanya berguna untuk state yang benar-benar direpresentasikannya. Jika API menghasilkan ETag dari sebagian field tetapi endpoint yang sama dapat mengubah state tambahan, comparison yang berhasil mungkin tidak mendeteksi perubahan di luar subset tersebut.
Ketidakcocokan pada arah sebaliknya juga dapat menimbulkan biaya. Validator yang mencakup metadata volatil dan tidak terkait dapat menolak edit yang sebenarnya tidak conflict pada level aplikasi. Penolakan tersebut tetap aman terhadap lost write, tetapi dapat menciptakan contention yang tidak diperlukan.
Pertanyaan desainnya adalah unit concurrency: state server mana yang harus tetap sama antara read dan write agar mutasi yang diajukan tetap valid? Kebijakan pembentukan ETag, bentuk representasi, dan storage predicate sebaiknya menggambarkan batas yang sama.
Conditional write membuat conflict terlihat
Tanpa precondition, dua response HTTP yang sukses dapat menyembunyikan lost update. Dengan strong validator dan mutasi bersyarat pada level storage, stale writer kedua menerima kegagalan yang terlihat sebelum state miliknya menggantikan revisi yang telah diterima.
Mekanisme ini tidak menggabungkan edit konkuren, memilih pemenang conflict, atau menetapkan kebijakan bisnis. Mekanisme tersebut mengubah race implisit menjadi hasil interface yang eksplisit. Kontraknya sengaja sempit: mutasi hanya diterapkan pada revisi representasi yang disebut client, dan revisi yang sudah berubah memaksa caller masuk ke jalur conflict terpisah.