8 menit

Bagaimana Jaminan ACID Membentuk Sistem Transaksional yang Andal

Pelajari bagaimana jaminan ACID memengaruhi desain basis data dan perilaku aplikasi. Jelajahi atomisitas, konsistensi, isolasi, durabilitas, trade-off, dan contoh nyata.

Bagaimana Jaminan ACID Membentuk Sistem Transaksional yang Andal

Apa arti “ACID” untuk transaksi sehari-hari

Saat Anda membayar bahan makanan, memesan tiket, atau memindahkan uang antar rekening, Anda mengharapkan hasil yang tegas: berhasil atau gagal. Basis data berusaha memberikan kepastian yang sama—bahkan saat banyak orang menggunakan sistem bersamaan, server jatuh, atau jaringan bermasalah.

Transaksi, dalam istilah sederhana

Sebuah transaksi adalah satu unit kerja yang diperlakukan basis data sebagai satu "paket." Ia mungkin melibatkan beberapa langkah—mengurangi inventaris, membuat catatan order, membebankan kartu, dan menulis tanda terima—tetapi dimaksudkan berperilaku sebagai satu tindakan koheren.

Jika ada langkah yang gagal, sistem sebaiknya memutar kembali ke titik aman daripada meninggalkan keadaan setengah jadi.

Mengapa pembaruan parsial menyebabkan masalah bisnis nyata

Pembaharuan parsial bukan sekadar gangguan teknis; mereka menjadi tiket dukungan pelanggan dan risiko finansial. Misalnya:

  • Pembayaran ditagih, tetapi order tidak dibuat—pelanggan dikenai biaya tanpa konfirmasi.
  • Order dibuat, tetapi inventaris tidak dikurangi—situs Anda oversell lalu membatalkan pesanan.
  • Transfer bank mendebit satu akun tetapi tidak mengkredit akun lain—saldo menjadi tidak masuk akal.

Kegagalan ini sulit di-debug karena semuanya terlihat “hampir benar,” tetapi angkanya tidak cocok.

ACID adalah seperangkat jaminan (bukan produk)

ACID adalah singkatan untuk empat jaminan yang banyak basis data sediakan untuk transaksi:

  • Atomicity: eksekusi semua-atau-tidak-satu-pun
  • Consistency: data tetap dalam aturan valid
  • Isolation: transaksi bersamaan tidak saling mengganggu dengan cara yang tidak aman
  • Durability: setelah dikomit, perubahan bertahan

Bukan merek basis data atau satu fitur yang Anda aktifkan; ini janji tentang perilaku.

Manfaat—dan biaya yang harus diantisipasi

Jaminan yang lebih kuat biasanya berarti basis data harus melakukan lebih banyak kerja: koordinasi ekstra, menunggu kunci, melacak versi, dan menulis ke log. Itu dapat mengurangi throughput atau menambah latensi saat beban tinggi. Tujuannya bukan “ACID maksimal setiap saat,” melainkan memilih jaminan yang sesuai dengan risiko bisnis nyata Anda.

Atomicity: Pembaruan Semua-atau-Tidak-Satu-Pun

Atomicity berarti sebuah transaksi diperlakukan sebagai satu unit kerja: ia selesai sepenuhnya atau tidak berpengaruh sama sekali. Anda tidak pernah mendapatkan “setengah pembaruan” yang terlihat di basis data.

Contoh sederhana transfer bank

Bayangkan mentransfer $50 dari Alice ke Bob. Di balik layar, ini biasanya melibatkan sedikitnya dua perubahan:

  • Mengurangi $50 dari saldo Alice
  • Menambahkan $50 ke saldo Bob

Dengan atomicity, kedua perubahan itu berhasil bersama atau gagal bersama. Jika sistem tidak bisa dengan aman melakukan keduanya, maka tidak boleh dilakukan keduanya. Itu mencegah hasil buruk di mana Alice dikenai biaya tetapi Bob tidak menerima uang (atau Bob menerima tanpa Alice terdebit).

Commit vs. rollback (dalam bahasa sederhana)

Basis data memberi transaksi dua jalan keluar:

  • Commit: “Semua langkah berhasil; jadikan hasil resmi.”
  • Rollback: “Ada yang salah; batalkan semua dari transaksi ini.”

Model mental yang berguna adalah “draft vs. publish.” Ketika transaksi berjalan, perubahan bersifat sementara. Hanya commit yang mem-publish perubahan.

Apa yang bisa salah di tengah transaksi?

Atomicity penting karena kegagalan itu normal:

  • Crash aplikasi: layanan Anda berhenti setelah memperbarui satu tabel tetapi sebelum memperbarui yang lain.
  • Putus jaringan: aplikasi tidak bisa mencapai basis data, atau klien tidak menerima respons "sukses".
  • Hilang daya: server basis data mati tiba-tiba.

Jika ini terjadi sebelum commit selesai, atomicity memastikan basis data bisa rollback sehingga pekerjaan parsial tidak bocor ke saldo nyata.

Atomicity plus idempotensi dan retry

Atomicity melindungi state basis data, tetapi aplikasi Anda tetap harus menangani ketidakpastian—terutama saat putus jaringan membuat tidak jelas apakah commit terjadi.

Dua pelengkap praktis:

  • Retries: ulangi permintaan saat Anda tidak mendapat respons.
  • Idempotency: buat mengulang permintaan aman (mis. menggunakan idempotency key sehingga "transfer #123" diterapkan paling banyak sekali).

Bersama-sama, transaksi atomik dan retry idempoten membantu Anda menghindari pembaruan parsial dan penagihan ganda yang tidak disengaja.

Consistency: Menjaga Data Sesuai Aturan

Consistency dalam ACID bukan berarti “data terlihat masuk akal” atau “semua replika cocok.” Ini berarti setiap transaksi harus membawa basis data dari satu state valid ke state valid berikutnya—menurut aturan yang Anda definisikan.

Consistency didefinisikan oleh aturan yang Anda pilih

Basis data hanya bisa menjaga konsistensi relatif terhadap konstraint eksplisit, trigger, dan invariant yang menjelaskan apa arti "valid" untuk sistem Anda. ACID tidak menciptakan aturan ini; ia menegakkannya selama transaksi.

Contoh umum meliputi:

  • Foreign keys: setiap order.customer_id harus menunjuk ke pelanggan yang ada.
  • Unique constraints: tidak ada dua pengguna yang berbagi email yang sama.
  • Check constraints / invariants: saldo akun tidak boleh negatif, atau jumlah barang tidak bisa negatif.

Jika aturan-aturan ini ada, basis data akan menolak transaksi yang melanggarnya—sehingga Anda tidak berakhir dengan data yang “setengah valid.”

Validasi aplikasi vs. konstraint basis data

Validasi di aplikasi penting, tetapi tidak cukup sendirian.

  • Validasi aplikasi meningkatkan pengalaman pengguna (pesan error jelas, umpan balik awal) dan dapat menegakkan aturan bisnis kompleks.
  • Konstraint basis data bertindak sebagai penjaga terakhir—khususnya saat banyak layanan, job latar belakang, import, atau tool admin menulis ke tabel yang sama.

Mode kegagalan klasik adalah memeriksa sesuatu di app ("email tersedia") lalu menyisipkan baris. Di bawah konkurensi, dua permintaan bisa lewat pengecekan pada saat bersamaan. Unique constraint di basis datalah yang menjamin hanya satu insert berhasil.

Tampilan konsistensi dalam praktik

Jika Anda mengkodekan "saldo tidak negatif" sebagai konstraint (atau menegakkannya dengan benar dalam satu transaksi), maka setiap transfer yang membuat rekening tertarik harus gagal secara keseluruhan. Jika Anda tidak mendefinisikan aturan itu di mana pun, ACID tidak bisa melindunginya—karena tidak ada yang menegakkannya.

Consistency pada akhirnya soal eksplisit: definisikan aturan, lalu biarkan transaksi memastikan aturan itu tidak pernah dilanggar.

Isolation: Bekerja Aman di Bawah Konkurensi

Isolation memastikan transaksi tidak saling menginjak. Saat sebuah transaksi berjalan, transaksi lain tidak boleh melihat kerja yang setengah jadi atau secara tidak sengaja menimpanya. Tujuannya sederhana: setiap transaksi harus berperilaku seolah-olah ia berjalan sendirian, meskipun banyak pengguna aktif bersamaan.

Mengapa konkurensi membuat ini sulit

Sistem nyata sibuk: pelanggan memesan, agen dukungan memperbarui profil, job latar belakang merekonsiliasi pembayaran—semua terjadi bersamaan. Aksi-aksi ini tumpang tindih dalam waktu, dan sering menyentuh baris yang sama (saldo akun, hitungan inventaris, atau slot pemesanan).

Tanpa isolasi, timing menjadi bagian dari logika bisnis Anda. Pembaruan “kurangi stok” bisa balapan dengan checkout lain, atau laporan bisa membaca data di tengah perubahan dan menampilkan angka yang tidak pernah ada dalam keadaan stabil.

Isolasi biasanya dapat dikonfigurasi

Isolasi penuh "bertindak seolah-olah Anda sendirian" bisa mahal. Itu dapat mengurangi throughput, menambah waktu tunggu (kunci), atau menyebabkan retry transaksi. Sementara itu, banyak alur kerja tidak membutuhkan perlindungan terketat—membaca analitik kemarin, misalnya, bisa mentolerir sedikit inkonsistensi.

Itulah mengapa basis data menawarkan level isolasi yang dapat Anda pilih: seberapa banyak risiko konkurensi yang Anda terima sebagai tukar performa dan lebih sedikit konflik.

Pratinjau singkat: anomali yang dicegah (atau diizinkan) oleh isolasi

Saat isolasi terlalu lemah untuk beban kerja Anda, Anda akan menemui anomali klasik:

  • Dirty reads: membaca perubahan yang belum dikomit oleh transaksi lain.
  • Lost updates: dua transaksi menimpa satu sama lain sehingga satu set perubahan hilang.
  • Phantom reads: menjalankan ulang query mengembalikan set baris berbeda karena transaksi lain menyisipkan atau menghapus data yang cocok.

Memahami mode kegagalan ini memudahkan memilih level isolasi yang sesuai dengan janji produk Anda.

Anomali Umum yang Dicegah (atau Diizinkan) oleh Isolasi

Tambahkan constraint nyata ke database
Buat backend Go + PostgreSQL dengan constraint yang melindungi aturan bisnis inti Anda.

Isolasi menentukan apa yang transaksi lain boleh “lihat” saat transaksi Anda masih berjalan. Saat isolasi terlalu lemah, Anda bisa mendapatkan anomali—perilaku yang mungkin terjadi secara teknis tetapi mengejutkan pengguna.

Anomali baca

Dirty read terjadi saat Anda membaca data yang ditulis transaksi lain tetapi belum dikomit.

Skenario: Alex mentransfer $500 keluar dari sebuah akun, saldo sementara menjadi $200, dan Anda membaca $200 itu sebelum transfer Alex kemudian gagal dan di-rollback.

Dampak pengguna: pelanggan melihat saldo rendah yang salah, aturan fraud memicu secara keliru, atau agen dukungan memberikan jawaban yang salah.

Non-repeatable read berarti Anda membaca baris yang sama dua kali dan mendapatkan nilai berbeda karena transaksi lain mengkomit di antaranya.

Skenario: Anda memuat total order ($49.00), lalu menyegarkan sebentar kemudian melihat $54.00 karena baris diskon dihapus.

Dampak pengguna: "Total saya berubah saat saya cek out," menyebabkan ketidakpercayaan atau keranjang ditinggalkan.

Phantom read mirip non-repeatable read, tetapi pada himpunan baris: query kedua mengembalikan baris tambahan (atau hilang) karena transaksi lain menyisipkan/menghapus record yang cocok.

Skenario: Pencarian hotel menunjukkan "3 kamar tersedia," lalu saat memesan sistem memeriksa kembali dan menemukan tak ada karena reservasi baru ditambahkan.

Dampak pengguna: percobaan double booking, layar ketersediaan tidak konsisten, atau overselling inventaris.

Anomali tulis (bug dunia nyata yang umum)

Lost update terjadi ketika dua transaksi membaca nilai yang sama dan keduanya menulis kembali pembaruan, dengan tulis terakhir menimpa yang sebelumnya.

Skenario: Dua admin mengedit harga produk yang sama. Keduanya mulai dari $10; satu menyimpan $12, yang lain menyimpan $11 terakhir.

Dampak pengguna: perubahan seseorang hilang; total dan laporan salah.

Write skew terjadi ketika dua transaksi masing-masing membuat perubahan yang secara individu valid, tetapi bersama-sama melanggar aturan.

Skenario: Aturan: “Setidaknya satu dokter on-call harus terjadwal.” Dua dokter secara independen menandai diri off-call setelah memeriksa bahwa yang lain masih on-call.

Dampak pengguna: Anda berakhir tanpa cakupan, meskipun setiap transaksi “lulus” pengecekannya.

Kenapa tidak selalu gunakan isolasi terketat?

Isolasi lebih kuat mengurangi anomali tetapi dapat menambah waktu tunggu, retry, dan biaya saat konkurensi tinggi. Banyak sistem memilih isolasi lebih lemah untuk analitik baca-berat, sementara menggunakan pengaturan lebih ketat untuk pergerakan uang, pemesanan, dan alur lain yang kritis terhadap kebenaran.

Level Isolasi: Memilih Pengaturan Keamanan yang Tepat

Isolasi tentang apa yang transaksi Anda boleh “lihat” sementara transaksi lain berjalan. Basis data mengekspresikan ini sebagai level isolasi: level lebih tinggi mengurangi perilaku mengejutkan, tetapi bisa menambah biaya throughput atau waktu tunggu.

Level isolasi umum

  • Read Uncommitted: Anda mungkin membaca perubahan yang belum dikomit oleh transaksi lain ("dirty reads"). Hampir tidak ada yang dicegah.
  • Read Committed: Anda hanya membaca data yang sudah dikomit, jadi dirty reads dicegah. Namun jika Anda menjalankan query yang sama dua kali, mungkin melihat hasil berbeda karena orang lain mengkomit di antaranya ("non-repeatable reads").
  • Repeatable Read: Bacaan yang sudah Anda lakukan tetap stabil selama transaksi, sehingga non-repeatable reads umumnya dicegah. Bergantung pada engine, Anda mungkin masih melihat “phantom” atau tidak.
  • Serializable: Transaksi berperilaku seolah-olah dijalankan satu per satu. Ini pengaturan terkuat, umumnya mencegah dirty reads, non-repeatable reads, dan phantoms, serta mengurangi banyak anomali tulis yang halus.

Memilih level: throughput vs. kebenaran

Tim sering memilih Read Committed sebagai default untuk aplikasi user-facing: performa baik, dan "tanpa dirty reads" cocok dengan ekspektasi kebanyakan orang.

Gunakan Repeatable Read saat Anda butuh hasil stabil di dalam transaksi (mis. membuat faktur dari sekumpulan line item) dan bisa mentolerir overhead. Gunakan Serializable saat kebenaran lebih penting daripada konkurensi (mis. menegakkan invariant kompleks seperti "tidak pernah oversell"), atau saat Anda tidak mudah memikirkan race condition di kode aplikasi.

Read Uncommitted jarang dipakai di sistem OLTP; kadang digunakan untuk monitoring atau pelaporan perkiraan di mana bacaan salah sesekali dapat diterima.

Peringatan penting: perilaku bervariasi

Nama-nama distandarisasi, tetapi jaminan tepat berbeda menurut engine basis data (dan kadang menurut konfigurasi). Konfirmasikan dengan dokumentasi basis data Anda dan uji anomali yang penting bagi bisnis Anda.

Durability: Membuat Commit Bertahan

Durability berarti setelah sebuah transaksi dikomit, hasilnya harus bertahan dari crash—kehilangan daya, restart proses, atau reboot mesin mendadak. Jika aplikasi Anda memberi tahu pelanggan "pembayaran berhasil", durability adalah janji bahwa basis data tidak akan "melupakan" fakta itu setelah kegagalan berikutnya.

Bagaimana basis data membuat commit bertahan dari crash

Kebanyakan basis data relasional mencapai durability dengan write-ahead logging (WAL). Secara garis besar, basis data menulis "tanda terima" berurutan dari perubahan ke log di disk sebelum menganggap transaksi dikomit. Jika basis data crash, ia bisa memutar ulang log saat startup untuk memulihkan perubahan yang sudah dikomit.

Untuk menjaga waktu pemulihan tetap wajar, basis data juga membuat checkpoint. Checkpoint adalah momen di mana basis data memastikan cukup perubahan terbaru telah ditulis ke file data utama, sehingga pemulihan tidak perlu memutar ulang sejarah log yang tak terbatas.

Durability tergantung pada penyimpanan dan konfigurasi

Durability bukan saklar on/off; ia bergantung pada seberapa agresif basis data memaksa data ke penyimpanan yang stabil.

  • Dengan pengaturan sinkron, basis data menunggu log di-flush (sering melalui fsync level OS) sebelum mengonfirmasi commit. Ini lebih aman, tapi dapat menambah latensi.
  • Dengan pengaturan asinkron, basis data bisa mengakui commit sebelum log benar-benar ada di penyimpanan tahan banting. Performa meningkat, tetapi crash bisa kehilangan transaksi "terkini".

Perangkat keras di bawahnya juga penting: SSD, RAID controller dengan write cache, dan volume cloud bisa berperilaku berbeda saat kegagalan.

Backup dan replikasi terkait—tetapi berbeda

Backup dan replikasi membantu Anda memulihkan atau mengurangi downtime, tapi bukan hal yang sama dengan durability. Sebuah transaksi bisa durable di primary meskipun belum mencapai replica, dan backup biasanya snapshot titik-waktu bukannya jaminan per-commit.

Bagaimana Basis Data Menegakkan ACID di Balik Layar

Rencanakan transaksi Anda dengan jelas
Gunakan chat berfokus perencanaan untuk menentukan invarian, tabel, dan batas transaksi sebelum menulis kode.

Saat Anda BEGIN sebuah transaksi dan kemudian COMMIT, basis data mengoordinasikan banyak bagian: siapa boleh membaca baris mana, siapa boleh memperbarui, dan apa yang terjadi jika dua orang mencoba mengubah record yang sama.

Pessimistic vs. optimistic concurrency control

Pilihan penting “di balik layar” adalah bagaimana menangani konflik:

  • Pessimistic locking mengasumsikan konflik mungkin terjadi. Saat transaksi memperbarui baris, basis data menguncinya sehingga transaksi lain harus menunggu. Ini mencegah banyak anomali, tetapi dapat menyebabkan blocking.
  • Optimistic approaches mengasumsikan konflik jarang. Transaksi berjalan dengan blok yang lebih sedikit, dan basis data mendeteksi konflik saat commit (atau lewat cek) dan mungkin menolak satu transaksi sehingga perlu di-retry.

Banyak sistem memadukan kedua ide tergantung beban kerja dan level isolasi.

MVCC: pembaca tidak memblok penulis

Basis data modern sering menggunakan MVCC (Multi-Version Concurrency Control): alih-alih hanya menyimpan satu salinan baris, basis data menyimpan beberapa versi.

  • Pembaca bisa melihat snapshot konsisten (versi lama) tanpa menunggu.
  • Penulis bisa membuat versi baru sementara pembaca berlanjut.

Ini alasan besar beberapa basis data menangani banyak baca dan tulis secara bersamaan dengan lebih sedikit blocking—meskipun konflik tulis/tulis tetap perlu diselesaikan.

Deadlock: saat tunggu membentuk loop

Kunci bisa menyebabkan deadlock: Transaksi A menunggu kunci yang dipegang B, sementara B menunggu kunci yang dipegang A.

Basis data biasanya menyelesaikan ini dengan mendeteksi siklus dan membatalkan salah satu transaksi ("deadlock victim"), mengembalikan error sehingga aplikasi dapat retry.

Tanda praktis ada yang salah

Jika penegakan ACID menyebabkan friction, Anda sering melihat:

  • Lock waits meningkat selama puncak penggunaan
  • Timeouts (query gagal setelah menunggu terlalu lama)
  • Hot spot kontensi (beberapa baris/tabel sering diperbarui, seperti counter atau field "last seen")

Gejala ini sering berarti saatnya meninjau ukuran transaksi, indexing, atau strategi isolasi/penguncian yang sesuai untuk beban kerja.

Bagaimana ACID Membentuk Keputusan Desain Aplikasi

Jaminan ACID bukan hanya teori basis data—mereka memengaruhi bagaimana Anda mendesain API, job latar belakang, dan bahkan alur UI. Ide inti: putuskan langkah mana yang harus berhasil bersama, lalu bungkus hanya langkah-langkah itu dalam transaksi.

Merancang API sekitar “satu perubahan bisnis”

API transaksional yang baik biasanya memetakan ke satu tindakan bisnis, meskipun menyentuh banyak tabel. Misalnya, operasi /checkout bisa: membuat order, mereservasi inventaris, dan mencatat payment intent. Tulis basis data itu biasanya sebaiknya berada dalam satu transaksi sehingga commit bersama (atau rollback bersama) jika ada validasi yang gagal.

Pola umum:

  • Lakukan validasi input sebelum membuka transaksi.
  • Buka transaksi.
  • Lakukan pembacaan/penulisan minimum yang diperlukan.
  • Commit.

Ini menjaga atomicity dan consistency sambil menghindari transaksi yang lambat dan rapuh.

Batas transaksi dalam request, layanan, dan job

Di mana Anda menempatkan batas transaksi tergantung pada apa arti "satu unit kerja":

  • Request pengguna: Jaga transaksi pendek—idealnya beberapa query. Jangan pegang kunci saat merender view atau menunggu respons downstream.
  • Background jobs: Anggap tiap percobaan job sebagai unit kerja. Jika job memproses 10.000 record, commit dalam batch sehingga Anda bisa restart dengan aman.
  • Batas layanan: Lebih baik menjaga transaksi di dalam database satu layanan. Menyeberangi layanan biasanya butuh pendekatan berbeda (mis. outbox), karena satu transaksi ACID sulit mencakup banyak basis data.

Penanganan error: rollback, retry, dan replay aman

ACID membantu, tapi aplikasi Anda tetap harus menangani kegagalan dengan benar:

  • Rollback on error: Jika ada langkah yang gagal, batalkan transaksi sehingga pembaruan parsial tidak bocor.
  • Retry on transient errors: Kegagalan serialisasi dan deadlock normal di bawah konkurensi. Mengulang seluruh transaksi seringkali solusi yang tepat.
  • Buat operasi idempoten: Jika permintaan di-retry (oleh klien atau job runner), Anda harus bisa "memutar ulang dengan aman" tanpa double-charge atau double-ship—gunakan idempotency keys dan unique constraints.

Anti-pattern umum

Hindari transaksi panjang, memanggil API eksternal di dalam transaksi, dan waktu pikir pengguna di dalam transaksi (mis. "kunci baris keranjang, minta pengguna konfirmasi"). Ini menambah kontensi dan membuat konflik isolasi jauh lebih mungkin.

Di mana tool bisa membantu (tanpa mengubah dasar)

Jika Anda membangun sistem transaksional dengan cepat, risiko terbesar jarang "tidak tahu ACID"—melainkan menyebarkan satu tindakan bisnis ke banyak endpoint, job, atau tabel tanpa batas transaksi yang jelas.

Platform seperti Koder.ai bisa membantu mempercepat sambil tetap merancang di sekitar ACID: Anda bisa mendeskripsikan alur kerja (mis. "checkout dengan reservasi inventaris dan payment intent") di chat perencanaan, menghasilkan UI React plus backend Go + PostgreSQL, dan iterasi dengan snapshot/rollback jika skema atau batas transaksi perlu diubah. Basis data tetap menegakkan jaminan; nilai tambahnya mempercepat jalan dari desain benar ke implementasi bekerja.

ACID di Sistem Terdistribusi dan Multi-Layanan

Uji konkurensi sejak dini
Modelkan deadlock, retry, dan transaksi singkat dengan cepat dalam proyek kerja yang bisa Anda jalankan.

Satu basis data biasanya bisa memberikan jaminan ACID dalam satu batas transaksi. Setelah Anda menyebarkan kerja ke banyak layanan (dan seringkali banyak basis data), jaminan yang sama menjadi lebih sulit dipertahankan—dan lebih mahal bila dicoba.

Konsistensi vs. availability: trade-off yang terasa di produksi

Konsistensi ketat berarti setiap baca melihat "kebenaran commit terbaru." Ketersediaan tinggi berarti sistem terus merespons meskipun beberapa bagian lambat atau tidak dapat dijangkau.

Dalam setup multi-layanan, masalah jaringan sementara dapat memaksa pilihan: blokir atau gagal request sampai setiap peserta setuju (lebih konsisten, kurang tersedia), atau terima bahwa layanan mungkin sementara tidak sinkron (lebih tersedia, kurang konsisten). Tidak ada yang selalu benar—tergantung kesalahan apa yang bisnis Anda bisa toleransi.

Kenapa transaksi terdistribusi sulit

Transaksi terdistribusi membutuhkan koordinasi melintasi batas yang tidak sepenuhnya Anda kendalikan: delay jaringan, retry, timeout, crash layanan, dan kegagalan parsial.

Bahkan jika setiap layanan benar, jaringan bisa menciptakan ambiguitas: apakah layanan pembayaran mengkomit tetapi layanan order tidak menerima ack? Untuk menyelesaikannya dengan aman, sistem menggunakan protokol koordinasi (seperti two-phase commit), yang bisa lambat, mengurangi availability saat kegagalan, dan menambah kompleksitas operasional.

Pola praktis yang menggantikan "satu transaksi besar"

Sagas memecah alur kerja menjadi langkah-langkah, masing-masing dikomit secara lokal. Jika langkah selanjutnya gagal, langkah-langkah sebelumnya "dibatalkan" menggunakan aksi kompensasi (mis. refund).

Outbox/inbox membuat publishing event dan konsumsi dapat diandalkan. Sebuah layanan menulis data bisnis dan sebuah record "event untuk dipublikasikan" dalam satu transaksi lokal (outbox). Konsumen mencatat ID pesan yang sudah diproses (inbox) untuk menangani retry tanpa menduplikasi efek.

Eventual consistency menerima jendela pendek di mana data berbeda antar layanan, dengan rencana rekonsiliasi yang jelas.

Kapan merelaksasi jaminan—dan bagaimana mengendalikan risiko

Relaksasi jaminan saat:

  • Anda bisa mentolerir mismatch sementara (status pengiriman tertinggal dari pembuatan order).
  • Anda bisa memperbaiki kesalahan dengan kompensasi (refund, pembatalan).
  • Latensi dan uptime lebih penting daripada kebenaran global instan.

Kendalikan risiko dengan mendefinisikan invariant (apa yang tidak boleh dilanggar), merancang operasi idempoten, menggunakan timeout dan retry dengan backoff, serta memonitor drift (saga yang macet, kompensasi berulang, tabel outbox yang membesar). Untuk invariant yang benar-benar kritis (mis. "tidak pernah membelanjakan akun lebih dari saldonya"), pertahankan mereka di dalam satu layanan dan satu transaksi basis data bila memungkinkan.

Daftar Periksa Praktis: Mendesain, Menguji, dan Memantau Sistem ACID

Sebuah transaksi bisa "benar" di unit test namun gagal di bawah lalu lintas nyata, restart, dan konkurensi. Gunakan daftar periksa ini agar jaminan ACID selaras dengan perilaku produksi.

1) Desain: Definisikan invariant dan batas transaksi

Mulailah dengan menuliskan apa yang harus selalu benar (invariant data Anda). Contoh: "saldo akun tidak pernah negatif," "total order sama dengan jumlah line item," "inventaris tidak boleh turun di bawah nol," "sebuah pembayaran terhubung ke tepat satu order." Perlakukan ini sebagai aturan produk, bukan trivia basis data.

Kemudian putuskan apa yang harus berada dalam satu transaksi versus apa yang bisa ditunda.

  • Invariant data: daftar tabel/baris yang terlibat dan aturan tepatnya.
  • Skenario kegagalan: crash proses di tengah request, timeout jaringan setelah commit, retry menyebabkan duplikat, failover replica, disk penuh.
  • Profil konkurensi: operasi mana yang berjalan paralel (lonjakan checkout, update batch, job terjadwal), hotspot kontensi yang diperkirakan, dan apakah baca harus "sekarang" atau boleh sedikit usang.

Jaga transaksi kecil: sentuh lebih sedikit baris, lakukan pekerjaan lebih sedikit (jangan panggil API eksternal), dan commit cepat.

2) Uji: Buktikan perilaku di bawah race dan fault

Jadikan konkurensi sebagai dimensi uji.

  • Tes kondisi balapan: jalankan operasi kritis yang sama bersamaan (mis. dua checkout untuk item terakhir) dan pastikan invariant tidak pernah rusak.
  • Fault injection: bunuh proses app di tengah transaksi; sisipkan timeout; simulasikan retry; paksa restart DB; verifikasi hasilnya terkomit sekali atau di-rollback dengan aman.
  • Load test dengan pemeriksaan kebenaran: di bawah throughput puncak, validasi bukan hanya latensi tetapi juga total, hitungan, dan konstraint "tanpa duplikat".

Jika Anda mendukung retry, tambahkan idempotency key eksplisit dan uji "permintaan diulang setelah sukses."

3) Monitor: Tangkap masalah ACID sebelum pengguna melakukannya

Pantau indikator bahwa jaminan Anda menjadi mahal atau rapuh:

  • Lock waits dan waktu antre (kontensi meningkat)
  • Deadlocks (frekuensi, query korban)
  • Transaksi berjalan lama (sering penyebab utama)
  • Replication lag (bacaan usang dan failover tertunda)
  • Waktu commit/fsync (tekanan penyimpanan; biaya durabilitas)

Beri alert pada tren, bukan hanya lonjakan, dan kaitkan metrik ke endpoint atau job yang menyebabkannya.

Aturan praktis: isolasi dan cakupan transaksi

Gunakan isolasi terlemah yang masih melindungi invariant Anda; jangan langsung "maksimalkan". Saat Anda butuh ketepatan untuk bagian kecil yang kritis (pergerakan uang, pengurangan inventaris), persempit transaksi hanya ke bagian itu dan taruh semua lainnya di luar.

Pertanyaan umum

What does ACID mean in a database, in practical terms?

ACID adalah seperangkat jaminan transaksional yang membantu basis data berperilaku terduga saat terjadi kegagalan dan kondisi konkurensi:

  • Atomicity: semua langkah berhasil atau tidak satu pun
  • Consistency: setiap commit mempertahankan aturan/konstraint yang Anda tetapkan
  • Isolation: transaksi bersamaan tidak saling mengganggu dengan cara yang berbahaya
  • Durability: perubahan yang dikomit bertahan setelah crash
What is a transaction, and why does it matter?

Transaksi adalah satu “unit kerja” yang dianggap sebagai satu paket oleh basis data. Meskipun melakukan beberapa pernyataan SQL (mis. membuat order, mengurangi inventaris, mencatat intent pembayaran), transaksi hanya punya dua hasil:

  • Commit: semua perubahan menjadi resmi
  • Rollback: tidak satu pun perubahan yang berlaku
Why are partial updates such a big business problem?

Karena partial update menciptakan kontradiksi dunia nyata yang mahal untuk diperbaiki nanti—misalnya:

  • pelanggan dibebankan tapi tidak ada order tercatat
  • order tercatat tapi inventaris tidak dikurangi (overselling)
  • satu sisi transfer diterapkan tapi sisi lain tidak

ACID (khususnya atomicity + consistency) mencegah keadaan “setengah jadi” ini muncul sebagai kebenaran.

How does atomicity prevent half-finished operations?

Atomicity memastikan basis data tidak pernah memaparkan transaksi yang “setengah jadi”. Jika sesuatu gagal sebelum commit—crash aplikasi, putus jaringan, restart DB—transaksi akan di-rollback sehingga langkah awal tidak bocor ke state persisten.

Secara praktis, atomicity membuat perubahan multi-langkah (mis. transfer yang memperbarui dua saldo) menjadi aman.

If ACID is safe, why do I still need idempotency and retries?

Anda tidak selalu dapat mengetahui apakah commit terjadi jika klien kehilangan respons (mis. timeout jaringan tepat setelah commit). Gabungkan transaksi ACID dengan:

  • Retries untuk kegagalan sementara
  • Idempotency keys (atau konstraint unik) sehingga mengulangi permintaan yang sama diterapkan paling banyak sekali

Ini mencegah partial update dan double-charge/double-write yang tidak disengaja.

What does “consistency” mean in ACID (and what doesn’t it mean)?

Dalam ACID, “consistency” berarti basis data berpindah dari satu keadaan valid ke keadaan lain menurut aturan yang Anda tentukan—konstraint, foreign key, unique, dan check.

Jika Anda tidak mendefinisikan aturan (mis. “saldo tidak boleh negatif”), ACID tidak bisa menegakkannya secara otomatis. Basis data membutuhkan invariants eksplisit agar bisa melindungi.

Why use database constraints if my application already validates inputs?

Validasi di aplikasi meningkatkan pengalaman pengguna dan dapat menegakkan aturan rumit, tetapi dapat gagal di bawah konkurensi (dua permintaan lulus pengecekan bersamaan).

Konstraint basis data adalah penjaga terakhir:

  • Unique constraints mencegah email ganda
  • Foreign keys mencegah record yatim
  • Check constraints/invariants mencegah nilai invalid

Gunakan keduanya: validasi di awal di aplikasi, dan penegakan pasti di basis data.

What kinds of concurrency bugs does isolation protect against?

Isolation mengontrol apa yang transaksi Anda boleh lihat saat transaksi lain berjalan. Isolasi lemah dapat menghasilkan anomali seperti:

  • Dirty reads: melihat data yang belum dikomit
  • Non-repeatable reads: baris yang sama berubah antar pembacaan
  • Phantoms: query mengembalikan set baris yang berbeda
  • Lost updates / write skew: penulisan bersamaan merusak kebenaran

Level isolasi memungkinkan Anda menukar performa dengan perlindungan terhadap anomali ini.

How do I choose an isolation level without killing performance?

Praktik umum yang masuk akal adalah Read Committed untuk banyak aplikasi OLTP karena mencegah dirty reads dengan performa baik. Naikkan jika perlu:

  • Repeatable Read untuk hasil yang stabil di dalam transaksi (mis. pembuatan faktur)
  • Serializable untuk invariant yang sangat kritis (mis. mencegah oversell) jika Anda dapat mentolerir lebih banyak kontensi/retry

Selalu konfirmasi perilaku pada engine basis data Anda karena detailnya berbeda-beda.

What does durability guarantee, and what can weaken it?

Durability berarti setelah basis data mengonfirmasi commit, perubahan akan bertahan dari crash. Biasanya diimplementasikan lewat write-ahead logging (WAL) dan checkpoint.

Perlu diingat trade-off konfigurasi:

  • Synchronous commit/fsync: lebih aman, latensi lebih tinggi
  • Asynchronous: lebih cepat, tapi mungkin kehilangan transaksi "terkini" setelah crash

Backup dan replikasi membantu recovery/availability, tetapi bukan jaminan durability per commit.

Related posts