Database Engineering · Concurrency

Apa Itu Race Condition? Solusi Database Locking

Bayangkan sebuah produk tinggal 1 stok terakhir, lalu dua pelanggan menekan tombol “Beli” pada detik yang nyaris bersamaan. Tanpa penanganan yang tepat, keduanya bisa sama-sama berhasil checkout, dan stok yang seharusnya 0 malah tercatat minus satu. Inilah contoh klasik race condition, salah satu bug paling sulit dideteksi karena hanya muncul dalam kondisi timing tertentu.

Bagi masyarakat umum yang pernah mengalami kejadian aneh seperti saldo dompet digital yang sempat tidak masuk akal atau tiket konser yang terjual melebihi kapasitas kursi, race condition sering menjadi biang keladi di baliknya. Bagi pembaca profesional seperti backend developer atau database engineer, memahami race condition dan cara mengatasinya lewat mekanisme locking adalah keterampilan fundamental, karena bug jenis ini sulit direproduksi di lingkungan development namun bisa menimbulkan kerugian nyata saat production menghadapi trafik tinggi.

2+

proses berjalan bersamaan sebagai pemicu utama

Timing

faktor penentu, bukan logika kode yang salah

2

strategi utama: pessimistic vs optimistic locking

0

toleransi untuk data korup akibat race condition

Apa Itu Race Condition?

Race condition adalah kondisi ketika hasil akhir sebuah program bergantung pada urutan atau timing eksekusi dari dua proses atau lebih yang berjalan bersamaan, padahal seharusnya hasilnya konsisten tidak peduli urutan mana yang dijalankan lebih dulu. Istilah “race” (balapan) menggambarkan situasi di mana proses-proses tersebut seolah “berlomba” mengakses dan mengubah data yang sama, dan siapa yang “menang” menentukan hasil akhir yang kadang benar, kadang salah, tergantung keberuntungan timing semata.

Pada konteks database, race condition paling sering muncul pada pola “read-modify-write”, yaitu ketika aplikasi membaca sebuah nilai, mengubahnya di level aplikasi, lalu menulis kembali hasilnya ke database sebagai operasi terpisah. Celah waktu antara proses baca dan tulis inilah yang menjadi titik rawan disusupi proses lain.

Contoh Konkret Race Condition pada Database

1. Aplikasi A: SELECT stok FROM produk WHERE id=1; -> hasil: 1 2. Aplikasi B: SELECT stok FROM produk WHERE id=1; -> hasil: 1 (sebelum A sempat update) 3. Aplikasi A: UPDATE produk SET stok = 1 – 1 WHERE id=1; -> stok jadi 0 4. Aplikasi B: UPDATE produk SET stok = 1 – 1 WHERE id=1; -> stok jadi 0 (bukan -1 di database, tapi checkout B tetap dianggap berhasil di aplikasi)

Perhatikan bahwa kedua proses membaca nilai stok yang sama sebelum salah satu sempat memperbaruinya, sehingga keduanya menganggap stok masih tersedia dan sama-sama memproses transaksi sebagai sukses, meski secara fisik barang hanya tersedia satu unit.

Dampak Race Condition yang Sering Terjadi

  • Overselling stok — produk terjual melebihi jumlah yang tersedia, seperti contoh di atas.
  • Double booking — dua pelanggan berhasil memesan slot atau kursi yang sama pada waktu bersamaan.
  • Saldo tidak akurat — dua transaksi penarikan dana diproses bersamaan tanpa saling mengetahui, berpotensi membuat saldo akhir salah hitung atau bahkan minus.
  • Duplikasi data — dua permintaan pembuatan akun dengan email sama diproses bersamaan sebelum validasi unik sempat berjalan, menghasilkan dua akun dengan data identik.

Solusi: Mengatasi Race Condition dengan Database Locking

1. Pessimistic Locking (SELECT … FOR UPDATE)

Pendekatan ini mengunci baris data begitu dibaca untuk keperluan update, sehingga proses lain yang ingin mengakses baris yang sama harus menunggu sampai lock dilepas. Ini adalah pendekatan paling “aman” karena mencegah race condition secara langsung di level database, namun berpotensi menyebabkan antrean atau bahkan freeze jika transaksi yang memegang lock berjalan terlalu lama.

BEGIN; SELECT stok FROM produk WHERE id=1 FOR UPDATE; — baris terkunci, proses lain menunggu UPDATE produk SET stok = stok – 1 WHERE id=1; COMMIT;

2. Optimistic Locking (Versioning)

Pendekatan ini tidak mengunci data sejak awal, melainkan menambahkan kolom version atau updated_at pada tabel. Saat proses ingin melakukan update, ia menyertakan syarat bahwa version harus masih sama dengan saat data dibaca. Jika ada proses lain yang sudah mengubah data lebih dulu, update akan gagal (0 baris terpengaruh) dan aplikasi bisa mencoba ulang (retry) dengan data terbaru.

UPDATE produk SET stok = stok – 1, version = version + 1 WHERE id=1 AND version = 5; — jika 0 rows affected, berarti version sudah berubah, lakukan retry

3. Atomic Operations di Level Database

Cara paling efisien untuk kasus sederhana adalah menghindari pola read-modify-write sama sekali, dengan melakukan perhitungan langsung dalam satu statement SQL atomik yang dijalankan sepenuhnya oleh database, bukan oleh aplikasi.

UPDATE produk SET stok = stok – 1 WHERE id=1 AND stok >= 1; — jika 0 rows affected, berarti stok memang sudah habis

4. Database Constraint sebagai Garis Pertahanan Terakhir

Menambahkan constraint seperti UNIQUE atau CHECK (misalnya CHECK (stok >= 0)) pada level skema database memastikan bahwa meski logika aplikasi lolos dari celah race condition, database akan tetap menolak perubahan yang menghasilkan data tidak valid, sebagai jaring pengaman terakhir.

Pessimistic vs Optimistic Locking: Kapan Pakai yang Mana

AspekPessimistic LockingOptimistic Locking
Cara kerjaKunci data sejak dibacaCek konflik saat menulis, bukan saat membaca
Cocok untukKonflik data sering terjadi (high contention)Konflik data jarang terjadi (low contention)
Dampak performaBisa memicu antrean/freeze saat trafik tinggiLebih ringan, namun butuh logika retry di aplikasi
Contoh kasus idealSistem pemesanan tiket dengan stok sangat terbatasUpdate profil pengguna yang jarang diedit bersamaan
Koneksi penting: pessimistic locking adalah mekanisme yang sama yang bisa menyebabkan gejala “database freeze tanpa error” jika transaksi yang memegang lock tidak kunjung selesai. Artinya, locking adalah solusi sekaligus sumber potensi masalah baru jika tidak dikelola dengan statement_timeout atau lock_timeout yang tepat.

Race Condition dan Prinsip ACID

Race condition pada dasarnya adalah pelanggaran terhadap prinsip isolation dalam ACID, yang seharusnya menjamin transaksi yang berjalan bersamaan tidak saling mempengaruhi hasil satu sama lain secara tidak terduga. Locking, baik pessimistic maupun optimistic, adalah alat teknis yang dipakai database dan aplikasi untuk menegakkan isolation tersebut secara nyata, memastikan hasil akhir tetap konsisten berapa pun banyaknya proses yang mencoba mengakses data yang sama secara bersamaan.

Baca Dokumentasi SELECT FOR UPDATE di PostgreSQL

FAQ Seputar Race Condition dan Database Locking

1. Kenapa race condition sulit dideteksi saat testing?

Karena bug ini hanya muncul dalam kondisi timing tertentu ketika dua proses benar-benar berjalan bersamaan, sehingga sering tidak terlihat saat testing dengan trafik rendah namun muncul saat production menghadapi banyak pengguna sekaligus.

2. Apakah pessimistic locking selalu lebih aman daripada optimistic locking?

Lebih aman dalam mencegah konflik langsung, namun berisiko menurunkan performa dan bahkan menyebabkan freeze jika transaksi yang memegang lock berjalan terlalu lama, sehingga pemilihannya tetap perlu disesuaikan dengan pola trafik aplikasi.

3. Apa yang harus dilakukan aplikasi saat optimistic locking gagal karena konflik version?

Aplikasi umumnya perlu membaca ulang data terbaru, menerapkan kembali perubahan yang diinginkan pengguna, lalu mencoba menyimpan ulang (retry), sehingga penting merancang logika retry yang aman dan tidak berulang tanpa batas.

4. Apakah race condition hanya terjadi pada database?

Tidak, race condition juga bisa terjadi pada sistem file, memori bersama antar thread, atau layanan terdistribusi mana pun yang memungkinkan beberapa proses mengakses resource yang sama secara bersamaan, bukan hanya pada database.

5. Apakah menambah database constraint saja sudah cukup tanpa locking?

Constraint efektif sebagai jaring pengaman terakhir mencegah data tidak valid tersimpan, namun tidak menggantikan kebutuhan locking atau atomic operation untuk memastikan logika bisnis berjalan benar sejak awal, terutama pada skenario yang butuh perhitungan bertahap.

Kesimpulan

Race condition adalah pengingat bahwa dunia aplikasi modern jarang berjalan satu proses dalam satu waktu, dan kegagalan mengantisipasi hal ini bisa berujung pada data yang tidak konsisten meski logika kode terlihat benar di atas kertas. Memahami perbedaan pessimistic locking yang mengunci data sejak awal dan optimistic locking yang mengandalkan verifikasi versi saat menulis membantu developer memilih strategi yang tepat sesuai karakteristik trafik aplikasinya. Kombinasi locking yang bijak, atomic operation, dan constraint di level database adalah fondasi yang membuat sistem tetap konsisten meski diakses ribuan pengguna secara bersamaan, sekaligus menjadi pelengkap alami dari pemahaman tentang ACID dan penyebab database freeze yang sudah dibahas sebelumnya.

Bagikan:

Kia Krikil

Editor di Kiakrikil.com yang fokus menyajikan berita terbaru seputar pendidikan dan teknologi.

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *