Race Condition

Race Condition #

Dalam sistem software modern—terutama yang concurrent, parallel, dan distributed—ada satu bug klasik yang sering muncul diam-diam, sulit direproduksi, dan dampaknya bisa fatal: race condition.

Bug ini sering lolos dari unit test, muncul hanya di production, dan biasanya baru ketahuan setelah data rusak, saldo jadi minus, atau user mendapatkan hak yang seharusnya tidak mereka miliki.

Artikel ini akan membahas:

  1. Apa itu race condition
  2. Prinsip terjadinya race condition
  3. Di level mana saja race condition bisa terjadi
  4. Contoh konkret di HTTP, application layer, dan database
  5. Pola umum pencegahan race condition

Apa Itu Race Condition? #

Race condition adalah kondisi ketika hasil akhir sebuah proses bergantung pada urutan eksekusi beberapa operasi yang berjalan bersamaan (concurrent), dan urutan tersebut tidak terkontrol.

Singkatnya:

Dua atau lebih proses “berlomba” mengakses atau memodifikasi resource yang sama.

Jika urutannya berbeda → hasilnya berbeda.

Definisi sederhana #

Race condition terjadi ketika:

  • Ada shared resource
  • Ada lebih dari satu eksekutor (thread, goroutine, request, process)
  • Tidak ada mekanisme sinkronisasi yang benar

Prinsip Dasar #

Race condition hampir selalu melibatkan tiga prinsip utama:

Shared Mutable State #

Ada data bersama yang bisa berubah, misalnya:

  • Variable di memory
  • Row di database
  • File
  • Cache (Redis, Memcached)
  • Counter, saldo, stok

Jika data read + write oleh banyak proses → potensi race.


Concurrency atau Parallelism #

Race condition tidak mungkin terjadi di sistem single-thread yang benar-benar sequential.

Ia muncul ketika:

  • Multi-thread
  • Multi-goroutine
  • Multi-process
  • Multiple HTTP request bersamaan
  • Distributed system (multi instance)

Non-Atomic Operation #

Operasi yang terlihat “satu langkah” di level bisnis, sebenarnya terdiri dari beberapa langkah teknis.

Contoh klasik:

1. Read balance
2. Check balance >= amount
3. Deduct balance
4. Save balance

Jika langkah-langkah ini tidak atomic, race condition hampir pasti muncul.


Bisa Terjadi di Level Mana Saja? #

Race condition bukan hanya soal thread atau mutex. Ia bisa muncul di hampir semua layer.

Mari kita bahas satu per satu.


Level HTTP / API #

Contoh Kasus: Double Submit Payment #

Misal endpoint:

POST /pay

Flow:

  1. Client kirim request pembayaran
  2. Server cek status invoice = UNPAID
  3. Server proses payment
  4. Server update status invoice = PAID

Masalah #

User:

  • Klik tombol “Pay” dua kali
  • Atau network retry
  • Atau client bug

Dua request masuk hampir bersamaan.

Timeline Race Condition #

Request A: check invoice = UNPAID
Request B: check invoice = UNPAID

Request A: process payment
Request B: process payment ❌

Invoice dibayar dua kali

Ini race condition di level HTTP, meskipun backend kamu single-thread.


Penyebab Umum di HTTP Layer #

  • Tidak ada idempotency key
  • Tidak ada locking di resource level
  • Stateless service tanpa proteksi concurrency
  • Retry dari client atau gateway

Application / Memory Level #

Contoh di Multithreading / Goroutine #

Misal di Go:

var counter int

func increment() {
    counter++
}

Dipanggil oleh 100 goroutine.

Kenapa bermasalah? #

counter++ bukan operasi atomic. Ia terdiri dari:

  1. Load value
  2. Increment
  3. Store value

Dua goroutine bisa membaca nilai yang sama → increment → overwrite.

Hasil akhirnya kurang dari 100.


Contoh Kasus Real #

  • In-memory cache
  • Rate limiter
  • Counter metrics
  • Session management
  • Feature flag toggle

Database Level #

Ini yang paling sering diremehkan.

Contoh Kasus: Stok Barang #

Tabel:

products(id, stock)

Flow:

SELECT stock FROM products WHERE id = 1;
-- stock = 1

-- logic aplikasi
if stock > 0:
    UPDATE products SET stock = stock - 1 WHERE id = 1;

Dua transaksi berjalan bersamaan #

Tx A: SELECT stock = 1
Tx B: SELECT stock = 1

Tx A: UPDATE stock = 0
Tx B: UPDATE stock = 0 ❌

Barang terjual dua kali, padahal stok cuma satu.


Ini Tetap RC Walau Pakai Database #

Banyak engineer berpikir:

“Kan sudah pakai database, harusnya aman”

❌ Salah.

Tanpa:

  • Transaction isolation yang tepat
  • Locking (SELECT FOR UPDATE)
  • Atomic update
  • Constraint

Database tidak otomatis mencegah race condition.


Contoh Lebih Halus: Lost Update #

UPDATE users SET login_count = login_count + 1 WHERE id = 1;

Jika dua transaksi overlap tanpa isolation → satu update bisa “hilang”.


Race Condition di Distributed System #

Ini level paling kompleks.

Contoh: #

  • Dua instance service memproses job yang sama
  • Consumer message queue ganda
  • Scheduler jalan di beberapa node
  • Leader election gagal

Tanpa:

  • Distributed lock (Redis, etcd, Zookeeper)
  • Idempotency
  • Exactly-once / at-least-once handling

Race condition bisa terjadi lintas mesin.


Pola Umum Pencegahan #

Race condition tidak bisa dihilangkan sepenuhnya, tapi bisa dikontrol.

Atomicity #

Pastikan operasi kritikal:

  • Atomic
  • Atau diperlakukan seolah atomic

Contoh:

  • UPDATE ... SET stock = stock - 1 WHERE stock > 0
  • CAS (Compare-And-Swap)

Locking #

  • Mutex / RWMutex (memory)
  • Database lock
  • Distributed lock

⚠️ Lock bukan solusi gratis — ada cost performa & deadlock risk.


Idempotency #

Khusus HTTP & distributed system:

  • Idempotency key
  • Request deduplication
  • Exactly-once semantic di level bisnis

Transaction & Isolation Level #

  • Gunakan transaction dengan benar
  • Pahami READ COMMITTED, REPEATABLE READ, SERIALIZABLE
  • Jangan asal percaya default DB

Design for Concurrency #

  • Hindari shared mutable state
  • Gunakan immutable data
  • Event-driven & append-only log

Kenapa Sangat Berbahaya? #

Karena:

  • Sulit direproduksi
  • Jarang muncul di dev
  • Biasanya muncul di load tinggi
  • Efeknya data corruption, bukan sekadar error

Bug ini sering dianggap:

“Random bug” Padahal sebenarnya bug deterministik yang tergantung timing.


Penutup #

Race condition bukan masalah “bahasa pemrograman” atau “framework”. Ia adalah masalah desain sistem.

Engineer yang matang bukan yang:

  • Tidak pernah kena race condition

Tapi yang:

  • Mengantisipasi race condition sejak desain
  • Paham di layer mana race bisa terjadi
  • Tidak terlalu percaya asumsi “ini aman”
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact