Nyatakan invarians bisnis
Bedain atomicity transaksi, durabilitas, dan kebenaran aplikasi. Transaksi nggak otomatis bikin bisnis kamu bener.
Pertanyaan
Aturan apa yang nggak boleh pernah rusak, dan jaminan mana yang beneran nahan aturan itu?
Setelah baca ini, kamu bisa nulis invarians bisnis sebuah fitur sebagai kalimat yang bisa dicek, terus misahin bagian yang dijaga database (atomicity, durabilitas, constraint) dari bagian yang tetap jadi tanggung jawab kode aplikasi.
Prasyarat dan contoh
Kamu sudah paham model data dan indeks dari M03 dan M04, dan tahu apa itu tabel, baris, dan constraint.
Toko tiket punya konser dengan 100 kursi. Aturannya: jumlah tiket terjual tidak pernah melebihi 100. Itu invarians: kondisi yang harus benar sebelum dan sesudah setiap operasi, di semua urutan eksekusi yang mungkin.
Kode pembelian yang kelihatan wajar:
BEGIN;
SELECT sisa FROM stok WHERE konser = 7; -- sisa = 1
-- aplikasi: if sisa > 0
UPDATE stok SET sisa = sisa - 1 WHERE konser = 7;
INSERT INTO tiket (konser, pembeli) VALUES (7, :pembeli);
COMMIT;
Transaksi ini atomik dan tahan crash. Tapi dua pembeli yang jalan bersamaan sama-sama baca
sisa = 1, sama-sama lolos if, dan dua tiket terbit buat satu kursi. Setiap transaksi benar
sendirian. Invarians-nya tetap rusak.
Mekanisme
Atomicity. Semua perubahan dalam satu transaksi terjadi, atau nggak ada yang terjadi. Kalau
INSERT tiket gagal, UPDATE stok ikut dibatalkan. Ini soal kegagalan di tengah jalan, bukan
soal pembeli lain.
Durabilitas. Setelah COMMIT dikonfirmasi, hasilnya bertahan walau proses atau mesin mati.
Biasanya lewat write-ahead log yang di-flush ke disk. Jaminan ini punya syarat: konfigurasi
(misalnya synchronous_commit di PostgreSQL) dan perangkat disk yang jujur soal flush. Detailnya
ada di M09.
Isolation. Seberapa jauh transaksi yang jalan bersamaan saling kelihatan. Ini yang nentuin apakah dua pembeli di atas bisa saling nggak tahu. Isolation punya tingkat, dan kebanyakan engine default-nya bukan yang paling ketat (lihat M05.02).
Constraint basis data. Tempat paling murah buat nahan invarians. Contoh:
ALTER TABLE stok ADD CONSTRAINT sisa_nonnegatif CHECK (sisa >= 0);
Dengan constraint ini, UPDATE kedua yang bikin sisa = -1 ditolak database, siapa pun yang
nulis dan seramai apa pun trafiknya. Constraint cuma bisa mengekspresikan aturan yang bisa dicek
dari baris itu sendiri atau lewat UNIQUE, FOREIGN KEY, dan sejenisnya.
Kebenaran aplikasi. Aturan seperti "satu pengguna maksimal 4 tiket per konser" atau "saldo semua akun harus nol saat dijumlah" nggak selalu bisa jadi constraint satu baris. Aturan itu perlu dijaga lewat kunci, pembaruan atomik, atau desain data yang bikin pelanggarannya mustahil (M05.03).
Daftar yang berguna. Buat tiap fitur tulis tiga kolom: invarians, penjaganya, dan apa yang terjadi kalau penjaganya gagal. Contoh singkat:
| Invarians | Penjaga | Kalau gagal |
| --- | --- | --- |
| sisa >= 0 | CHECK di tabel | Transaksi ditolak, pembeli dapat "habis" |
| Satu tiket per kursi | UNIQUE (konser, kursi) | Insert kedua ditolak |
| Maks 4 tiket per pengguna | Kode + kunci per pengguna | Pengguna bisa beli 5 |
Saat rancangan ini gagal
- Menyamakan "pakai transaksi" dengan "aman". Transaksi memberi atomicity, bukan jaminan bahwa keputusan yang diambil dari data yang dibaca masih valid saat commit.
- Cek di aplikasi, tulis di database. Pola baca,
if, tulis, punya jendela waktu di tengah. Pada 200 permintaan/detik untuk kursi terakhir, jendela beberapa milidetik sudah cukup buat oversell. - Invarians hanya di komentar atau dokumentasi. Kode baru, skrip admin, atau import data nggak membaca komentar. Constraint tetap berlaku untuk semuanya.
- Mengira durabilitas berarti kebenaran. Data yang salah pun disimpan dengan tahan crash. Durabilitas melestarikan hasilnya, benar atau salah.
- Invarians lintas sistem. Kalau stok di database dan tiket di layanan lain, satu transaksi nggak mencakup keduanya (M05.06).
Latihan
Sebuah dompet digital punya aturan: saldo pengguna tidak boleh negatif, dan transfer
memindahkan uang dari A ke B tanpa menambah atau mengurangi total. Kode transfer: baca saldo A,
cek saldo >= jumlah, kurangi A, tambah B, semuanya dalam satu transaksi dengan isolation
default.
Tulis invarians sebagai kalimat, tentukan penjaga untuk masing-masing, dan sebut satu celah yang masih ada.
Lihat pembahasan
Invarians:
- Untuk setiap akun,
saldo >= 0. - Untuk setiap transfer, jumlah yang keluar dari A sama dengan yang masuk ke B, jadi total saldo tidak berubah.
Penjaga:
- Aturan 1:
CHECK (saldo >= 0)di tabel akun. Ini menolak pembaruan yang bikin saldo negatif walau dua transfer dari akun yang sama jalan bersamaan. - Aturan 2: atomicity. Pengurangan A dan penambahan B ada di satu transaksi, jadi nggak ada keadaan di mana uang hilang di tengah. Jangan menaruhnya di dua transaksi atau dua layanan.
Celah: kode yang membaca saldo lalu mengecek di aplikasi masih bisa salah jika dua transfer
membaca saldo yang sama. Dengan CHECK, salah satunya ditolak (bagus, tapi aplikasi harus
menangani error itu sebagai "saldo kurang", bukan 500). Tanpa CHECK, dua transfer Rp80.000
dari saldo Rp100.000 bisa lolos dan saldo jadi -Rp60.000.
Jawaban yang bagus memisahkan aturan yang bisa jadi constraint dari aturan yang butuh kunci atau pembaruan atomik, dan nggak mengandalkan "sudah pakai transaksi".
Terkait
- M05.02 Amati anomali isolasi menjelaskan kenapa dua transaksi yang benar bisa melanggar invarians bersama.
- M05.03 Koordinasikan perubahan konkuren membahas alat untuk menjaga aturan yang nggak bisa jadi constraint.
- M03 (model data) dan M04 (indeks, termasuk indeks unik) menyediakan constraint yang dipakai di sini.
- M09 membahas durabilitas dan WAL lebih dalam.
Catatan
- Asumsi: satu database relasional dengan transaksi ACID dan constraint standar.
- Yang nggak dibahas: detail implementasi WAL, dan transaksi terdistribusi seperti two-phase commit.
- Istilah ACID di sini dipakai praktis. Makna "C" (consistency) di ACID sebenarnya adalah invarians yang kamu definisikan sendiri, bukan sesuatu yang diberikan database.
- Terakhir ditinjau: 26 Oktober 2026.